Monday, August 31, 2026
59 changes · master
Enhancements to existing features
Portal users should see much faster results when filtering their tasks by milestone. This improves responsiveness on the My Tasks page by avoiding a slow database search path.
Original PR description
Go to `/my/tasks`. The search on milestones is really slow. Performance improvement for a portal user: | | Time | Query plan | |--------|--------|--------| | Before | ~3.6s |…
Go to `/my/tasks`. The search on milestones is really slow. Performance improvement for a portal user: | | Time | Query plan | |--------|--------|--------| | Before | ~3.6s | https://explain.dalibo.com/plan/dga21917bc86eg54 | | After | ~60ms | https://explain.dalibo.com/plan/91b818beg2f1077f | For portal users, complex record rules require joining the `project` table. Because the query includes a `limit=1`, the postgresql query planner assumes it will find a matching row almost immediately. Hoping for a "fast exit", it chooses to sequentially scan the `project_id` index to perform a Merge Join. However, if it doesn't find a match early on, it ends up scanning the entire index, resulting in a massive slowdown. We update the `search_count` constraint from `limit=1` to `limit=80`. By increasing the limit, we alter postgresql's cost estimation. The planner can no longer assume a cheap "fast exit" is guaranteed, which forces it to abandon the flawed Merge Join strategy. Instead, it correctly evaluates the query and chooses the index on `milestone_id` to retrieve the records. task-6373729 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283239
Warehouse users can now split stock move lines into equal-sized packages directly from detailed operations instead of manually creating each line. The process keeps the correct source locations and can suggest package size from the selected package type, making packing faster and less error-prone.
Original PR description
Before, whenever a user wanted to split move lines in different equal by size packages, user had to manually add new lines. Now by provividing the size of a package, it can be done automatically. This functionality takes into account current locations, and preserves them for newly created move lines. Moveover, if several move lines are picked, it is impossible to split them together, only separately. Moveover, Package size can be calculated automatically by providing Package Type. We check the UoMs that have this package type, and get the relative factor with the move UoM. If several UoMs have this package type, then the first one is picked. task-6034274 I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Manufacturing users can now open a complete, ordered view of a bill of materials, including nested subassemblies, directly from the BoM form. This makes it faster to review and update components without manually opening each related BoM one by one.
Original PR description
Currently, when a BoM contains components with child BoM, clicking `Compare BoMs` shows the immediate components only in list view. Users still need to open each BoM separately to edit nested…
Currently, when a BoM contains components with child BoM, clicking `Compare BoMs` shows the immediate components only in list view. Users still need to open each BoM separately to edit nested components. This change will adds a new smartbutton that shows: - Components: number of components of the BoM - SubAssembly: number of sub BoMs inside the BoM Clicking the smartbutton opens the list of exploded components by following the hierarchy order, which provides a complete view of the BoM structure in a single place, allows user to update nested components and without navigating manually. ### About `forced_order_ids` context: ### - The action uses a `forced_order_ids` context key to preserve the exploded hierarchy order instead of the model's default ordering when the list view performs a `search_fetch()`. - When opening the exploded list view -> product form -> BoM of that product, `forced_order_ids` should be reset to `False`. Otherwise, if the same child BoM is added multiple times, the BoM show repeated BoM lines. [video](https://drive.google.com/file/d/1M5k4XXGvr8KASzhBsRRByV3vepM8BBn1/view?usp=drivesdk) - When the user searches for a component through the search panel, `forced_order_ids` should filtered using the records returned by the `super()` call, which only contains the matched records. Without that filtering, the search would stop working correctly and would return all `forced_order_ids`. [video](https://drive.google.com/file/d/1De0IfTyPPTjk02rCOh2yJPHa9wI0Zi_W/view?usp=drivesdk) - It is also used to change the order of the fields in the search view, because when the list is opened from the `Components/SubAssembly` smartbutton, the component search should be on top, in other cases the BoM search should be on top. Ent PR https://github.com/odoo/enterprise/pull/120254 task-6247051
Warehouse barcode scanning now automatically creates a package when a scanned packaging barcode has a package type, such as a box for a pack of six. This helps keep grouped items packed correctly during stock and manufacturing barcode flows, reducing manual packing steps and improving consistency.
Original PR description
When users scan packaging barcodes, they want them to be stored together for UoMs with packaging type specified. For example, if Pack of 6 is scanned and it has a packaging type, you would like a package to be automatically created if packaging barcode for Pack of 6 was scanned. This commit implements exactly that. task-6034274
Timesheets billing target menus and settings have been reorganized and renamed to make them easier to understand. The update also keeps related menu visibility in sync when leaderboard settings change, reducing confusion from outdated menu options.
Original PR description
In this commit, we clean the billing target and menus of Timesheets. In short, we: - Add a new "Billable Time" parent menu that is hidden when billable time targets are not displayed. - Hide the "Tips" configuration menu when the "Billing rate leaderboard" is disabled. - Reword the billable time settings (billable time targets & billable time leaderboard). - Rename the billable time target labels in their configuration view and on the Employee form. Additionally, we ensure the menu cache is invalidated when the leaderboard setting changes, the same way as currently done for the billing rate setting. Without this, the "Tips" configuration menu would stay stale until the cache got cleared by some potentially unrelated reason. task-6486399
Manufacturing scheduling now skips over fully occupied time blocks instead of checking many tiny windows one by one. This significantly speeds up planning when work centers are heavily booked, especially for very short work orders.
Original PR description
### Description of the issue/feature this PR addresses: The workcenter planning logic in _get_first_available_slot can become inefficient when searching for very short available slots. The method…
### Description of the issue/feature this PR addresses: The workcenter planning logic in _get_first_available_slot can become inefficient when searching for very short available slots. The method repeatedly builds small candidate time windows and checks them against existing workorder and leave intervals, potentially iterating many times before finding a free slot. This leads to unnecessary computational overhead in scenarios where a large number of busy intervals exist and the remaining duration to schedule is small. ### Current behavior before PR: The planner checks for conflicts by computing the intersection between the candidate window and the busy intervals. When a conflict is detected, the candidate window is shifted forward (or backward) to the end (or start) of the intersection, and the process is repeated until a free slot is found. This approach requires repeatedly performing full interval merge operations, which becomes disproportionately expensive when the candidate windows are very small and the loop iterates many times. ### Desired behavior after PR is merged: The planner uses a new Intervals.conflicting() helper to retrieve the entire busy interval that overlaps with the candidate window. Instead of advancing only to the end of the intersection slice, the planner can jump directly to the end (or start) of the full busy interval. This avoids repeated full-merge work, reduces the number of iterations needed to find a valid slot, and prevents pathological performance slowdowns in short-duration planning scenarios. ### Benchmarks Profiling _get_first_available_slot with different workorder durations. Database has multiple months that are fully booked. Speedup is more dramatic with shorter durations but there is at minimum minor improvements across the board. | Work Order Duration | Before | After | | --- |---|---| | 1sec | ~2.5min | <1sec | | 1min | ~2sec | <1sec | ### References opw-5437256 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284278 Forward-Port-Of: odoo/odoo#246015
The map view now more reliably opens location details when users select an item and keeps their map layout when they leave and return. Users also get a cleaner side panel with resizable width, making field service planning easier and less repetitive.
Original PR description
First commit fixes an issue where clicking a list item to center the map and open its marker's popup often did nothing at all. The pending record was stored in a plain, non-reactive field, only ever…
First commit fixes an issue where clicking a list item to center the map and open its marker's popup often did nothing at all. The pending record was stored in a plain, non-reactive field, only ever rechecked by updateMap()'s useLayoutEffect, which reruns on every patch of the component - but nothing about the click itself reliably causes a patch: closing pinListPopover (a mobile-only bottom sheet) only triggers one if it was actually open. On desktop, where it never is, closing it is a no-op, so no patch happens and the pending record is never rechecked. The popup would then only show up later, incidentally, whenever some unrelated interaction (toggling a group fold, expanding Unlocated, ...) happened to trigger a patch of its own. Even when it did open, doing so before the map had finished panning to it made it visibly reposition/flicker, since it isn't tracked by Leaflet's own pan animation the way a native Leaflet popup would be. Replace the deprecated Owl 2 useLayoutEffect compat shim with native onMounted/onPatched, and branch on whether pinListPopover is actually open: when it isn't (desktop), open the popup right away instead of depending on a patch that was never going to happen; when it is (mobile), wait for the patch that closing it triggers - the same one that recreates the markers - before opening the popup, so it anchors to the freshly created marker element instead of the stale one about to be removed. Also wait for the pan's moveend before creating the popup, to avoid the repositioning/flicker. Second commit preserves map view state when navigating away to another view and returning, preventing the map from resetting to its default initial state. The following state variables have been moved into the map model so they can be exported via `getLocalState` and restored upon remounting: * **Group Folding:** Retains folded group states via `closedGroupIds`. * **Unlocated Panel:** Maintains the expansion toggle state of `expandUnlocated`. Since closed groups now outlive a reload, the groups a view folds by default can no longer be re-applied on every load: they come from `_getDefaultClosedGroupIds`, only consulted when the closed groups are (re)initialized, i.e. when the groupBy changes. Field service maps fold their material resources through that hook, instead of overwriting `closedGroupIds` after each load, which discarded both the restored state and whatever the user had folded. The current user's position is instead persisted in `localStorage`, keyed by the view and action. When it comes from an address search or from the company's address, the actual coordinates are cached. When it comes from the browser's geolocation API, only the fact that it should be looked up again is cached, since GPS coordinates can quickly become stale. Third commit refines list sidebar and group header styling. Group headers now span the full width of the list sidebar and darken on hover, like group rows in list views. The group pin icon is narrower so it no longer feels bloated next to the group name, and the Google Maps button in group headers no longer has a background or hover effect of its own. Also unifies the "clickable" styling of located pins and unlocated records under a single class, and aligns the unlocated section's fold icon with the one used for other groups. Record rows now carry their own horizontal padding instead of the list itself, so their hover background spans the sidebar's full width. The "my location" button icon uses the default icon size, and the pin list no longer keeps a stray bottom spacing. Fourth commit adds the possibility to resize map view's side panel (pin list) similarly to what is done in the search panel or gantt view. task-6431782
Users can now switch to a Kanban layout when selecting documents from the chatter, making it easier to browse files visually. The dialog has also been simplified by removing less relevant fields and filters such as Favorites, Activities, and Owner.
Original PR description
`*` = ai_documents_source, documents_spreadsheet **Purpose:** When we add document in a chatter, the file selection dialog currently displays only the list view. **Specifications:** - Add a Kanban view option to the document selection dialog. - Remove the Favorites field/filter. - Remove the Activities field. - Remove the Owner field. Task-6236894
AI agents can now work through longer tasks before reaching their activity limit. If they do reach the limit, they are guided to summarize progress and ask the user whether to continue instead of abruptly ending the conversation with an error.
Original PR description
Before this change, AI Agents were limited to a default of 20 successive API calls (agentic loop turns) before stopping. When this limit was exceeded, the Python backend raised a hard UserError exception. This commit increases the default fallback limit as well as introducing a graceful fallback for the case when that limit were to be reached. task-6313259
Document and folder action menus are now more consistent across views, making common actions easier to find. Users can copy links from the sidebar and add stars from right-click or top action menus, while the folder cog menu has been simplified by removing the Info & Tag action.
Original PR description
Purpose ======= Remove the "Info & Tag" action from the folder cog wheel. Make the order of the action consistent across the views, add "Copy Link" in the side bar, and "Add star" in the right-click / top actions. Task-6474895
Belgian payroll calendars now show working time reorganisation details for variable schedules. Users can record a Time Reorganisation Amount for credit-time arrangements, with automatic full-time value filling in one case and a blank value available in another.
Original PR description
. Show Working Time Reorganisation field with the variable calendars . Add Time Reorganisation Amount field to show mount of time for the Credit-Time . If user select 3, display a new field Time Reorganisation Amount (float) below and made it filled with the same value as Hours per week (we expect a full credit time) . If user select 4, display the same field Time Reorganisation Amount (float) below and let it empty task-6449163
The point of sale now highlights available loyalty rewards with an attention animation, helping cashiers notice and apply customer benefits more easily. Text input popups can also clean pasted content by removing line breaks, reducing formatting issues during checkout workflows.
Original PR description
In This Commit: ================ - Add removeNewLines option to TextInputPopup to strip newlines on paste - Add a reward attention animation to rewards button when active Task-6462056
Long-term sickness absences can now be separated into the appropriate sick leave categories based on duration. This helps payroll and HR reporting reflect guaranteed salary, unpaid sick leave, and long-term sick periods more accurately for extended absences.
Original PR description
This commit will support the new split for Long Term Sickness (LEAVE280) Ex:- Leave from 1st Jan 2026 to 30th Jan 2027. This time off will be split into: - LEAVE110 - Sick Time Off: 30 days - LEAVE214 - Sick Time Off (Without Guaranteed Salary): 335 days - LEAVE280 - Long Term Sick: 30 days task-[6307896](https://www.odoo.com/odoo/project/1251/tasks/6307896) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Belgian payroll now separates employee sick leave after 12 months into a dedicated long-term sickness category. This improves payroll accuracy and compliance by distinguishing long-term sickness from earlier unpaid sickness periods, including relapse cases.
Original PR description
Currently, a sick leave for an employee is split into LEAVE110 (guaranteed salary, first 30 days) and LEAVE214 (no guaranteed salary, after 30 days). This commit adds new split for Long term sickness…
Currently, a sick leave for an employee is split into LEAVE110 (guaranteed salary, first 30 days) and LEAVE214 (no guaranteed salary, after 30 days). This commit adds new split for Long term sickness (LEAVE280) for employee which is applied after 12 months sickness. What changed: - New helper `_get_l10n_be_long_term_sick_cutoff_date` return the cuttoff date at which a sickness chain crosses the 12-month threshold accounting relapse using `_l10n_be_get_sick_leave_twelve_months_cutoff_date` which is moved from `hr_payslip` to `hr_employee` as it will be commonly used for leave split and the paylsip work entry generation. - New helper `_get_l10n_be_long_term_sick_split` takes the existing LEAVE110/LEAVE214 split and converts whatever part of it falls on/after the 12-month cutoff into LEAVE280. If the cutoff falls in the middle of a segment, that segment is cut in two. - Applied this check to every place in `_get_l10n_be_sick_leave_split` that can return a final result. - `_get_leave_work_entry_type_dates` (legacy support for old, unmigrated data) gets the same 12-month check before its existing 30-day check. task-[6307896](https://www.odoo.com/odoo/project/1251/tasks/6307896)
Currency amounts written in words now use language-aware forms where supported, so documents can show grammatically correct currency names and number wording. This improves localization for languages with complex plural and gender rules while keeping existing formatting behavior when no specialized wording is available.
Original PR description
## Summary POC exploring a better use of `num2words` in `res.currency.amount_to_text()` (core `base` module). Today `amount_to_text()` converts the integral/fractional parts with a plain…
## Summary POC exploring a better use of `num2words` in `res.currency.amount_to_text()` (core `base` module). Today `amount_to_text()` converts the integral/fractional parts with a plain `num2words(...).title()` cardinal conversion, and always uses the currency's single-form `currency_unit_label`/`currency_subunit_label`. This is fine for English-like languages, but wrong for languages with grammatical number agreement: many Slavic languages use one of 3 noun forms depending on the last digit(s) of the amount (e.g. Ukrainian "гривня"/"гривні"/"гривень"), and some also need the numeral itself gendered (e.g. "одна гривня", not "один гривня", for 1 UAH). `num2words` already ships this data per `(language, ISO currency code)` via `CURRENCY_FORMS` + `pluralize()` + `_money_verbose`/`_cents_verbose`, normally consumed through its own `to='currency'` mode. That mode is not used directly here because it changes Odoo's current sentence shape (it always renders the subunit clause, even when zero, and joins the two clauses with its own separator instead of a translatable "and"). This PR uses that per-language data to build just the two correctly-inflected words, and plugs them into Odoo's *existing* template: - the zero-subunit clause is still dropped (no "...and zero cents"), - the two clauses are still joined with the translatable `"and"`. When `num2words` has no currency data for the `(language, currency)` pair, behavior falls back unchanged to the previous plain-cardinal + translated-label path. ## Example (Ukrainian / UAH) | Amount | Before | After | |---|---|---| | 1.00 | `Один Hryvnia` | `одна гривня` | | 2.00 | `Два Hryvnia` | `дві гривні` | | 5.00 | `П'Ять Hryvnia` | `п'ять гривень` | | 21.56 | `Двадцять Один Hryvnia and П'Ятьдесят Шість Kopiyka` | `двадцять одна гривня and п'ятдесят шість копійок` | (English/EUR and other currencies `num2words` has no data for are unaffected.) ## Scope / open questions (see inline code comments) - This is a POC: only currencies `num2words` explicitly models (`CURRENCY_FORMS`) get the improved output; everything else keeps today's behavior byte-for-byte. - The untranslated literal `"and"` in some locales is a pre-existing translation-coverage gap, unrelated to this change. - `num2words`' own data isn't uniformly correct/complete across all its language modules (e.g. English `EUR` forms don't pluralize `"euro"` upstream) - that's an upstream data quality question, not something this PR should paper over. - Longer term this raises the question of whether `currency_unit_label`/`currency_subunit_label` should stay only as the fallback path.
Users can now update tags directly in the subscriptions list instead of opening each subscription individually. This makes it faster to organize one or many subscriptions at once, improving day-to-day subscription management.
Original PR description
Before this commit, the Tags column in the subscriptions list view was read-only, preventing tags from being edited directly from the list. After this commit, tags can be edited directly from the subscriptions list, either on a single subscription or on multiple selected subscriptions at once. task-6222724
Belgian payroll now includes a warning when an employee may not meet the required minimum age rules. This helps payroll teams spot potential compliance issues earlier and reduce manual checks.
Original PR description
task-6515312
This update improves the HTML editor so users can keep editing list items even when special non-editable elements are present. It also restores safeguards that prevent editing placeholders from appearing in inappropriate places, reducing frustrating cursor behavior and regressions around links.
Original PR description
If a list item has a selection blocker, the user can be stuck and forced to move to the previous or next block instead of being able to continue to edit the list item. To get around this, this commit allows selection placeholders in list items. task-6394918 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279432 Forward-Port-Of: odoo/odoo#278323
Pay later receivables in Point of Sale settlement are now reconciled in one grouped operation instead of repeated partner-by-partner processing. This keeps the accounting outcome unchanged while reducing processing overhead, especially for sessions with many customers.
Original PR description
The pay later receivable lines are reconciled with one `reconcile()` call per partner, and each call filters the whole set of lines again. Reconcile them in a single `_reconcile_plan` call grouped by partner instead: the result is the same, but the recompute cascade of the ORM runs once instead of once per partner. opw-6458271 Related: https://github.com/odoo/odoo/pull/281495 Forward-Port-Of: odoo/enterprise#129637 Forward-Port-Of: odoo/enterprise#127415
Inventory batch picking is now part of the main stock app instead of being split across separate add-on modules. This simplifies setup and maintenance for businesses using delivery, localization, and batch picking features, while also removing unused legacy code.
Original PR description
(Re)opening an useless PR for the sake of the runbot's builds... **Enterprise PR**: odoo/enterprise#128175
This update addresses common website quality issues flagged by Lighthouse, especially around accessibility for keyboard and screen reader users. It also improves privacy and performance by hosting Google fonts locally by default, helping websites provide a smoother and more compliant visitor experience.
Original PR description
This PR fixes some common Lighthouse issues. Please have a look at the different commits for more information.
Resolved issues and error corrections
Customers previewing helpdesk tickets can now see the Field service button when related interventions exist. This fixes a visibility issue caused by a portal display change, helping users access completed field service information as expected.
Original PR description
Steps to reproduce: -------------------------- 1. Install helpdesk_planning_field_service_sale_timesheet and website with demo data. 2. Open a helpdesk team (e.g., Customer Care) and enable field…
Steps to reproduce: -------------------------- 1. Install helpdesk_planning_field_service_sale_timesheet and website with demo data. 2. Open a helpdesk team (e.g., Customer Care) and enable field service planning. 3. Create a new ticket in Customer Care, plan two interventions, and mark them as completed. 4. Click the cog menu of the helpdesk ticket and click Preview. Issue: ------- The "Field service" button is not visible. Cause: ------- Earlier, portal card visibility and existence were controlled directly in the QWeb template. After the portal refactoring, their visibility and existence are now managed by `portal.entry` records. As a result, the field service entry is incorrectly considered unavailable, causing the test to fail on runbot. Related build: https://runbot.odoo.com/runbot/build/123497806 Fix: ------ Determine the visibility of the "Field service" portal entry using the corresponding `portal.entry` record. (Similar https://github.com/odoo-dev/odoo/commit/84ccbded9f4f1f00359b2eef59bce9527368fb05) Related pr: https://github.com/odoo/enterprise/pull/129227 opw-6481737 Forward-Port-Of: odoo/enterprise#129860
This fix prevents broad internal searches when security rules rely on document-related fields, avoiding cases where systems with many attachments could become unable to fetch product documents efficiently. It improves reliability and performance for databases with large numbers of attachments without changing user-facing workflows.
Original PR description
When the security domain uses a many2one field that needs the `search_domain` context, the field itself might not be present in the user-given domain. When this happens, we assumed to search on all records which is too much in the case of attachments. The practical example is product.document where a simple fetch could not be done anymore on databases having a lot of attachments. opw-6486492 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285146
Users can again create folders, links, and document requests in the Documents app without seeing an error. The fix restores the proper behavior after a recent internal code change, preventing interruptions in document organization workflows.
Original PR description
The recent JS refactoring in commit b0aeefd7a1e moved several logic hooks into class methods, but they lost their execution context when passed down as props to child components. Steps to reproduce: 1. Open the Documents app. 2. Try to create a new folder, URL, or document request from the UI. 3. Traceback appears: `TypeError: Cannot read properties of undefined`. This commit fixes the issue by binding the component's context to these callback methods during setup so they execute correctly. Task-6524435
Belgian payroll reporting now uses the correct DMFA remuneration code for benefits in kind when an employee has no working days on the payslip. This helps ensure payroll declarations comply with Belgian reporting rules and reduces the risk of incorrect submissions.
Original PR description
Previously, BIK remunerations were reported in the DMFA using code 1. This change uses remuneration code 2 for BIK in months where the employee has no working days on the payslip. task: 6431491
Belgian SODA file imports now work for users who do not have Analytic Accounting access when that feature is not enabled. This prevents an unnecessary access error and lets affected accounting users complete their import workflow normally.
Original PR description
**Description of the issue/feature this PR addresses:** When importing a SODA XML file, users without the Analytic Accounting group encounter an access rights error even if Analytic Accounting is not…
**Description of the issue/feature this PR addresses:** When importing a SODA XML file, users without the Analytic Accounting group encounter an access rights error even if Analytic Accounting is not enabled. This occurs because the import wizard reads the `analytic_account_id` field on the `soda.analytic.mapping` model to build an internal dictionary of departments. Because this field is restricted to the Analytic Accounting group, the evaluation of this field crashes the import for users even when the Analytic Accounting feature is disabled. This commit resolves the issue by using `.sudo()` on the analytic mapping recordset to bypass the field-level group restriction. **Steps to reproduce:** - Log in as Mitchell Admin, change company to “My Belgian Company” - Settings > Users & Companies > Users > Mitchell Admin > Access Rights > Extra Rights > ensure “Analytic Accounting” is unchecked - Also ensure Mitchell Admin is not part of the “Analytic Accounting” group - Accounting Dashboard > remove “Favorites” from filter > drag & drop a SODA XML file to “Miscellaneous Operations” > save > observe Access Error **Current behavior before PR:** - Users who don't belong to the Analytic Accounting group encounter an access error when attempting to import SODA XML files, even when the Analytic Accounting feature isn't enabled **Desired behavior after PR is merged:** - Those users no longer receive an access error opw-6376039 Forward-Port-Of: odoo/enterprise#127867
The Peruvian sales ledger now reports the full gross value of sales affected by the 3% IGV withholding. This aligns the report with SUNAT expectations and avoids understating sales totals because the withholding is handled as a payment-time mechanism.
Original PR description
The 3% IGV withholding is a negative sale tax, so it reduced amount_total and the 14.4 ledger reported a net total. SUNAT expects the gross total of the operation, the withholding being a payment-time mechanism. task-5935227 Forward-Port-Of: odoo/enterprise#129089 Forward-Port-Of: odoo/enterprise#128849
Belgian payroll now correctly applies an employee's default private fuel card use when calculating payslips, when applicable. The related fuel card private use amount is also categorized as a benefit in kind, helping ensure the right social security and withholding treatment.
Original PR description
FUEL_CARD_PRIV never fired: nothing copied the employee's fuel_card_personal_use default into the payslip's property input. Fixed by seeding it in _compute_input_line_ids(), gated on fuel_card set, no company car, no mobility budget. FUEL_CARD_PRIV is also a Benefit in Kind (ONSS + withholding), like ATN.INT, but was missing the BIK category. Added it. Task 6469003 Forward-Port-Of: odoo/enterprise#129581 Forward-Port-Of: odoo/enterprise#128789
This fix makes Odoo properly clean up reusable page elements such as popups, carousels, tooltips, menus, and tabs when users leave or refresh parts of a page. It reduces browser memory growth during repeated page interactions and automated testing, improving stability without changing normal user workflows.
Original PR description
*: pos_self_order, website, portal, portal_rating, project, website_event, website_links, website_mass_mailing, website_slides, account_payment Several places create a Bootstrap 5 component…
*: pos_self_order, website, portal, portal_rating, project, website_event, website_links, website_mass_mailing, website_slides, account_payment Several places create a Bootstrap 5 component (Carousel, Modal, Dropdown, Tab, Popover, Tooltip, Collapse, Offcanvas) without ever calling .dispose() on teardown. Bootstrap keeps every instance in a page-global Map (not a WeakMap) keyed by its DOM element (see `elementMap` [1], so a missing dispose() permanently leaks the element. Three helpers centralize the fix and dedupe disposal per instance (safe even if several owners obtain the same instance, e.g. a popup surviving a builder re-render): getOrCreateBootstrapInstanceForInteraction and getOrCreateBootstrapInstanceForEditorPlugin get-or-create an instance and register its disposal on the owner's teardown, for Interaction and Plugin owners respectively; useBootstrapInstance is the Owl hook equivalent for Components. disposeBootstrapInstance disposes an instance right away (e.g. from a hidden.bs.modal handler) through the same dedup tracking, so it is never disposed twice. Mostly invisible in normal usage, but a severe leak in the JS unit test suite (thousands of mount/unmount cycles in one browser session): | Run | Scope | Memory before | Memory after | Percent | |-------------------------------|-------|--------------:|-------------:|--------:| | carousel_hook.js (16719 tests) | all | 934.5 MB | 363.2 MB | -61% | Runbot errors: - https://runbot.odoo.com/odoo/error/945770 - https://runbot.odoo.com/odoo/error/945772 [1]: https://github.com/odoo/odoo/blob/6d2fda1/addons/web/static/lib/bootstrap/js/dist/dom/data.js#L23
The search bar now uses the same rounded styling in both Community and Enterprise editions, reducing visual inconsistencies for users. This also makes dashboard search bars match the rest of the interface and simplifies future maintenance and testing.
Original PR description
*: spreadsheet_dashboard The round styling of the search bar was defined in web_enterprise, as xpaths over web.SearchBar / web.SearchBarMenu plus two scss files, so community and enterprise had two visibly different search bars. There is not much reason for them to differ, so the enterprise look becomes the only one and is written directly on the base templates. It also removes two annoyances: hoot tests often load web without web_enterprise, so the actual styling inside tests moves even further away from reality, and xpath attribute edition always adds complexity to the code so it ends up less readable and harder to customize without issues. The dashboard search bar, a standalone template the xpaths never reached, is a good example of that so this commit also adapts its template to the unified styling.
When an action fails, Odoo now keeps existing dialogs open instead of closing them during recovery. This prevents users from losing important pop-ups or missing error-related dialogs, improving reliability in the web interface.
Original PR description
Before this commit, clicking a notification of an inaccessible record in the messaging menu opened no dialog, and the nightly build failed on FAILED: [4/8] Tour access_inbox_records_tour -> Step…
Before this commit, clicking a notification of an inaccessible record in the messaging menu opened no dialog, and the nightly build failed on
FAILED: [4/8] Tour access_inbox_records_tour -> Step .o_dialog
.o-mail-Message-body:text(Message in inaccessible record).
Element has not been found.
This happens because the messaging menu shows the message in a dialog from the rejected promise of the action, while the action service restores the controller displayed before the failure, and `_updateUI` closes every dialog at the end of a restore. A dialog the user had opened before the failed click is closed the same way.
This only shows since "[IMP] web, project: enable test_documents_full eslint" *: the group check that commit moves to the end of a cog menu `isDisplayed` ran on every view, and its await delayed the mount of the controller, so the stack was still empty when the error arrived and the error path restored nothing.
This commit fixes the issue by keeping the dialogs open when the restore recovers from a controller error, as such a restore brings back the controller that was already displayed.
https://runbot.odoo.com/odoo/error/946461
* https://github.com/odoo/odoo/pull/284082Point of Sale now ignores barcode scanner input when staff are entering refund quantities on the ticket screen. This prevents accidental refund amounts or confusing maximum-quantity warnings caused by scanned product barcodes.
Original PR description
Steps to reproduce: - Have a paid order with a product ordered once - Open the ticket screen, select that order and its line - Scan a product barcode with a keyboard-wedge scanner Issue: "Maximum…
Steps to reproduce: - Have a paid order with a product ordered once - Open the ticket screen, select that order and its line - Scan a product barcode with a keyboard-wedge scanner Issue: "Maximum Exceeded - The requested quantity to be refunded is higher than the ordered quantity. 6 is requested while only 1 can be refunded." When the line holds enough quantity no dialog is shown at all and a refund quantity taken from the barcode is silently set. Cause: A keyboard-wedge scanner types the barcode as a burst of keystrokes. The number buffer discards such bursts by waiting barcodeService.maxTimeBetweenKeysInMs before handling the keys it collected and dropping any batch of more than two, but only when its holder asks for it with `useWithBarcode`. TicketScreen never set the flag, so its buffer handled every keystroke on its own and the digits of the barcode reached _setToRefundDetail as the refund quantity. ProductScreen, OrderSummary and PaymentScreen all set it. Fix: Set `useWithBarcode: true` on the ticket screen number buffer. Since the keys are now handled with a delay, capture the buffer before the selected order or orderline changes, so that a keystroke is applied to the line that was selected when it was typed and not to the next one. opw-6465148 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285083 Forward-Port-Of: odoo/odoo#281962
Shop search results and filter counts now use the same product information when matching customer searches. This prevents irrelevant matches from hidden webpage formatting, improving result accuracy and performance for larger catalogs.
Original PR description
The `/shop` product results and facets use different search fields. In particular, facets search raw `website_description` HTML, causing terms such as `weight` to match CSS like `font-weight` and process far more products than are displayed. Use one shared field list for both paths: - `name` - `variants_default_code` - `description_sale` - `description_ecommerce` Stop searching `default_code`, internal `description`, and raw `website_description`. opw-6391984 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283238 Forward-Port-Of: odoo/odoo#280720
Opening the timesheet systray from a task now pre-fills the relevant project and task without resetting any running timer. This prevents users from losing tracked time unexpectedly and keeps timesheet entries accurate.
Original PR description
Before this commit, the prefill used when the user opens the timesheet systray when his current loaded page is a task, will alter the form view in the timesheet systray to set the project and task when those fields are unset. The problem is `unit_amount` is also given to 0 and so the timer is reset due to the prefill system. This commit makes sure the `unit_amount` field is not inside the prefill data to make sure the timer is no longer reset. opw-6481443 Forward-Port-Of: odoo/enterprise#128672 Forward-Port-Of: odoo/enterprise#128531
Self-order customers with a zero total no longer get sent to an unnecessary payment page. This makes checkout smoother for free items, fully discounted orders, or other no-payment scenarios.
Original PR description
Before this commit: -------- - Self-orders with a total amount of zero are still redirected to the payment page, which was unnecessary. After this commit: -------- - The payment step is now skipped for zero-amount self-orders, providing a smoother checkout flow. task-5106938 Forward-Port-Of: odoo/odoo#284736 Forward-Port-Of: odoo/odoo#230218
This fix makes navigation breadcrumbs visible again on portal list pages across several apps, including sales, purchases, projects, timesheets, accounting, loyalty, subcontracting, and partner assignment. Customers and portal users can more easily understand where they are and navigate back to the portal home area.
Original PR description
*=account, hr_timesheet, loyalty, mrp_subcontracting, project, purchase, sale, website_crm_partner_assign Steps to reproduce: 1. install sale 2. Create and confirm a sale order for a portal user 3.…
*=account, hr_timesheet, loyalty, mrp_subcontracting, project, purchase, sale, website_crm_partner_assign Steps to reproduce: 1. install sale 2. Create and confirm a sale order for a portal user 3. Login as a portal user 4. Open sale orders Issue: - Breadcrumbs are not visible beside the title. Cause: - After this commit https://github.com/odoo/odoo/commit/bba2fc505f5d0b4770eacc6877155b1aeda6d772 Variables are passed as attributes directly on the element, but breadcrumbs_searchbar was passed to portal_layout, but the nested portal_searchbar no longer received it. As a result, portal list pages rendered their title instead of the breadcrumb home link. Solution: - Pass breadcrumbs_searchbar directly to portal_searchbar Alternative: - An alternative would be to propagate t-call parameters to slot content in QWeb or changes the condition for breadcrumbs visibility related enterprise pr: https://github.com/odoo/enterprise/pull/118352 opw-6232899 Forward-Port-Of: odoo/odoo#266020
This fixes a display issue where default contact or user avatars could be downloaded instead of shown on screen in some environments. The generated avatar image format is now recognized consistently, so initials-based avatars appear properly across contacts, users, chatter, and Discuss.
Original PR description
**Description of the issue/feature this PR addresses:** `avatar.mixin._avatar_generate_svg()` generates an auto-initial SVG avatar for any record without a real uploaded image (used by `res.partner`,…
**Description of the issue/feature this PR addresses:** `avatar.mixin._avatar_generate_svg()` generates an auto-initial SVG avatar for any record without a real uploaded image (used by `res.partner`, `res.users`, and anything else inheriting `avatar.mixin`). The generated SVG opens with a single-quoted XML declaration: `<?xml version='1.0' encoding='UTF-8' ?>`. When this content is served via `/web/image/...`, `guess_mimetype()` needs to determine its Content-Type since it's a computed value with no stored attachment metadata. On systems where the installed `libmagic` library classifies that specific single-quoted byte pattern as `text/xml` rather than `image/svg+xml`, the wrong Content-Type reaches the browser. **Current behavior before PR:** On affected `libmagic` versions/databases, an auto-generated avatar (a contact or user with no uploaded photo) gets served with `Content-Type: application/octet-stream` (or `text/xml`) instead of `image/svg+xml`. Browsers can't render that inline as an image, so instead of showing the colored-initial avatar, the browser downloads it as an unrecognized file. This affects any place these avatars are displayed: contact/user form and kanban views, chatter message authors, Discuss, etc. Confirmed reproducible with `libmagic` 538 (`python-magic`), where the single-quoted declaration is classified as `text/xml`, while the exact same content with double-quoted attributes is correctly classified as `image/svg+xml`. **Desired behavior after PR is merged:** `_avatar_generate_svg()` now generates its markup with double-quoted attributes throughout, which is correctly sniffed as `image/svg+xml` regardless of the installed `libmagic` version. Auto-generated avatars render inline in the browser as intended. No other code depends on the exact quoting of this generated SVG - `res_users.py` and `hr_employee.py` both only assign the returned bytes to an image field without inspecting their content. Avatar fields are computed and non-stored, so nothing needs to be migrated - every record gets the corrected markup on its very next read, with no backfill required. Updated the two existing `test_avatar_mixin.py` tests that asserted the exact (single-quoted) SVG string, and added `test_generated_partner_avatar_mimetype` to assert the generated avatar is actually sniffed as `image/svg+xml`, so this can't silently regress. Ran locally: all 6 tests in `TestAvatarMixin` pass. Forward-Port-Of: odoo/odoo#285081 Forward-Port-Of: odoo/odoo#283149
This fixes portal pages so customers can once again see breadcrumb navigation next to page titles. The change improves navigation clarity across affected customer-facing areas such as appointments, helpdesk, subscriptions, field service, equity, and signing.
Original PR description
*=appointment, equity, helpdesk, planning_field_service, sale_subscription, sign Steps to reproduce: 1. install sale 2. Create and confirm a sale order for a portal user 3. Login as a portal user 4. Open sale orders Issue: - Breadcrumbs are not visible beside the title. Cause: - After this commit https://github.com/odoo/odoo/commit/bba2fc505f5d0b4770eacc6877155b1aeda6d772 Variables are passed as attributes directly on the element, but breadcrumbs_searchbar was passed to portal_layout, but the nested portal_searchbar no longer received it. As a result, portal list pages rendered their title instead of the breadcrumb home link. Solution: - Pass breadcrumbs_searchbar directly to portal_searchbar Alternative: - An alternative would be to propagate t-call parameters to slot content in QWeb or changes the condition for breadcrumbs visibility releted community pr: https://github.com/odoo/odoo/pull/266020 opw-6232899 Forward-Port-Of: odoo/enterprise#118352
This fix preserves the recorded cost of a tracked product lot after all items from that lot have been sold or consumed. This helps keep historical inventory valuation information accurate instead of showing a misleading zero cost.
Original PR description
Issue before this commit: ========================= When a lot-valuation-enabled product uses FIFO costing and all quantities from a lot are consumed, the lot's standard price is reset to 0. Steps to…
Issue before this commit: ========================= When a lot-valuation-enabled product uses FIFO costing and all quantities from a lot are consumed, the lot's standard price is reset to 0. Steps to Reproduce: ========================= - Install the stock_account and purchase modules. - Create a lot-tracked product and enable Valuation by Lot/Serial, set FIFO as the costing method. - Create a PO for 5 units of the product with a unit price of 5. - Receive the products and check the lot's standard price; it is set to 5. - Sell all the product quantity for that product. - Open the lot form. Observation: The lot's standard price (cost) is reset to 0. Cause of the issue: ========================= In this [PR](https://github.com/odoo/odoo/pull/276163), the FIFO valuation logic computes the standard price using: - std_price = value / quantity if quantity else 0 When the lot quantity becomes 0, the quantity used for this computation is also 0. As a result, the standard price is reset to 0 even though the lot's historical cost is still relevant. With This Commit: ========================= Keep the lot's standard price when its quantity reaches 0, instead of resetting it to 0.
Payslip totals for worked days and hours now count only paid work entries, so periods such as days outside an active contract no longer inflate totals. This gives payroll teams more accurate figures while still keeping unpaid lines visible for transparency.
Original PR description
### Purpose Fix totals (`sum_worked_days`, `sum_worked_hours`) on payslips by excluding unpaid worked days (e.g., "Out of Contract"). By updating the compute method for those fields to take account for `is_paid` lines only. Added a unit test to ensure mid-month contract totals calculate correctly. ### How to reproduce - Go to Payroll and create a contract for an employee starting in the middle of the month (e.g., 15th of the current month). - Generate a payslip for that employee for the full month. - Check the total "Worked Days" and "Worked Hours" at the top of the payslip. Before this PR: <img width="1238" height="516" alt="image" src="https://github.com/user-attachments/assets/76cfdb29-a706-481c-b07f-03aabc548e84" /> After this PR: <img width="1238" height="516" alt="image" src="https://github.com/user-attachments/assets/ca3e0ecd-fac3-4f98-b0bc-203aabb89c4b" /> task-6205747
This change rolls back a recent GIF resizing update because it caused slow performance and memory errors when pages displayed multiple GIFs. It helps keep image-heavy views, such as Kanban screens, responsive and reliable for users.
Original PR description
Revert commit d9fae40571c4f10c17fe00efc087cb25b30b85ab as it's slow on odoo.com and the call to `frame.copy()` is raising a MemoryError when loading a KanbanView with multiple gifs. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284884 Forward-Port-Of: odoo/odoo#284479
Point of Sale loyalty rewards now correctly grant a free product from a tagged product group when the cashier changes the quantity using the numpad or barcode quantities. This prevents missed promotions and ensures Buy 2 Take 1 offers behave consistently regardless of how items are entered.
Original PR description
Steps to reproduce: - create a "Buy 2 Take 1" program whose rule and reward both target a product tag containing several products - in the PoS, add one of the tagged products to the order - set its…
Steps to reproduce: - create a "Buy 2 Take 1" program whose rule and reward both target a product tag containing several products - in the PoS, add one of the tagged products to the order - set its quantity to 3 with the numpad Issue: The free product is not given. Clicking the product a third time instead of typing the quantity does give it, and so does a program whose reward is a single product. Cause: A reward whose products come from a tag is multi_product, so getClaimableRewards never computes its unclaimed quantity and the auto claim of updateRewards skips it: which product to give out is unknown. The only place claiming such a reward is addLineToCurrentOrder, which resolves it with the product that was just added. Setting a quantity does not go through it, hence the difference. Fix: Resolve the reward product the same way when auto claiming: the product of the line being worked on, or, failing that, the only reward product the order contains. The reward then follows the quantity of any of the tagged products, not only of the first one added, and the cashier is still asked when the order does not settle the choice. opw-6430385 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284777
Planning now calculates allocated hours correctly when multiple employees share the same shift and working schedule. This helps managers see accurate daily workload totals in the Gantt view, reducing scheduling mistakes.
Original PR description
## Steps to reproduce: - Install Planning and Employee - Create a working schedule for example 38h/week (8h,8h,8h,8h,6h) - Create two employees and assign the created working schedule to them -…
## Steps to reproduce: - Install Planning and Employee - Create a working schedule for example 38h/week (8h,8h,8h,8h,6h) - Create two employees and assign the created working schedule to them - Create a shift in planning for the two employees for a week - Notice in gantt view the total allocated hours for each day are not calculated correctly ## Cause: When calculating each cell's duration we fetch the resources' intervals while doing this here https://github.com/odoo/enterprise/blob/1851d3046682020686f517dec3c744d88a38b049/planning/static/src/views/planning_gantt/planning_gantt_renderer.js#L354-L359 we loop over the first interval and instead of pushing to the intervals we overwrite on the whole list with the interval we fetched so when it comes to the next interval it won't find any intersection so it will set the resourceIntervals to an empty array. So here https://github.com/odoo/enterprise/blob/1851d3046682020686f517dec3c744d88a38b049/planning/static/src/views/planning_gantt/planning_gantt_renderer.js#L231-L234 it will mess up the percentage calculation which will lead to wrong allocated hours numbers. ## Fix: Instead of overwriting on resourceIntervals we push to it the intervals returned to keep the intervals for each resource. opw-6316368 Forward-Port-Of: odoo/enterprise#123750
Corrects several SAF-T/FAIA report issues affecting Luxembourg audit exports, including negative tax amounts, software version length, foreign currency tax values, and invoice customer/supplier details. These fixes help businesses produce compliant audit files and reduce validation errors during tax reporting.
Original PR description
This is one of many errors fixing the FAIA report, which is the SAF-T report for Luxembourg. See PR #113316 for a list of similar PRs. ### Error 1: Negative tax amounts Auditors from Luxemborg…
This is one of many errors fixing the FAIA report, which is the SAF-T report for Luxembourg. See PR #113316 for a list of similar PRs. ### Error 1: Negative tax amounts Auditors from Luxemborg provided one Odoo user with analysis files of their FAIA xml report. The following discrepancy was present in more than 300 lines: `[TaxInformation/TaxAmount/Amount] # is negative. Only postive values are admitted. The sign is automatically determined by the corresponding CreditAmount (-) Or DebitAmount (+) on the same Line.` This discrepancy was caused by two different scenarios. The first was a negative `unit_price` line, such as a Discount product. The second was a tax with negative and positive repartition lines, such as a tax with xml ID `lu_2015_tax_AP-EC-17`. Luxembourg officials confirmed the following behavior: 1. The TaxInformation/TaxAmount/Amount element must be positive. 2. The TaxInformationTotals/TaxAmount/Amount element may be negative. 3. There may only be one TaxInformationTotals element per TaxCode in an Invoice element. This commit ensures that these conditions are met for the FAIA report. I'm not sure if the TaxInformation changes should also be applied to the base `account_saft saft_report.xml` file. ### Error 2: SoftwareVersion The SoftwareVersion element is limited to 18 characters. The relevant error from a customer's analysis file is below. Error: Value exceeds maxLength of "18". ### Error 3: CurrencyAmount The `account_saft` method `GeneralLedgerCustomHandler._saft_fill_report_tax_details_values()` does not report the amount of tax in foreign currency, instead replacing this value with the amount in company currency. No errors prompted this change; it just seems wrong on its face. ### Error 4: PR #113720 ensured that the TaxType element is always TVA. This means that the TaxType should no longer should be ignored in our example documents. ### Error 5: Schema validation failure The elements Inovice/CustomerInfo and Invoice/SupplierInfo are defined with the element `<xs:choice>` in the XSD file linked below. Only one can be present at any time, not both. https://pfi.public.lu/dam-assets/backup/FAIA/FAIA/XSD_Files.zip. note: currently the link is broken. PR #100749 allowed many parts of SAF-T code to display both customer and supplier data, including these elements. This commit ensures that the elements are mutually exclusive. opw-6344914 [Link](https://www.odoo.com/odoo/project.task/6344914) Forward-Port-Of: odoo/enterprise#129043 Forward-Port-Of: odoo/enterprise#126121
Manufacturing orders in warehouses using a 3-step production flow are now counted correctly in stock forecasts. This helps replenishment teams see incoming finished goods accurately and avoid unnecessary purchasing or production decisions.
Original PR description
### Steps to reproduce: - In the settings enable Multi-Steps Routes - Put your warehouse in manufacture in 3 steps - Create a storable product P - Create and confirm an MO for 1 unit of P - Go to…
### Steps to reproduce: - In the settings enable Multi-Steps Routes - Put your warehouse in manufacture in 3 steps - Create a storable product P - Create and confirm an MO for 1 unit of P - Go to Inventory > Operations > Procurement > Replenishment - Create a new one for P in WH/stock #### > The forecasted quantity in stock is still 0 but should be at 1 ### Cause of the issue: This is the exact use case already fixed in 85dd3369ed17b98b2ce485be04f140cf4cfa8aa3, which stamped the finished move with a `location_final_id` pointing at WH/Stock so that the move contributes to the forecast there even though its `location_dest_id` is the intermediate WH/Post-Production. That fix was reverted in practice by 42275f83dc5350822a625e19d65148e8b41ab1d4, which replaced the value with `mo.location_dest_id`: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_move.py#L466-L467 Its reasoning was that in a single-warehouse setup `location_dest_id` equals the warehouse stock location, so the behaviour would be unchanged. That holds in 1 and 2 steps, where the extra step is on the component side and only moves `default_location_src_id` to the pre-production location. It breaks in 3 steps, the only mode that also moves `default_location_dest_id`, to the post-production location: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L246-L247 and `_compute_locations` propagates it to the MO: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/mrp_production.py#L334-L341 WH/Post-Production is a sibling of WH/Stock under the warehouse view location, not a child of it. Since `location_final_id` takes precedence over `location_dest_id` for the non-done part of the move chain: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L331-L334 the finished move stopped being counted in the WH/Stock forecast. Why the test did not catch it: `test_3_steps_manufacturing_forecast` stayed green through the whole regression, because it scoped `virtual_available` with a `location_id` context key. `_get_domain_locations` only reads `location` and `warehouse_id`; `location_id` is silently ignored: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L284-L287 The call therefore fell through to the branch scoping the forecast to every warehouse view location: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L303-L309 and the warehouse view location is the common parent of both WH/Stock and WH/Post-Production. The assertion held regardless of where `location_final_id` pointed, so the test was a false positive from the start: it also passes with 85dd3369ed17b98b2ce485be04f140cf4cfa8aa3 fully reverted. Using the `location` key makes it fail without the fix and pass with it. ### Fix: Neither fix proposition was right on its own; each one was correct only in its own scenario. The`mo.warehouse_id.lot_stock_id` resolves the warehouse from the components, so it points at the wrong warehouse as soon as the finished product is produced for another one. `mo.location_dest_id` is the post-production location as soon as the warehouse manufactures in 3 steps, so it drops the quantity from the forecast of the manufacturing warehouse itself. What separates the two is not the warehouse but whether the destination is a transit step. In 3 steps the finished product only reaches the stock through the post-production push rule: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L57 so the final location is the stock of the warehouse owning that destination. Any other destination is already final and is kept as is, which leaves cross-warehouse MOs and destinations set to a sub-location of the stock untouched. The rule's destination is read rather than `warehouse.lot_stock_id` because a push move takes its destination from the rule and not from the operation type: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/stock_rule.py#L256-L260 so the forecast stays correct when the store step is reconfigured to land somewhere else than the warehouse stock. The rule is looked up on `pbm_route_id` by its `picking_type_id` instead of through `warehouse.sam_rule_id`, because that field is no longer set. It used to be an entry of `_generate_global_route_rules_values`, and it is that entry which made the generic warehouse machinery create the rule and store it back on the warehouse: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/stock_warehouse.py#L403-L410 11e69870db1c49d9a6af79ffd263e4e162b34b6b removed it when the post-production step stopped being a pull rule on the Manufacture route and became a push rule generated from `get_rules_dict`. Only the field declaration was left behind, and nothing writes it any more: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L21-L22 so reading it would silently give an empty recordset. The lookup is not delegated to `_get_push_rule` to avoid a search per finished move. opw-4882390 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285185 Forward-Port-Of: odoo/odoo#283234
This fix ensures user group checks work correctly when the same group is referenced by more than one XML identifier. It prevents access or permission checks from returning inconsistent results depending on which valid group identifier is used.
Original PR description
A group can be identified by multiple xmlids. We add support to provide a list of "refs" to the `SetDefintions` object.
Reproductible issue:
```
demo = self.env["res.users"].browse(5)
demo.has_group("accountant.group_account_user") # False
demo.has_group("account.group_account_user") # True
assert self.env.ref("accountant.group_account_user") == self.env.ref("account.group_account_user")
```
task-6471260
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#285153
Forward-Port-Of: odoo/odoo#284866Changing a project’s visibility no longer fails when the project’s document folder includes shortcuts. This ensures access settings can be updated as expected while still preventing direct access changes on shortcut-only documents.
Original PR description
Changing a project's visibility fails when its documents folder contains a shortcut. The visibility change is never applied and the following error is raised: "You can not update the access of a…
Changing a project's visibility fails when its documents folder contains a shortcut. The visibility change is never applied and the following error is raised: "You can not update the access of a shortcut, update its target instead." ### Reproduction steps - Create a project and add a document to its folder. - Create another document outside the project's folder. - Create a shortcut to that document in the project's folder. - Change the project's visibility. ### Cause Changing a project's visibility updates the access rights of its folder and documents together. The shortcut access check is meant to reject operations performed only on shortcuts. However, reading `shortcut_document_id` on a recordset returns the shortcut targets found across that recordset. Therefore, the presence of a single shortcut makes the check reject the whole operation. This prevents regular documents and the project folder from having their access updated. ### Fix Only reject access updates when all records involved are shortcuts. This preserves the protection against changing shortcut access directly while allowing project access updates to include shortcuts alongside regular documents and folders. opw-6472637 Forward-Port-Of: odoo/enterprise#129588 Forward-Port-Of: odoo/enterprise#128756
Inventory users can now validate dropship transfers for products using average costing when landed costs are enabled. This prevents an access error that blocked normal sales and purchasing workflows for affected products.
Original PR description
# How to reproduce - Activate the stock_landed_costs module - Enable Dropshipping - Create a product with : - Category : - Costing Method : AVCO - Inventory Valuation : Perpetual - Routes : Dropship…
# How to reproduce - Activate the stock_landed_costs module - Enable Dropshipping - Create a product with : - Category : - Costing Method : AVCO - Inventory Valuation : Perpetual - Routes : Dropship - Atleast one vendor - Create a SO for that product - Confirm the SO & then Confirm the associated PO - Login as an user with "User" rights for Inventory - Try to validate the Dropship transfer # The issue You get an access error. If the same flow is done with a product with a Standard Price costing method, then the Dropship is properly validated # Cause When validating the Dropship, we'll call `_action_done` on the moves. This will trigger an update of the standard price of the product : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/stock_move.py#L177 https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/stock_move.py#L345-L349 Since we're in avco, this will run the `_run_average_batch` method : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/product.py#L675 That will fetch the value of each moves. For the Dropship moves, it'll do so by calling the `_get_value()` method : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/product.py#L486 This method will compute the value of the move, notably by using the associated landed costs : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/stock_move.py#L431 https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_landed_costs/models/stock_move.py#L14 Now the issue is that this computation calls `_read_group` on 'stock.valuation.adjustment.lines' that are retricted to inventory administrators : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_landed_costs/models/stock_move.py#L11 https://github.com/odoo/odoo/blob/5f6fb63d5d7585805642c702d096b2f882e73761/addons/stock_landed_costs/security/ir.model.access.csv#L4 # Proposed solution Get the value of the move in sudo like previously done in the flow : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/stock_move.py#L314 opw-6323645 Forward-Port-Of: odoo/odoo#284251 Forward-Port-Of: odoo/odoo#273102
This fixes an issue where retrying a rejected simplified Saudi e-invoice could fail because the QR code was missing. The system now recreates the QR code from the invoice submission data during retry, helping businesses resubmit rejected invoices without errors while keeping normal QR display rules unchanged.
Original PR description
- When a simplified (B2C) invoice is rejected by ZATCA and subsequently retried, its state remains rejected. The QR code computation therefore returns an empty value for the rejected invoice, as the computation is primarily intended for the post-EDI state. During the retry, this results in a traceback when the QR code is applied to the XML. - Regenerate the QR code directly from the submission data when preparing a new B2C XML, instead of relying on the state-dependent QR code field. This preserves the existing QR visibility rules for the final invoice. task-6485555 Forward-Port-Of: odoo/odoo#284733 Forward-Port-Of: odoo/odoo#283534
Resumed shopping carts now recalculate the selected delivery cost after product prices are refreshed at checkout. This prevents customers from incorrectly keeping free shipping when an updated order total no longer qualifies, while preserving selected pickup locations for click-and-collect orders.
Original PR description
Steps to reproduce ================== 1. Configure a delivery method with free shipping above a threshold 2. Add a product to the cart above that threshold, select the delivery method and leave the…
Steps to reproduce ================== 1. Configure a delivery method with free shipping above a threshold 2. Add a product to the cart above that threshold, select the delivery method and leave the cart unfinished 3. Lower the product price below the threshold 4. Recover the cart and confirm the order from /shop/checkout => The product prices are refreshed, but shipping stays free although the new total is below the threshold. Root cause ========== Since [1], nothing re-rates the carrier after /shop/confirm_order refreshes the cart prices: the delivery method is selected before the confirmation. In 17.0, the payment page auto-clicked the selected carrier on load, which re-rated the shipping cost and masked the issue. Fix === Re-rate the selected delivery method in `shop_confirm_order` after the prices have been recomputed, as `_cart_update` already does. [1]: https://github.com/odoo/odoo/commit/8e2b6cede55b51f7ccdbe7601aa7e6035fd6f9fe opw-6383849 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284606 Forward-Port-Of: odoo/odoo#276905
Fixed an issue that could prevent the Assets list from opening when older or inconsistent analytic distribution data referenced deleted accounts. Users can now access the list view without getting stuck in an error loop, while the invalid underlying data remains unchanged for later correction.
Original PR description
Issue - If there are any account.asset records with analytic distributions with accounts that do not exist, it causes a recursive traceback when opening the list view of the `account.asset` model. The issue stems from `jsonToData` attempting to save the distributions json via the `save` call, where one (or multiple) accounts are non existent, which in turn runs `jsonToData` after refetching via the `load` call - overwriting `record.data` with the original, still-corrupt JSON. This creates a loop with no exit condition. Solution - In the Assets list every row is readonly, so `save()` is never reached, so `root.load()` never fires, so there is no reload to re-read the corrupt JSON. Makes the list accessible, even though the JSON values for the `analytic_distribution` are invalid. opw-6500446 Forward-Port-Of: odoo/odoo#285527 Forward-Port-Of: odoo/odoo#285381
This fix prevents custom website buttons from losing their gradient background when a user changes the button text color. It helps website editors keep the intended button design without needing to reapply styling.
Original PR description
Steps to Reproduce : 1. Go to Website → Edit Mode 2. Add a snippet with button 3. Click on button and change its type to : " Custom" 4. Apply the gradient type color in fill color option 5. Apply any…
Steps to Reproduce : 1. Go to Website → Edit Mode 2. Add a snippet with button 3. Click on button and change its type to : " Custom" 4. Apply the gradient type color in fill color option 5. Apply any color in text color option 6. You will notice that the gradient type color in fill color option is removed. Problem: Since [this commit][1] new button style options have been added to the sidebar. If one changes the style of a button to custom, changes the background to gradient, and tries to change the text color, the background gradient is removed. Cause: Whenever a gradient is added either to text or as a background, it is applied as a background image. In the case of a text gradient, an additional class, `text-gradient`, is applied for correct styling. Once a change to either color or background/fill is applied that is not a gradient color change, the background image of the element would be reset to nothing. This is the result of [this line][2] from a [previous commit][3]. In the case of a button, this meant changing the font color would reset the background. That is not the desired outcome. The flaw was only discovered once new options were added to change non-text/font elements' backgrounds to gradient. Solution: An additional check has been added to see if the element being edited is text. If so, and it's a gradient style being changed, we remove the background image. Otherwise it is kept so that the button case from above is resolved. [1]: https://github.com/odoo/odoo/commit/2bf1db001b195480b963b338583c170aa1c009a3 [2]: https://github.com/odoo/odoo/blob/8e0845712f462ecfafd2176406dcbafc869a5564/addons/html_editor/static/src/main/font/color_plugin.js#L575 [3]: https://github.com/odoo/odoo/commit/8e0845712f462ecfafd2176406dcbafc869a5564 task-6247134 Forward-Port-Of: odoo/odoo#285063 Forward-Port-Of: odoo/odoo#279070
This update prevents invoice printing from failing when an Argentine company's partner record contains a VAT number that is valid in another country but not a valid Argentine CUIT. It improves reliability for Argentine localization users by allowing invoices to print instead of showing an error.
Original PR description
When printing an invoice, a traceback will occur if the company's partner has an invalid CUIT. Steps to reproduce the error: - Install ``l10n_ar_edi`` module with demo data - Switch to ``(AR)…
When printing an invoice, a traceback will occur if the company's partner has an invalid CUIT. Steps to reproduce the error: - Install ``l10n_ar_edi`` module with demo data - Switch to ``(AR) Exento`` Company - Go to Invoicing > Configuration > Journals > Open ``Ventas Preimpreso`` journal > ARCA POS System: ``Electronic Invoice - Web Service`` > Save - Create a new invoice with ``ADHOC SA`` partner > Confirm the invoice - Open the ``(AR) Exento`` partner and set the VAT to ``BE0477472701`` - Open the Invoice > print Traceback: ```py ValueError: invalid literal for int() with base 10: 'BE0477472701' ``` After this [commit], companies outside the EU can use European VAT numbers. Consequently, an Argentine partner can have a CUIT number such as ``BE0477472701``, which is valid as a Belgian VAT number but not as a CUIT. When printing the invoice, the ``l10n_ar_vat`` field is computed from the partner's VAT and its value is passed to ``int()``, causing a traceback at the following line: https://github.com/odoo/enterprise/blob/d55486866d09f8aa87c2003dab722cfa323068b4/l10n_ar_edi/models/account_move.py#L138 [commit]: https://github.com/odoo/odoo/commit/a2afe3292e1cd0a4f339dc47707e469653d13ea0 Enterprise PR: https://github.com/odoo/enterprise/pull/127820 sentry-7666042143 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284880 Forward-Port-Of: odoo/odoo#282454
Invoice printing in Argentina localization now avoids crashing when a company partner has an invalid CUIT tax ID. This helps users complete invoice printing more reliably and prevents an unexpected error message in a common accounting workflow.
Original PR description
When printing an invoice, a traceback will occur if the company's partner has an invalid CUIT. Steps to reproduce the error: - Install ``l10n_ar_edi`` module with demo data - Switch to ``(AR)…
When printing an invoice, a traceback will occur if the company's partner has an invalid CUIT. Steps to reproduce the error: - Install ``l10n_ar_edi`` module with demo data - Switch to ``(AR) Exento`` Company - Go to Invoicing > Configuration > Journals > Open ``Ventas Preimpreso`` journal > ARCA POS System: ``Electronic Invoice - Web Service`` > Save - Create a new invoice with ``ADHOC SA`` partner > Confirm the invoice - Open the ``(AR) Exento`` partner and set the VAT to ``BE0477472701`` - Open the Invoice > print Traceback: ```py ValueError: invalid literal for int() with base 10: 'BE0477472701' ``` The issue occurs because when the partner's identification type is CUIT, At [1] ``_run_check_identification()`` method does not include partners whose identification type has ``is_vat=True``. As a result, CUIT is not validated by ``_run_check_identification()`` method in ``l10n_ar`` module at [2]. So, partner's ``l10n_ar_vat`` field can be computed as ``BE0477472701``. Passing this value to ``int()`` raises the traceback during invoice printing at below line. https://github.com/odoo/enterprise/blob/d55486866d09f8aa87c2003dab722cfa323068b4/l10n_ar_edi/models/account_move.py#L138 [1]:https://github.com/odoo/odoo/blob/c2a39085ba0fbcf8a0e6a55228191e764499caea/addons/l10n_latam_base/models/res_partner.py#L24-L30 [2]:https://github.com/odoo/odoo/blob/c2a39085ba0fbcf8a0e6a55228191e764499caea/addons/l10n_ar/models/res_partner.py#L55-L65 Community PR: https://github.com/odoo/odoo/pull/282454 sentry-7666042143 Forward-Port-Of: odoo/enterprise#129449 Forward-Port-Of: odoo/enterprise#127820
Point of Sale preparation tickets now reprint correctly when an order includes combo products. This prevents silent printing failures in kitchen or preparation workflows, helping staff recover tickets reliably when needed.
Original PR description
Steps to reproduce: --- - Configure a Point of Sale with a preparation printer whose product categories contain the products of a combo. - Add the combo to an order and send it to preparation. - Try to reprint the order. Issue: --- - Nothing is printed for orders containing a combo, without any error being shown. Fix: --- - Store the combo children of a preparation change as plain ids and match the printer categories on those ids. task-6472281 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282663
Fixed an issue where commission plan calculations could fail when the same salesperson had multiple assignment rows in an overlapping plan. The system now checks each assignment separately, preventing crashes and ensuring overlapping plans are detected correctly.
Original PR description
Version: 19.4 Steps to reproduce: - Open the commission plans list with demo data - Open a plan whose salesperson has duplicate assignment rows in an overlapping plan Issue: When calculating other_plans, the code assumed each salesperson had only one assignment in an overlapping plan. It used filtered() and directly accessed date_from .If the salesperson was assigned multiple times in the same plan, filtered() returned multiple records, which caused the error and crashed the calculation. Fix: Loop over all matching rows one at a time. If any row's date range overlaps with the current assignment, flag the plan and stop. This avoids the multi record access while correctly detecting all overlap cases. Task ID: 6302927 Forward-Port-Of: odoo/enterprise#120576
Fixed an issue where regular timesheet users could lose access to Assistant Rules when Billing Rate Indicators were turned off. The Timesheets configuration menu now stays visible for users who need assistant-related settings, reducing confusion and avoiding the need for debug mode.
Original PR description
Steps to reproduce --- 1. Install sale_timesheet_enterprise. 2. Go to Timesheets > Configuration > Settings (as Admin). 3. Disable the "Billing Rate Indicators" setting and enable timesheet…
Steps to reproduce --- 1. Install sale_timesheet_enterprise. 2. Go to Timesheets > Configuration > Settings (as Admin). 3. Disable the "Billing Rate Indicators" setting and enable timesheet assistant. 4. Log in as a normal user with "Timesheets > Own timesheet" access. 5. Open the Timesheets app. Issue --- The "Configuration" menu is completely hidden. The regular user cannot access the "Assistant Rules" menu unless they turn on debug mode. Cause --- When the billing rates feature is turned off, the _load_menus_blacklist function hides the Enterprise Configuration menu. The logic used an and condition, meaning the menu was only kept visible if the user was an Assistant AND had Manager/Admin rights. This locked out standard users. Fix --- Change the blacklist condition. Allow the Enterprise Configuration menu to stay visible if the user needs it for the Assistant feature (when UoM is not Days), OR if the user is a Manager/Admin. References to check other issues https://github.com/odoo/enterprise/pull/120609 https://github.com/odoo/enterprise/pull/117984 task - 6470183 Forward-Port-Of: odoo/enterprise#128727 Forward-Port-Of: odoo/enterprise#128096
This fix prevents a shopper's personal contact name from being replaced by their company name when they enter a valid VAT number during checkout. It keeps customer and company records correctly separated, improving order accuracy and customer data quality.
Original PR description
Steps to produce: --- - Install website_sale module without demo. - Create a new product and publish it. - From the incognito, go to shop and add that product to cart. - While doing checkout > fill…
Steps to produce: --- - Install website_sale module without demo. - Create a new product and publish it. - From the incognito, go to shop and add that product to cart. - While doing checkout > fill the address form. - Add the all details including the company name and valid VAT. (ex. BE0477472701) - Submit the form. Issue: --- - When a public or guest user submits a new address with both a contact name and a company name along with a valid VAT number, the contact's name was being overwritten with the company name. Root cause: --- - The _compute_is_company heuristic in res.partner automatically sets is_company=True for partners with valid VAT. In [commit] `_create_or_update_address`, the condition `elif partner_sudo.is_company:` was designed to rename existing companies, but incorrectly caught newly created individuals marked as companies due to VAT, causing their name to be overwritten instead of creating a parent company. Solution: --- - Replaced the is_company check with explicit conditions: the partner has no parent_id, has contact-type child_ids. - This ensures the rename-company branch only fires for actual company heads that the logged-in user belongs to, not for individuals that happen to have is_company=True due to a valid VAT. [commit]: https://github.com/odoo/odoo/commit/8f3a32170c842f5f42edc79bf8620276f0da7be4 opw-6356772 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285495 Forward-Port-Of: odoo/odoo#275044
This fixes activity filters so "today" is based on the user's local calendar date instead of UTC. Users in time zones ahead of UTC will no longer see activities due today incorrectly listed as future activities for part of the day.
Original PR description
#### Description of the issue: Activity filters using context_today() bucket against the UTC date instead of the user's local date, off by one for part of the day. Partial revert of #265250 (e048bb5), scoped to PyDate: UTC getters are right for PyDateTime, wrong for a calendar day. #### Current behavior before PR: A Perth (UTC+8) user finds an activity due today under "Future Activities" from 00:00 to 08:00 local, while the chatter labels the same activity "Today". #### Desired behavior after PR is merged: context_today(), today and current_date return the user's local calendar day, so filters agree with the chatter. PyDateTime and PyTime keep the UTC getters; now and time.strftime() are unchanged. opw-6415985 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281453 Forward-Port-Of: odoo/odoo#278761