Daily updates from Odoo
Navigate
Branch
Monday, August 17, 2026
142 changes
13 changes
Security fixes and vulnerability patches
Odoo Sign now prevents users from linking signature requests to business records they are not allowed to view. This closes a data exposure risk where changing the linked record could reveal restricted information during signing.
Original PR description
Version: saas-18.3 Reported issue: 1. Marc Demo creates a sign request from a template whose fields are automatically populated from the linked record. 2. It is sent to himself. He does not have…
Version: saas-18.3 Reported issue: 1. Marc Demo creates a sign request from a template whose fields are automatically populated from the linked record. 2. It is sent to himself. He does not have access to all records of a referenced model (e.g. Sales Orders). 3. The "Linked To" (reference_doc) field is edited afterwards to point to a record the signer does not have access to. By the time it's signed, the value of that record becomes visible - so a user can, simply by changing the linked record, see the value of a record they were never authorized to access. Even a Sign Manager could link a request to a record they have no access to and later see its value through it. Issue: `reference_doc` could be set or changed to any record of any allowed model with no validation that the acting user actually has access to it. In the interface, you can only create a signature request from a record you can see, but editing `reference_doc` manually (via write(), RPC, etc.) was not held to the same rule, making it an easy way to leak information about records outside your normal access. Cause: The only restriction was cosmetic, enforced client-side by the record picker widget filtering its search results. Nothing on the server validated the value being written to `reference_doc`. Fix: `write()` now checks that the acting user has read access to the target record before allowing `reference_doc` to be set, raising a ValidationError otherwise, bringing manual edits in line with what the interface already enforces when creating a request. Forward-Port-Of: odoo/enterprise#127870 Forward-Port-Of: odoo/enterprise#127715
Enhancements to existing features
The Belgian payroll meal voucher report now calculates total voucher value correctly when vouchers are postponed. This helps payroll teams produce more accurate reports and avoids incorrect meal voucher amounts for affected employees.
Original PR description
-Adjust the total value for meal voucher report in case of postponed meal vouchers. Forward-Port-Of: odoo/enterprise#127941
Resolved issues and error corrections
Fixed an error that prevented users from exporting the Deferred Revenue Report to Excel when report lines included annotations. This ensures accounting teams can keep and share annotated deferred revenue data without hitting a server error.
Original PR description
**Steps to reproduce:** * Install the **Accounting** module. * Unhide the **Start Date** and **End Date** fields on invoice lines. * Create and post a customer invoice with deferred dates. * Go to…
**Steps to reproduce:** * Install the **Accounting** module. * Unhide the **Start Date** and **End Date** fields on invoice lines. * Create and post a customer invoice with deferred dates. * Go to **Accounting → Reports → Deferred Revenue Report**. * Add an annotation to a deferred revenue line by clicking the **annotate** from three dots next to the account. * Export the report in **XLSX** format. **Observed behavior:** * The export fails with a server error: `UnboundLocalError: cannot access local variable 'annotations_x_offset' where it is not associated with a value` **Cause:** * The variable `annotations_x_offset` is assigned inside the `for header_level_index, header_level in enumerate(options['column_headers'])` loop, which writes the "Annotations" column header for each header level. * The Deferred Revenue Report produces an empty `column_headers` list, so the loop body never executes and `annotations_x_offset` is never assigned. * When the code later tries to write annotation data for each report line, it references the unassigned variable, causing Python to raise `UnboundLocalError`. **Fix:** * Introduce a boolean flag `annotations_header_written = False` before the header loop to explicitly track whether the "Annotations" column header has already been written. * Inside the header loop, set `annotations_header_written = True` after writing the header. * After writing all individual column headers (where `x_offset` already points to the first free column after all data columns), add a fallback: if `report_annotations` is set but `annotations_header_written` is still `False`, assign `annotations_x_offset` from the current `x_offset` and write the "Annotations" header. opw-6354473 Forward-Port-Of: odoo/enterprise#127840 Forward-Port-Of: odoo/enterprise#122768
Invoices in Peru with missing tax information now produce a clear validation error during batch sending instead of causing a background processing failure. This prevents one problematic invoice from blocking other invoices from being submitted to SUNAT.
Original PR description
In l10n_pe_edi, invoices containing lines without tax can't be submitted to SUNAT.
When sending a single invoice, an error message is displayed. However, sending multiple invoices processes them in the background by a cron job. In this case, EDI document creation fails without error handling, raising a generic parsing error and blocking the cron from processing other invoices.
Steps to reproduce:
1. Create and post two invoices with no tax on some lines.
2. From the list view, select both invoices and click "Send" and mark "SUNAT".
3. An exception is raised: `ValueError: XMLSyntaxError("Start tag expected, '<' not found, line 1, column 1")`.
opw-6390480
Forward-Port-Of: odoo/enterprise#127947
Forward-Port-Of: odoo/enterprise#125143The planning field service onboarding tour now matches recent interface changes, helping users complete guided setup without getting stuck. The complete action is also shown in the right place depending on whether users are working in Kanban or Gantt views, reducing confusion during field service scheduling.
Original PR description
## [FIX] planning_field_service: display complete button in popover footer Before this commit, the complete button is displayed in the card even in the gantt popover instead of displaying it in the…
## [FIX] planning_field_service: display complete button in popover footer Before this commit, the complete button is displayed in the card even in the gantt popover instead of displaying it in the footer of the gantt popover. This commit makes sure the complete button in the card is only displayed in the kanban view and that button is displayed in the footer of the gantt view. ## [FIX] planning_field_service: adapt onboarding tour based on recent changes Before this commit, the quick create on resource_ids field in planning.slot has been replaced by a form view inside a modal. The Sign in button in gantt/calendar popover no longer automatically redirects the user to the form view of the intervention and so the user cannot directly complete the shift. This commit adapts the onboarding tour based on the recent changes. It also forces a reload in the gantt view when the user signs in a intervention via the Sign in button in the gantt popover. runbot-error-941063 task-[6353582](https://www.odoo.com/odoo/project/4105/tasks/6353582)
This fixes a payroll pay run automated test that was failing because the screen adds empty placeholder rows. The test now checks only rows containing payroll data, making payroll validation more reliable without changing user-facing behavior.
Original PR description
Issue: The original trigger was searching for 2 table rows, when it enforces 4 with added empty rows. The [getEmptyRowIds](https://github.com/odoo/odoo/blob/33dc65bbac165f33030ad3da59ea785b69482b3f/addons/web/static/src/views/list/list_renderer.js#L1104-L1110) enforces max of 4 rows. The condtional (one up the stack) !ctx["this"].props.list.isGrouped&&!ctx["this"].props.noContentHelp returns true, and it adds empty rows. Fix: Since this enforces 4 rows with empty rows we check the rows that have data instead of how many rows are added. Because anything less than or equal to 4 but greater than 0 records it will always be 4 table rows while the conditional above returns true . opw-6349513 <img width="1337" height="674" alt="Screenshot 2026-07-15 at 4 53 51 PM" src="https://github.com/user-attachments/assets/76f53521-ec9f-4807-9083-95533904b5de" /> Forward-Port-Of: odoo/enterprise#125091
The Documents app now handles the Info & Tags panel more reliably on mobile, especially after reloading, switching views, previewing files, or clearing a selection. This prevents users from seeing an enabled button that opens a hidden or inaccessible panel, reducing confusion when managing documents on phones.
Original PR description
**Steps to reproduce:** - Go to Documents app in mobile - Go to the kanban view - Add some files and select one - Click on `Info & Tags` button in the control panel - Reload the page - Chatter is not…
**Steps to reproduce:** - Go to Documents app in mobile - Go to the kanban view - Add some files and select one - Click on `Info & Tags` button in the control panel - Reload the page - Chatter is not displayed but the button is still enabled - Switching to the list view properly shows it **Issue:** Original fix (see [1]) was not enough for every case. Additional issues: - Chatter hidden on init even when its panel has `visible = true` - State desynchronized with the view when switching menu type (kanban/list) or by previewing a document and coming back - When using the button with an open preview, chatter shows up in the background but is not accessible (and going back discards it) - Removing selection with an open chatter disable the related action **Fix:** - Disable the chatter on mobile init by default to avoid having to manually move it back - Reset chatter on selection removal to avoid getting stuck in the menu - Reset chatter on view switch to avoid being in the wrong state afterwards (and revert the previous css changes) Not a great fix (quite mobile-specific) and there might still be some edge cases. [1] original fix: https://github.com/odoo/enterprise/commit/cf3c2fce8a6b7b2d7547d44a0e4423f887986d52 opw-6061993 Forward-Port-Of: odoo/enterprise#121521
Timesheet assistant entries now display durations more clearly without overlapping nearby text. Long titles wrap properly, spacing is improved, and the chronological view is adjusted so users can read timesheet details more easily.
Original PR description
- enforce duration in one line - add a gap between the title and duration - wrap title if so long - adapt flex direction of chronological view to avoid overlapping of title and start time --- task-6432434 Forward-Port-Of: odoo/enterprise#128001 Forward-Port-Of: odoo/enterprise#126236
Historical Luxembourg payslips now calculate indexed wages using the wage index that was active at the payslip period end date, rather than today's index. This helps ensure past payroll calculations remain accurate when wage indexes change over time.
Original PR description
Historical payslips incorrectly used today's wage index instead of the index active during the payslip period. Now, salary rules evaluate the indexed wage using `payslip.date_to` via the new `_get_l10n_lu_indexed_wage(date)` contract method. Task: 6395557 Forward-Port-Of: odoo/enterprise#125861
Users can now create a warehouse directly while setting up stock by vehicle mappings. This removes an unnecessary setup blocker and makes configuring vehicle-based stock operations smoother.
Original PR description
Before this commit, users could not create a new warehouse directly from the 'stock by vehicle' mapping view because the `warehouse_id` field had the `no_create` option enabled.
With this commit, we remove `options="{'no_create': True}"` from the `warehouse_id` field in both the list and form views. This enables on-the-fly warehouse creation directly from the vehicle mapping settings.
task-6381795The Dutch reporting module now skips over status records that are missing the accounting entry needed for a chatter message. This prevents one incomplete tax return record from blocking status updates for all Digipoort tax returns.
Original PR description
The `l10n_nl_reports_sbr_status_info` contains the `l10n_nl_reports_sbr.status.service` class. The class is responsible for fetching the status of sent Digipoort tax returns. The status is then posted as a chatter message to the tax return's closing entry. Issues can arise when one of the status service records is, for whatever reason, missing a closing entry. In such case, the message cannot be posted, resulting in an exception being raised. Since the records are processed in a loop without a try-catch, this causes the whole action to fail. This can lead to one broken record effectively shutting down the whole module's functionality. This PR adds some if-else checks to gracefully handle the case where the closing entry is missing. Related tickets: opw-5901446 and opw-6410082 Forward-Port-Of: odoo/enterprise#127844 Forward-Port-Of: odoo/enterprise#125996
When receiving lot-tracked products with putaway rules, the Barcode app now keeps the intended storage shelf for additional scanned lots. This prevents items from being shown in the wrong stock location and helps warehouse teams process receipts accurately.
Original PR description
Steps to reproduce --- 1. Enable Storage Locations and Lots & Serial Numbers. 2. Add a putaway rule sending a lot-tracked product from WH/Stock to WH/Stock/Shelf 1. 3. Confirm a receipt reserving 2…
Steps to reproduce --- 1. Enable Storage Locations and Lots & Serial Numbers. 2. Add a putaway rule sending a lot-tracked product from WH/Stock to WH/Stock/Shelf 1. 3. Confirm a receipt reserving 2 units of that product; putaway sets the reserved move line destination to WH/Stock/Shelf 1. 4. In the Barcode app, scan a first lot, then a second lot. The second lot lands on a separate line at WH/Stock instead of WH/Stock/Shelf 1. Issue --- The first lot reuses the reserved line and keeps its Shelf 1 destination. The second lot cannot reuse it because its tracking number differs, so `_findLine` returns nothing and `_getNewLineDefaultValues` builds a new line with `location_dest_id` set to `_defaultDestLocation()`, the picking's default destination (WH/Stock). https://github.com/odoo/enterprise/blob/314a79b774f30dc9377b2971492576c4b84483e1/stock_barcode/static/src/models/barcode_picking_model.js#L1591-L1601 Putaway relocates the destination on the move line at reservation, never on the picking, so only the reserved line carries Shelf 1. Since `groupKey` includes `location_dest_id`, the new line does not group with the first lot and shows separately at WH/Stock. This is not a regression: new lines have always defaulted to the operation destination. https://github.com/odoo/enterprise/blob/314a79b774f30dc9377b2971492576c4b84483e1/stock_barcode/static/src/models/barcode_picking_model.js#L239-L241 The new line now inherits the selected line's `location_dest_id`, already relocated by putaway, instead of the default. opw-6317077 Forward-Port-Of: odoo/enterprise#127906 Forward-Port-Of: odoo/enterprise#125309
Steps to reproduce: --- - Install `website_sale_collect` without demo data. - Create and publish a product. - Add the product to the cart and proceed to checkout. - Complete the main address. - Select a `Pick Up in Store` delivery method and click checkout. Issue: --- The checkout redirects back to the address form instead of continuing to the payment step. Root cause: --- During checkout, `shop/checkout`[1] calls `_check_cart_and_addresses()`, which eventually invokes `_check_ad
Original PR description
Steps to reproduce: --- - Install `website_sale_collect` without demo data. - Create and publish a product. - Add the product to the cart and proceed to checkout. - Complete the main address. -…
Steps to reproduce: --- - Install `website_sale_collect` without demo data. - Create and publish a product. - Add the product to the cart and proceed to checkout. - Complete the main address. - Select a `Pick Up in Store` delivery method and click checkout. Issue: --- The checkout redirects back to the address form instead of continuing to the payment step. Root cause: --- During checkout, `shop/checkout`[1] calls `_check_cart_and_addresses()`, which eventually invokes `_check_addresses()`[2]. That method then calls `_check_delivery_address()`[3] to verify that all mandatory delivery address fields are present. When db is initialized without demo data, the pickup location address may not contain all mandatory fields (such as ZIP code). As a result, the validation fails and the checkout incorrectly redirects the customer back to the address form, even though the delivery address is a pickup location that should not be edited by the customer. Solution: --- Override `_can_be_edited_by_current_customer()` to treat the selected pickup location as a non-editable address. Since the pickup location belongs to the store, it does not make sense to ask the customer to edit or complete its address. This prevents the checkout flow from requesting address completion and allows the customer to proceed directly to the payment step. [1]https://github.com/odoo/odoo/blob/815de3f1a43bccdb5714436da2060f7a45aa385e/addons/website_sale/controllers/main.py#L1141-L1142 [2]https://github.com/odoo/odoo/blob/815de3f1a43bccdb5714436da2060f7a45aa385e/addons/website_sale/controllers/main.py#L1894-L1895 [3]https://github.com/odoo/odoo/blob/815de3f1a43bccdb5714436da2060f7a45aa385e/addons/website_sale/controllers/main.py#L1945 opw-6394166 I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#277189
20 changes
Security fixes and vulnerability patches
This fix prevents users from linking a signature request to business records they are not allowed to view. It closes a gap that could expose protected record information through signed documents, aligning manual edits with the permissions already enforced in the interface.
Original PR description
Version: saas-18.3 Reported issue: 1. Marc Demo creates a sign request from a template whose fields are automatically populated from the linked record. 2. It is sent to himself. He does not have…
Version: saas-18.3 Reported issue: 1. Marc Demo creates a sign request from a template whose fields are automatically populated from the linked record. 2. It is sent to himself. He does not have access to all records of a referenced model (e.g. Sales Orders). 3. The "Linked To" (reference_doc) field is edited afterwards to point to a record the signer does not have access to. By the time it's signed, the value of that record becomes visible - so a user can, simply by changing the linked record, see the value of a record they were never authorized to access. Even a Sign Manager could link a request to a record they have no access to and later see its value through it. Issue: `reference_doc` could be set or changed to any record of any allowed model with no validation that the acting user actually has access to it. In the interface, you can only create a signature request from a record you can see, but editing `reference_doc` manually (via write(), RPC, etc.) was not held to the same rule, making it an easy way to leak information about records outside your normal access. Cause: The only restriction was cosmetic, enforced client-side by the record picker widget filtering its search results. Nothing on the server validated the value being written to `reference_doc`. Fix: `write()` now checks that the acting user has read access to the target record before allowing `reference_doc` to be set, raising a ValidationError otherwise, bringing manual edits in line with what the interface already enforces when creating a request. Forward-Port-Of: odoo/enterprise#127870 Forward-Port-Of: odoo/enterprise#127715
Enhancements to existing features
Odoo now checks whether a payment or batch payment exceeds the maximum amount allowed by the connected financial institution before starting the transfer. This helps prevent failed payment attempts and gives businesses earlier clarity when a bank or provider enforces transaction limits.
Original PR description
Before trying to initiate payments through Odoo/Odoofin, we should check that the total amount for the (batch) payment does not exceed the maximum payment amount allowed by the institution (some Powens institutions introduced that limit). task-6310729 Forward-Port-Of: odoo/enterprise#126955 Forward-Port-Of: odoo/enterprise#121513
Timesheet Assistant suggestions for Helpdesk tickets now show the actual ticket name and fill it into the timesheet form when users add a suggestion. The update also prevents unrelated assistant events from being grouped under the wrong name, reducing duplicate entries and incorrect time totals.
Original PR description
Before this commit, the Timesheet Assistant displayed static labels for Helpdesk Tickets. Furthermore, when a user clicked "Add" on a ticket suggestion, the Timesheet Inline Form did not auto-populate the ticket name, as the source ID was lost during the grouping phase. Task: 6320652 Forward-Port-Of: odoo/enterprise#121662
Filters are now completely exclusive, which prevent 0 results but also prevents more "open" searches as "Lenovo" OR "HP" AND "512GB SSD". Stop updating the filters based on selected attribute values to avoid the extra product query and allow selecting non exclusive filters from the same attribute. task-6341310 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Original PR description
Filters are now completely exclusive, which prevent 0 results but also prevents more "open" searches as "Lenovo" OR "HP" AND "512GB SSD". Stop updating the filters based on selected attribute values to avoid the extra product query and allow selecting non exclusive filters from the same attribute. task-6341310 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Resolved issues and error corrections
XLSX exports no longer fail when annotated Deferred Revenue Reports have no column headers. This helps accounting users reliably export reports with their notes included, avoiding a server error during routine reporting.
Original PR description
**Steps to reproduce:** * Install the **Accounting** module. * Unhide the **Start Date** and **End Date** fields on invoice lines. * Create and post a customer invoice with deferred dates. * Go to…
**Steps to reproduce:** * Install the **Accounting** module. * Unhide the **Start Date** and **End Date** fields on invoice lines. * Create and post a customer invoice with deferred dates. * Go to **Accounting → Reports → Deferred Revenue Report**. * Add an annotation to a deferred revenue line by clicking the **annotate** from three dots next to the account. * Export the report in **XLSX** format. **Observed behavior:** * The export fails with a server error: `UnboundLocalError: cannot access local variable 'annotations_x_offset' where it is not associated with a value` **Cause:** * The variable `annotations_x_offset` is assigned inside the `for header_level_index, header_level in enumerate(options['column_headers'])` loop, which writes the "Annotations" column header for each header level. * The Deferred Revenue Report produces an empty `column_headers` list, so the loop body never executes and `annotations_x_offset` is never assigned. * When the code later tries to write annotation data for each report line, it references the unassigned variable, causing Python to raise `UnboundLocalError`. **Fix:** * Introduce a boolean flag `annotations_header_written = False` before the header loop to explicitly track whether the "Annotations" column header has already been written. * Inside the header loop, set `annotations_header_written = True` after writing the header. * After writing all individual column headers (where `x_offset` already points to the first free column after all data columns), add a fallback: if `report_annotations` is set but `annotations_header_written` is still `False`, assign `annotations_x_offset` from the current `x_offset` and write the "Annotations" header. opw-6354473 Forward-Port-Of: odoo/enterprise#127840 Forward-Port-Of: odoo/enterprise#122768
Starting a timesheet timer from a task linked to a sales order now automatically carries over the correct billable sales item. This prevents missing billing information and avoids extra manual steps for users tracking time on customer work.
Original PR description
**Problem:** Starting the timer on a task that is linked to a sale order item produces a timesheet that is not linked to it, and the Billable toggle is missing from the timer. **Steps to reproduce:**…
**Problem:** Starting the timer on a task that is linked to a sale order item produces a timesheet that is not linked to it, and the Billable toggle is missing from the timer. **Steps to reproduce:** 1. Install Timesheets and Sales 2. Open a task whose Sales Order Item is set 3. Start the timer from the Timesheets systray 4. Save it and open the resulting timesheet **Current behavior:** The Sales Order Item is empty. The Billable toggle only appears after removing and re-adding the task in the timer. **Expected behavior:** The timer is billable on the task's sale order item as soon as it is opened. **Cause of the issue:** `_get_timesheet_pre_filled_form_data` returns only `project_id` and `task_id`. The timer form merges that pre-fill over `timesheet_default_values`, which `lazy_session_info` computes once per session from `account.analytic.line.new()` - a record with no project and no task, so `so_line`, `allow_billable` and `has_available_so` are all `False` in it. Because the pre-fill carries none of those keys, they keep the task-independent session values: the timesheet stays unlinked from the sale order item, and the Billable toggle stays hidden since it is displayed from `has_available_so`. **Fix:** The pre-fill endpoint is the only place that knows which task the timer is being opened on, so it is where the task-dependent values have to be resolved. Reading them off a new timesheet built with that project and task keeps the endpoint generic - it returns whatever `_get_aw_timesheet_fields_specification` declares, so the sale fields stay owned by sale_timesheet_enterprise rather than being named in timesheet_grid. Dropping the session defaults instead was rejected: they are also the only source of `date`, `user_id` and `company_id` for the timer record, and removing them makes saving fail on the required Date field. opw-6423577 Forward-Port-Of: odoo/enterprise#127800
Barcode receipt processing now keeps the putaway destination when users scan additional lots for the same product. This prevents items from being shown or processed in the default stock location instead of the intended shelf, reducing warehouse confusion and manual corrections.
Original PR description
Steps to reproduce --- 1. Enable Storage Locations and Lots & Serial Numbers. 2. Add a putaway rule sending a lot-tracked product from WH/Stock to WH/Stock/Shelf 1. 3. Confirm a receipt reserving 2…
Steps to reproduce --- 1. Enable Storage Locations and Lots & Serial Numbers. 2. Add a putaway rule sending a lot-tracked product from WH/Stock to WH/Stock/Shelf 1. 3. Confirm a receipt reserving 2 units of that product; putaway sets the reserved move line destination to WH/Stock/Shelf 1. 4. In the Barcode app, scan a first lot, then a second lot. The second lot lands on a separate line at WH/Stock instead of WH/Stock/Shelf 1. Issue --- The first lot reuses the reserved line and keeps its Shelf 1 destination. The second lot cannot reuse it because its tracking number differs, so `_findLine` returns nothing and `_getNewLineDefaultValues` builds a new line with `location_dest_id` set to `_defaultDestLocation()`, the picking's default destination (WH/Stock). https://github.com/odoo/enterprise/blob/314a79b774f30dc9377b2971492576c4b84483e1/stock_barcode/static/src/models/barcode_picking_model.js#L1591-L1601 Putaway relocates the destination on the move line at reservation, never on the picking, so only the reserved line carries Shelf 1. Since `groupKey` includes `location_dest_id`, the new line does not group with the first lot and shows separately at WH/Stock. This is not a regression: new lines have always defaulted to the operation destination. https://github.com/odoo/enterprise/blob/314a79b774f30dc9377b2971492576c4b84483e1/stock_barcode/static/src/models/barcode_picking_model.js#L239-L241 The new line now inherits the selected line's `location_dest_id`, already relocated by putaway, instead of the default. opw-6317077 Forward-Port-Of: odoo/enterprise#127704 Forward-Port-Of: odoo/enterprise#125309
Sendcloud delivery requests now include the recipient tax number in customs information, preventing DPD international shipments from failing validation. The fix also ensures required customs fields use acceptable fallback values and English tax labels so Sendcloud accepts the shipment data regardless of the user's database language.
Original PR description
### This is a revision of #119399 which had to be reverted. Original issue ----- Deliveries cannot be validated using DPD with Sendcloud, users get an error. Steps to reproduce ----- - Set up…
### This is a revision of #119399 which had to be reverted. Original issue ----- Deliveries cannot be validated using DPD with Sendcloud, users get an error. Steps to reproduce ----- - Set up Sendcloud DPD - Create a SO - Interntional customer - Some VAT number - Some product - Add sendcloud delivery - Confirm SO - Validate the linked picking > Error: “The receiver VAT number is missing; please provide it to continue” Issue's cause ----- Tax numbers should be included in the `customs_information` field of the request as per the API https://sendcloud.dev/api/v2/parcels/create-a-parcel-or-parcels#body-one-of-0-parcel-customs-information-tax-numbers For the `vat_label` field, we have to force the language to English in the context because the field is translated by default, but sendcloud only accepts the english names (eg French "TVA" is not accepted, expected value is "VAT"). https://github.com/odoo/odoo/blob/d1d1610332a1596d026fb0a42ec236d1a79c71cc/odoo/addons/base/models/res_country.py#L75 Revert cause ----- The vat_label field is marked for translation (translate=True) https://github.com/odoo/odoo/blob/d1d1610332a1596d026fb0a42ec236d1a79c71cc/odoo/addons/base/models/res_country.py#L75 So if the user has the DB in french for example, we are sending "TVA" instead of "VAT" in the name field. Other issues ----- - We need to provide an actual fallback for `customs_invoice_nr`. As it stands, if we create a new delivery it cannot be validated because Sendcloud doesn't accept for the field to be empty. - Same for `name`, we need to provide an actual fallback. ----- Ticket: opw-6250860 Forward-Port-Of: odoo/enterprise#126818 Forward-Port-Of: odoo/enterprise#124245
Historical Luxembourg payslips now use the wage index that was active at the payslip date instead of the current index. This helps ensure past payroll calculations remain accurate when wage index values change over time.
Original PR description
Historical payslips incorrectly used today's wage index instead of the index active during the payslip period. Now, salary rules evaluate the indexed wage using `payslip.date_to` via the new `_get_l10n_lu_indexed_wage(date)` contract method. Task: 6395557 Forward-Port-Of: odoo/enterprise#125861
An internal accounting test was updated to match a related platform fix that changes how grouped data is read. This helps keep automated checks accurate and reduces the risk of false test failures during future accounting updates.
Original PR description
The fix at https://github.com/odoo/odoo/pull/281911 adds bin_size: tru in the web_read_group. This commit adpats an accounting test as a consequence Forward-Port-Of: odoo/enterprise#127719 Forward-Port-Of: odoo/enterprise#127638
Fixed an access issue that prevented regular timesheet users from creating or duplicating their own assistant rules. The shared user setting is now protected for non-administrators while still allowing users to manage rules intended for themselves.
Original PR description
Steps to reproduce --- - Log in as a user with "User: all timesheets" access rights. - Open the assistant rules list or kanban view. - Create a new assistant rule. Issue --- - Normal users get an…
Steps to reproduce --- - Log in as a user with "User: all timesheets" access rights. - Open the assistant rules list or kanban view. - Create a new assistant rule. Issue --- - Normal users get an Access Error saying "Only Timesheet Administrators are allowed to modify the 'Shared With' field." The create and write methods check for shared_user_ids in the values. When creating a rule, this field is also set by default to the current user, so the check incorrectly blocks the creation. - An Access Error is raised when a user tries to duplicate a rule they don't have access to. - In the kanban view, using the avatar widget raises an Access Error instead of being read-only. Expected behavior --- - Users should be able to create their own assistant rules. - shared_user_ids field should be read-only for non-admins. - Users should be able to duplicate a rule they don't have access to, with themselves set as "Shared With". - The avatar widget should be read-only for non-admins. Fix --- - Move the validation to a constraint on shared_user_ids. - Override copy_data to set shared_user_ids to the current user for non-admins. - Make the kanban avatar widget read-only for non-admins. saas-19.4 replaced the "Shared With" mechanism with an "Applies To" field to control which employees a rule applies to Related - https://github.com/odoo/enterprise/pull/117235 task-6460116
Changing the request unit of a time type should not recompute existing leaves, as their computed dates and duration must be preserved. This behavior was introduced in hr_holidays by removing the request unit from the dependencies of the leave date and duration computations. The French localization still declared `work_entry_type_request_unit` as a dependency of `_compute_date_from_to`, causing existing leaves to be invalidated and recomputed when the time type configuration changed. Thi
Original PR description
Changing the request unit of a time type should not recompute existing leaves, as their computed dates and duration must be preserved. This behavior was introduced in hr_holidays by removing the request unit from the dependencies of the leave date and duration computations. The French localization still declared `work_entry_type_request_unit` as a dependency of `_compute_date_from_to`, causing existing leaves to be invalidated and recomputed when the time type configuration changed. This commit removes this dependency to align the French computation with the base behavior and preserve historical leave values. Related: https://github.com/odoo/odoo/pull/261036 [error-243674 ](https://runbot.odoo.com/odoo/error/243674) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
## Issue When filtering projects using the "Timesheets >100%" filter, some projects with negative remaining hours (and with their `is_project_overtime` field set to True) won't be displayed, even though their expected hours are completed. This happens with projects which have tasks set to the "Done" or "Cancelled" state. The timesheets entries in those tasks are not taken into account when searching using the "Timesheets >100%" filter. ## Steps to reproduce 1. Install *Task Logs* (`hr_ti
Original PR description
## Issue When filtering projects using the "Timesheets >100%" filter, some projects with negative remaining hours (and with their `is_project_overtime` field set to True) won't be displayed, even…
## Issue
When filtering projects using the "Timesheets >100%" filter, some projects with negative remaining hours (and with their `is_project_overtime` field set to True) won't be displayed, even though their expected hours are completed.
This happens with projects which have tasks set to the "Done" or "Cancelled" state. The timesheets entries in those tasks are not taken into account when searching using the "Timesheets >100%" filter.
## Steps to reproduce
1. Install *Task Logs* (`hr_timesheet`)
2. Create a Project P (with Timehseets enabled)
3. Set the allocated hours of the project to 3:00 (3 hours)
4. Create two tasks:
- T1: State "In progress", and one timesheet entry of 2:00 (2 hours)
- T2: State "Done", and one timesheet entry of 2:00 (2 hours)
5. Back to the project view, set the filter to "Timesheets >100%"
6. **Project P is not shown, even though the total time spent on the project is 4 hours, completing the allocated hours set on the project.**
## Cause
The `_search_is_project_overtime` method filters out the tasks in "closed" states (Done/Cancelled) when computing the amount of time spent on the project.
https://github.com/odoo/odoo/blob/126b5bdd1e85771549198976f8570cd2ff167608/addons/hr_timesheet/models/project_project.py#L103-L114
This does not match with the behavior of the `_compute_is_project_overtime`, which does not take into account the state of the tasks to determine the value of the field:
https://github.com/odoo/odoo/blob/126b5bdd1e85771549198976f8570cd2ff167608/addons/hr_timesheet/models/project_project.py#L85-L94
This leads to a confusing behavior, where a project can have its `is_project_overtime` field set to True, but will still not be shown when using the "Timsheets >100%", even though that filter is defined as `[("is_project_overtime", "=", True)]`.
The compute method was updated by https://github.com/odoo/odoo/commit/d4252825f52a3172420dcda0ea394e42da9f8853, but the related search method was left unchanged, leading to this slight incoherence between the two methods.
opw-6422173
Forward-Port-Of: odoo/odoo#282338
Forward-Port-Of: odoo/odoo#281997On highly loaded runbots, waiting a single animation frame might not be sufficient for the table deselection to be available in the DOM after a key press. This commit waits for the table to be deselected before continuing the test. runbot-944722 Forward-Port-Of: odoo/odoo#282008
Original PR description
On highly loaded runbots, waiting a single animation frame might not be sufficient for the table deselection to be available in the DOM after a key press. This commit waits for the table to be deselected before continuing the test. runbot-944722 Forward-Port-Of: odoo/odoo#282008
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError: maximum recursion depth exceeded`. **Cause:** - In a previous commit (3e32d7b9eace62dfa7334009707a93967906c726) aimed at fixing stale analytic distribution totals, the assignment loop in `_compute_discount_allocation_needed` was changed from iterating over `self` to `self.move_id.line_ids`. -
Original PR description
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError:…
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError: maximum recursion depth exceeded`. **Cause:** - In a previous commit (3e32d7b9eace62dfa7334009707a93967906c726) aimed at fixing stale analytic distribution totals, the assignment loop in `_compute_discount_allocation_needed` was changed from iterating over `self` to `self.move_id.line_ids`. - While this ensured all lines generated updated distribution ratios, it violated the compute logic: assigning values to records outside the current compute batch (`self`). - By executing `line.discount_allocation_dirty = True` on external sibling lines, the method forced the ORM to trigger out-of-band `write()` calls. These writes re-triggered dependency checks (`_field_will_change`), which invoked the compute method again, leading to a recursive loop. **Fix:** 1. Revert the assignment iteration back to `for line in self:`. 2. To preserve the intention of the previous commit (ensuring all lines recompute their shared distribution pool when one line changes), modify the method's `@api.depends` to be `move_id.line_ids.discount` and `move_id.line_ids.analytic_distribution`. By declaring these relational dependencies, modifying a single line now batches all sibling lines into `self` from the start. This allows the lines to synchronize properly without triggering new ORM writes, eliminating the recursion. opw-6451854 Forward-Port-Of: odoo/odoo#282050
Steps to reproduce --- 1. Create and confirm a sale order. 2. Create a down payment invoice on it and post it: the down payment line reads "Down Payment (ref: INV/... on ...)". 3. Open that invoice and use Reverse and Create Invoice, then post the newly created draft down payment invoice. 4. Open the sale order: the down payment line has lost its reference and now reads only "Down Payment", and that empty label also carries over to the down payment section when generating the final invoice.
Original PR description
Steps to reproduce --- 1. Create and confirm a sale order. 2. Create a down payment invoice on it and post it: the down payment line reads "Down Payment (ref: INV/... on ...)". 3. Open that invoice…
Steps to reproduce --- 1. Create and confirm a sale order. 2. Create a down payment invoice on it and post it: the down payment line reads "Down Payment (ref: INV/... on ...)". 3. Open that invoice and use Reverse and Create Invoice, then post the newly created draft down payment invoice. 4. Open the sale order: the down payment line has lost its reference and now reads only "Down Payment", and that empty label also carries over to the down payment section when generating the final invoice. Issue --- The down payment line description is built by `_get_downpayment_description`, which only produces the "Down Payment (ref: ... on ...)" label when exactly one customer invoice is linked to the down payment `sale.order.line`, guarded by `len(invoice) == 1`. https://github.com/odoo/odoo/blob/3a5f7431effd4b2b2eb8ce3eed81aaba42fcd8ea/addons/sale/models/sale_order_line.py#L484-L509 Reverse and Create Invoice runs `account.move.reversal.modify_moves`, which copies the reversed invoice with `include_business_fields=True`, so the copied line keeps its `sale_line_ids` and the new draft invoice is attached to the very same down payment line as the reversed original. https://github.com/odoo/odoo/blob/3a5f7431effd4b2b2eb8ce3eed81aaba42fcd8ea/addons/account/wizard/account_move_reversal.py#L142-L149 That down payment line then references two `out_invoice` moves (the reversed one and the re-issued one), so `len(invoice) == 1` is false and the label silently falls back to the bare "Down Payment", losing the reference that the final invoice's down payment section reuses. Going back to the sale order to raise a fresh down payment instead creates a new line, which keeps a single invoice and is why the slower flow is unaffected. The `len(invoice) == 1` guard was introduced in ba954604e529. Discarding the reversed invoice (`payment_state == 'reversed'`) leaves the active re-issued invoice as the single match, so its reference is shown again; when the only linked invoice is itself reversed, the fallback keeps displaying it so existing descriptions are preserved. opw-6353384 Forward-Port-Of: odoo/odoo#277802
### Steps to reproduce: - Enable "Lots & Serial Numbers" in the Inventory settings - Create a storable product tracked by lots - Create a lot for that product and leave it without any quantity - Open the lot form > cog menu > Scrap #### > ValueError: Expected singleton: stock.location() ### Cause of the issue: A lot is only given a `location_id` when all its positive quants lay in a single location, so a lot with no quant at all has none: https://github.com/odoo/odoo/blob/8b8852f1c3
Original PR description
### Steps to reproduce: - Enable "Lots & Serial Numbers" in the Inventory settings - Create a storable product tracked by lots - Create a lot for that product and leave it without any quantity - Open…
### Steps to reproduce: - Enable "Lots & Serial Numbers" in the Inventory settings - Create a storable product tracked by lots - Create a lot for that product and leave it without any quantity - Open the lot form > cog menu > Scrap #### > ValueError: Expected singleton: stock.location() ### Cause of the issue: A lot is only given a `location_id` when all its positive quants lay in a single location, so a lot with no quant at all has none: https://github.com/odoo/odoo/blob/8b8852f1c3ae5b78a4a3e99c99eacab83f0be5c9/addons/stock/models/stock_lot.py#L169-L172 The `action_scrap` nevertheless always forwards that value to the scrap move, so the form is opened with `default_location_id` set to `False`: https://github.com/odoo/odoo/blob/8b8852f1c3ae5b78a4a3e99c99eacab83f0be5c9/addons/stock/models/stock_lot.py#L415-L426 Now, the issue is that the `_onchange_lot_ids` of the new stock move then evaluates the reservation of a move whose source location is still empty which raises the traceback because of a `self.ensure_one` required on the location to determine if the move `should_bypass_reservation`: https://github.com/odoo/odoo/blob/8b8852f1c3ae5b78a4a3e99c99eacab83f0be5c9/addons/stock/models/stock_move.py#L1515 https://github.com/odoo/odoo/blob/8b8852f1c3ae5b78a4a3e99c99eacab83f0be5c9/addons/stock/models/stock_move.py#L1989-L1992 https://github.com/odoo/odoo/blob/8b8852f1c3ae5b78a4a3e99c99eacab83f0be5c9/addons/stock/models/stock_location.py#L414-L416 Note that if the default key was not present, the traceback would not be triggered and the scrap would be performed from the `default_stock_location` of the company: https://github.com/odoo/odoo/blob/4c4219a7d9d51f703b15e83ab755faf1f2c8a71d/addons/stock/models/res_company.py#L21-L22 Indeed the issue is that the `default_get` looks the context key up by *membership*, so that a falsy `default_location_id` is still returned as a default value for the field if provided: https://github.com/odoo/odoo/blob/8b8852f1c3ae5b78a4a3e99c99eacab83f0be5c9/odoo/orm/models.py#L1313-L1317 And therefore will not be computed in the first onchange: https://github.com/odoo/odoo/blob/8b8852f1c3ae5b78a4a3e99c99eacab83f0be5c9/addons/web/models/models.py#L1974-L1988 Note: The `stock.lot.action_scrap`, and this `default_location_id` line with it, were introduced by 1c7d80a10b5d, which replaced the `stock.scrap` model by scrap `stock.move` records. That commit is only present from saas-19.2 onwards, so earlier versions are not affected. opw-6441937 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280666
How to reproduce: - In a Fiscal Position, map the Downpayment account set in the settings to anything else - Put that Fiscal Position on a SO. - On that SO, create a Downpayment invoice -> The regular Downpayment account is used on the Downpayment invoice, but it should have been mapped because of the Fiscal Position account mapping Solution: Pre-map the company's default down payment account using the Sales Order's Fiscal Position before passing it to the invoice line creation
Original PR description
How to reproduce: - In a Fiscal Position, map the Downpayment account set in the settings to anything else - Put that Fiscal Position on a SO. - On that SO, create a Downpayment invoice -> The regular Downpayment account is used on the Downpayment invoice, but it should have been mapped because of the Fiscal Position account mapping Solution: Pre-map the company's default down payment account using the Sales Order's Fiscal Position before passing it to the invoice line creation. This ensures the correct account mapping is always respected for advance payment invoices. Task-6212218 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280777 Forward-Port-Of: odoo/odoo#279464
Before this commit and since the new read_group (which fetches records from open groups server side), images were loaded as base64, overloading the return payload and potentially triggering overload errors (MemoryError) This was because the bin_size = true context key was forgotten. After this commit, images are not loaded as base64 thanks to that context key Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged:
Original PR description
Before this commit and since the new read_group (which fetches records from open groups server side), images were loaded as base64, overloading the return payload and potentially triggering overload errors (MemoryError) This was because the bin_size = true context key was forgotten. After this commit, images are not loaded as base64 thanks to that context key Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282099 Forward-Port-Of: odoo/odoo#281911
Miscellaneous changes
Allow resetting sent moves to draft. Ensures a rectificative flow exists or is created. Task: 6273211 Forward-Port-Of: odoo/odoo#273013
Original PR description
Allow resetting sent moves to draft. Ensures a rectificative flow exists or is created. Task: 6273211 Forward-Port-Of: odoo/odoo#273013
14 changes
Enhancements to existing features
Timesheet Assistant suggestions for helpdesk tickets now show the actual ticket name and fill it into the timesheet form when selected. This makes time entry faster and avoids duplicate or incorrect assistant suggestions that could inflate calendar-based durations.
Original PR description
Before this commit, the Timesheet Assistant displayed static labels for Helpdesk Tickets. Furthermore, when a user clicked "Add" on a ticket suggestion, the Timesheet Inline Form did not auto-populate the ticket name, as the source ID was lost during the grouping phase. Task: 6320652
Resolved issues and error corrections
Starting a timesheet timer from a task linked to a sales order now correctly carries over the related billable sales item. This ensures the resulting timesheet is billed against the right customer order and shows the Billable option immediately.
Original PR description
**Problem:** Starting the timer on a task that is linked to a sale order item produces a timesheet that is not linked to it, and the Billable toggle is missing from the timer. **Steps to reproduce:**…
**Problem:** Starting the timer on a task that is linked to a sale order item produces a timesheet that is not linked to it, and the Billable toggle is missing from the timer. **Steps to reproduce:** 1. Install Timesheets and Sales 2. Open a task whose Sales Order Item is set 3. Start the timer from the Timesheets systray 4. Save it and open the resulting timesheet **Current behavior:** The Sales Order Item is empty. The Billable toggle only appears after removing and re-adding the task in the timer. **Expected behavior:** The timer is billable on the task's sale order item as soon as it is opened. **Cause of the issue:** `_get_timesheet_pre_filled_form_data` returns only `project_id` and `task_id`. The timer form merges that pre-fill over `timesheet_default_values`, which `lazy_session_info` computes once per session from `account.analytic.line.new()` - a record with no project and no task, so `so_line`, `allow_billable` and `has_available_so` are all `False` in it. Because the pre-fill carries none of those keys, they keep the task-independent session values: the timesheet stays unlinked from the sale order item, and the Billable toggle stays hidden since it is displayed from `has_available_so`. **Fix:** The pre-fill endpoint is the only place that knows which task the timer is being opened on, so it is where the task-dependent values have to be resolved. Reading them off a new timesheet built with that project and task keeps the endpoint generic - it returns whatever `_get_aw_timesheet_fields_specification` declares, so the sale fields stay owned by sale_timesheet_enterprise rather than being named in timesheet_grid. Dropping the session defaults instead was rejected: they are also the only source of `date`, `user_id` and `company_id` for the timer record, and removing them makes saving fail on the required Date field. opw-6423577
This fix improves how calendar event suggestions are matched to the right project or task when creating timesheets. It also corrects event duration calculations after overlapping calendar items are adjusted, helping users see more reliable suggested time entries.
Original PR description
task: 6435164
Fixes an issue where running task or ticket timers could jump between values, show negative seconds, or track incorrect elapsed time after repeated stop/start and page reload cycles. The timer now updates only from the active on-screen widget, improving reliability for users recording work time.
Original PR description
A running task/ticket timer can sometimes stutter, show negative values, and track the wrong elapsed time. ### Steps to reproduce On a record with a timesheet timer (for example, a Field Service…
A running task/ticket timer can sometimes stutter, show negative values, and track the wrong elapsed time. ### Steps to reproduce On a record with a timesheet timer (for example, a Field Service task): 1. Start the timer and let it run for about 15-20 seconds. 2. Stop it and confirm the dialog. 3. Start it again. This creates a new `timer_start`. 4. Reload the page. The timer starts jumping every second between two different values. As it keeps running, it can even show negative values such as `00:00:-57`. If the problem does not appear right away, repeat steps 2-4 a few times. It usually shows up after a few stop/start/reload cycles. ### Cause The timer shown in the button bar is the `timer_start_field` widget. It starts a `setInterval` that updates a shared `TimerReactive` object once per second. While a form is loading, Odoo renders it several times in a row (for example: a first render, another when the chatter is loaded, and another when the record data comes back from the server). Rendering a form builds all of its fields to produce the display, so each of these renders creates its own `timer_start_field`. Odoo keeps and mounts only the render that ends up on screen; the earlier ones are thrown away before being mounted. The interval is started while the field renders, from the record observer set up in `setup`, before the field is mounted. So the fields that are later thrown away also start an interval. Those intervals keep running for the rest of the session. Each one updates the same shared `TimerReactive` object using the `timer_start` it was created with. As long as every instance has the same `timer_start`, they all write the same value and the problem stays hidden. After the timer is stopped and started again, the old instances keep the old `timer_start` while the mounted one uses the new one. Every second they overwrite each other's value, so the timer jumps between two different elapsed times. When the instance with the newer `timer_start` writes right after one with an older start, it tries to show a smaller elapsed time than what is already there, and the subtraction in `TimerReactive` produces a negative number of seconds. ### Fix Move the per-second timer update into a `useEffect`. The effect only runs after the field is mounted, and Owl automatically cleans it up when the field is unmounted or when `timer_start` or `timer_pause` change. This means fields that are destroyed before they are mounted never start an interval, so only the mounted field updates the shared timer. `onRecordChange` no longer starts or stops the interval. It only updates the displayed timer value to match the current record. opw-6209405 Forward-Port-Of: odoo/enterprise#126242
Luxembourg payroll calculations now use the wage index that was active at the end date of each payslip, rather than the current index. This helps ensure historical payslips are recalculated accurately and prevents incorrect salary amounts for past periods.
Original PR description
Historical payslips incorrectly used today's wage index instead of the index active during the payslip period. Now, salary rules evaluate the indexed wage using `payslip.date_to` via the new `_get_l10n_lu_indexed_wage(date)` contract method. Task: 6395557 Forward-Port-Of: odoo/enterprise#125861
Confirmed manufacturing orders now correctly update when related bill of materials operations are changed or removed. This helps production teams avoid outdated work steps and keeps manufacturing instructions aligned with the latest product setup.
Original PR description
Steps to reproduce: - Create a product with a bom and 2 operations - Create an MO for 1 unit of that product - Confirm the MO - On the bom, delete the second operation and modify the first operation…
Steps to reproduce: - Create a product with a bom and 2 operations - Create an MO for 1 unit of that product - Confirm the MO - On the bom, delete the second operation and modify the first operation on anything else than the company, name or workcenter - Go back to the MO, click the "Update Bom" button > The second operation is not unlinked and the first operation is not updated Cause of the issue: The `action_update_bom` updates the move raws and operations of the MO via the `_link_bom`: https://github.com/odoo/odoo/blob/f66614193cce18f5a3298d03ce7e5f29d54f07e9/addons/mrp/models/mrp_production.py#L1214-L1218 For draft MO's all the work of these updates is done via the compute methods and by deleting all the records unrelevant to the new bom: https://github.com/odoo/odoo/blob/f66614193cce18f5a3298d03ce7e5f29d54f07e9/addons/mrp/models/mrp_production.py#L2603-L2626 And, in that case all the workorders that are not linked to an operation of the bom are expected to be deleted. However, when the MO is not in draft, the update of operations is expected to be performed here: https://github.com/odoo/odoo/blob/f66614193cce18f5a3298d03ce7e5f29d54f07e9/addons/mrp/models/mrp_production.py#L2647-L2664 However, since the operation of the bom has been deleted, the workorder that is expected to be deleted is not linked to any operation and hence does not satisfy the condition to be deleted: https://github.com/odoo/odoo/blob/f66614193cce18f5a3298d03ce7e5f29d54f07e9/addons/mrp/models/mrp_production.py#L2663-L2664 Concerning the non update of operations, it happens because the MO's operation are only updated on the three fields: `company_id`, `workcenter_id`, `name`: https://github.com/odoo/odoo/blob/31df5033e31c193b4576ef37dfbc5fc683817bc5/addons/mrp/models/mrp_production.py#L2647-L2664 https://github.com/odoo/odoo/blob/31df5033e31c193b4576ef37dfbc5fc683817bc5/addons/mrp/models/mrp_production.py#L2628-L2629 However, many other cahnges can and are actually relevant. Note: Prior to commit 80e6ed658fb43584bc2fad673ca40d9af6cf0ab6 operations were archived on boms rather than deleted: https://github.com/odoo/odoo/blob/4a5270218fe6fd7d30edb6d684b3340dc7423bab/addons/mrp/views/mrp_routing_views.xml#L53-L55 As such they would still be linked to an operation (but unrelated to the present values of the bom) and hence would fall into the condition of being unlinked from the MO. Since the bom operations are no longer archived there is no way to determine if an operation used to be linked to a bom and we therefore need to chose between deleting all operations unrelated to the present bom or to keep them all (when the MO has been confirmed). Community: https://github.com/odoo/odoo/pull/269747 opw-6285878 opw-6261738 Forward-Port-Of: odoo/enterprise#122746 Forward-Port-Of: odoo/enterprise#120709
Odoo now recognizes valid Brazilian NF-e invoice XML files even when the main invoice tag has no attributes. This prevents valid vendor bills from being silently skipped during import, helping accounting teams process supplier invoices more consistently.
Original PR description
### Issue before this commit: Certain valid Brazilian NF-e (electronic invoice) XML files fail to import because the system silently ignores them during the initial EDI recognition phase. ### Steps to reproduce the issue: 1. Download Accounting and l10n_br_edi 2. Go to Vendor > Bills 3. Try to import both xmls in the ticket 4. One of the two will not be imported correctly ### Cause of the issue: https://github.com/odoo/enterprise/blob/3ed1721b702555e96c9774969927f6517e855704/l10n_br_edi/models/account_move.py#L819-L827 This function relies on a strict byte string search for b"<NFe " while it's also correct if the tag is only `<NFe>`. ### Reason to introduce the fix: To make the initial NF-e file recognition more robust and compliant with standard XML namespace rules, ensuring Odoo successfully processes all valid Brazilian invoices regardless of attribute formatting. opw-6402843 Forward-Port-Of: odoo/enterprise#127851 Forward-Port-Of: odoo/enterprise#126881
Peruvian accounting reports now use the exchange rate already stored on each accounting entry instead of recalculating it when reports are generated. This reduces rounding differences and improves the reliability of reported figures.
Original PR description
Previously, the `_get_ple_report_data` method computed the currency rate when called. Since the calculation was based on the entry totals, it was prone to rounding errors. This PR makes it use the rate stored in the entry itself. This should lead to more accurate results. opw-6411322 Forward-Port-Of: odoo/enterprise#126882
The Dutch tax reporting status check now continues even when a tax return record is missing its closing entry. This prevents one incomplete or broken record from blocking status updates for all Digipoort tax returns.
Original PR description
The `l10n_nl_reports_sbr_status_info` contains the `l10n_nl_reports_sbr.status.service` class. The class is responsible for fetching the status of sent Digipoort tax returns. The status is then posted as a chatter message to the tax return's closing entry. Issues can arise when one of the status service records is, for whatever reason, missing a closing entry. In such case, the message cannot be posted, resulting in an exception being raised. Since the records are processed in a loop without a try-catch, this causes the whole action to fail. This can lead to one broken record effectively shutting down the whole module's functionality. This PR adds some if-else checks to gracefully handle the case where the closing entry is missing. Related tickets: opw-5901446 and opw-6410082 Forward-Port-Of: odoo/enterprise#127844 Forward-Port-Of: odoo/enterprise#125996
## Issue When filtering projects using the "Timesheets >100%" filter, some projects with negative remaining hours (and with their `is_project_overtime` field set to True) won't be displayed, even though their expected hours are completed. This happens with projects which have tasks set to the "Done" or "Cancelled" state. The timesheets entries in those tasks are not taken into account when searching using the "Timesheets >100%" filter. ## Steps to reproduce 1. Install *Task Logs* (`hr_ti
Original PR description
## Issue When filtering projects using the "Timesheets >100%" filter, some projects with negative remaining hours (and with their `is_project_overtime` field set to True) won't be displayed, even…
## Issue
When filtering projects using the "Timesheets >100%" filter, some projects with negative remaining hours (and with their `is_project_overtime` field set to True) won't be displayed, even though their expected hours are completed.
This happens with projects which have tasks set to the "Done" or "Cancelled" state. The timesheets entries in those tasks are not taken into account when searching using the "Timesheets >100%" filter.
## Steps to reproduce
1. Install *Task Logs* (`hr_timesheet`)
2. Create a Project P (with Timehseets enabled)
3. Set the allocated hours of the project to 3:00 (3 hours)
4. Create two tasks:
- T1: State "In progress", and one timesheet entry of 2:00 (2 hours)
- T2: State "Done", and one timesheet entry of 2:00 (2 hours)
5. Back to the project view, set the filter to "Timesheets >100%"
6. **Project P is not shown, even though the total time spent on the project is 4 hours, completing the allocated hours set on the project.**
## Cause
The `_search_is_project_overtime` method filters out the tasks in "closed" states (Done/Cancelled) when computing the amount of time spent on the project.
https://github.com/odoo/odoo/blob/126b5bdd1e85771549198976f8570cd2ff167608/addons/hr_timesheet/models/project_project.py#L103-L114
This does not match with the behavior of the `_compute_is_project_overtime`, which does not take into account the state of the tasks to determine the value of the field:
https://github.com/odoo/odoo/blob/126b5bdd1e85771549198976f8570cd2ff167608/addons/hr_timesheet/models/project_project.py#L85-L94
This leads to a confusing behavior, where a project can have its `is_project_overtime` field set to True, but will still not be shown when using the "Timsheets >100%", even though that filter is defined as `[("is_project_overtime", "=", True)]`.
The compute method was updated by https://github.com/odoo/odoo/commit/d4252825f52a3172420dcda0ea394e42da9f8853, but the related search method was left unchanged, leading to this slight incoherence between the two methods.
opw-6422173
Forward-Port-Of: odoo/odoo#282338
Forward-Port-Of: odoo/odoo#281997In this commit: - Ensure event ticket information is preserved during self-order processing and use the configured ticket price when recomputing order line prices. - This prevents ticket prices from being replaced by the product price after proceeding to payment and keeps the amounts consistent across the payment page. Task:6375899 Forward-Port-Of: odoo/odoo#275645
Original PR description
In this commit: - Ensure event ticket information is preserved during self-order processing and use the configured ticket price when recomputing order line prices. - This prevents ticket prices from being replaced by the product price after proceeding to payment and keeps the amounts consistent across the payment page. Task:6375899 Forward-Port-Of: odoo/odoo#275645
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError: maximum recursion depth exceeded`. **Cause:** - In a previous commit (3e32d7b9eace62dfa7334009707a93967906c726) aimed at fixing stale analytic distribution totals, the assignment loop in `_compute_discount_allocation_needed` was changed from iterating over `self` to `self.move_id.line_ids`. -
Original PR description
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError:…
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError: maximum recursion depth exceeded`. **Cause:** - In a previous commit (3e32d7b9eace62dfa7334009707a93967906c726) aimed at fixing stale analytic distribution totals, the assignment loop in `_compute_discount_allocation_needed` was changed from iterating over `self` to `self.move_id.line_ids`. - While this ensured all lines generated updated distribution ratios, it violated the compute logic: assigning values to records outside the current compute batch (`self`). - By executing `line.discount_allocation_dirty = True` on external sibling lines, the method forced the ORM to trigger out-of-band `write()` calls. These writes re-triggered dependency checks (`_field_will_change`), which invoked the compute method again, leading to a recursive loop. **Fix:** 1. Revert the assignment iteration back to `for line in self:`. 2. To preserve the intention of the previous commit (ensuring all lines recompute their shared distribution pool when one line changes), modify the method's `@api.depends` to be `move_id.line_ids.discount` and `move_id.line_ids.analytic_distribution`. By declaring these relational dependencies, modifying a single line now batches all sibling lines into `self` from the start. This allows the lines to synchronize properly without triggering new ORM writes, eliminating the recursion. opw-6451854 Forward-Port-Of: odoo/odoo#282050
compute_date_from_to calls compute_work_entry_type_id calls _compute_duration, orm finds a cyclic dep so it doesn't call compute_date_from_to again) This commit makes _compute_work_entry_type_id an onchange method instead since it's only needed for the view and adds work_entry_type_id as dependency of date_from_to Task-6222907
Original PR description
compute_date_from_to calls compute_work_entry_type_id calls _compute_duration, orm finds a cyclic dep so it doesn't call compute_date_from_to again) This commit makes _compute_work_entry_type_id an onchange method instead since it's only needed for the view and adds work_entry_type_id as dependency of date_from_to Task-6222907
Miscellaneous changes
Allow resetting sent moves to draft. Ensures a rectificative flow exists or is created. Task: 6273211 Forward-Port-Of: odoo/odoo#273013
Original PR description
Allow resetting sent moves to draft. Ensures a rectificative flow exists or is created. Task: 6273211 Forward-Port-Of: odoo/odoo#273013
15 changes
Enhancements to existing features
Belgian Blackbox point-of-sale flows now move on without waiting for receipt printing when an order is canceled. This reduces delays for staff and keeps checkout operations smoother when cancellations happen.
Original PR description
Stop awaiting the receipt print in the POS for canceled orders task-id: 6425204 community PR: https://github.com/odoo/odoo/pull/280002 Forward-Port-Of: odoo/enterprise#127818
Resolved issues and error corrections
Accounting users in Spanish companies can now export VAT record books even when the report includes Point of Sale transactions. The report securely reads the needed POS data internally, avoiding access errors without requiring extra POS permissions.
Original PR description
Steps to reproduce:
- With an ES Company
- Open a POS session, add product with tax and pay
- As a user with only accounting access
- Go to Accouting > Reporting > Tax report
- Select Generic Tax report
- Print "VAT record Books"
Issue:
An AccessError will raise
```
Access Error
You are not allowed to access 'Point of Sale Session' (pos.session) records.
This operation is allowed for the following groups:
- Point of Sale/User
Contact your administrator to request access if necessary.
```
Analysis:
Vat Record Books handler for POS needs to read pos.session and pos.order records. Currently, the action is performed with the rights of the user running the report, so accounting-only user face an error.
As POS records are only read internally to build the report, we add sudo call to get the data.
opw-5862529
Forward-Port-Of: odoo/enterprise#126590
Forward-Port-Of: odoo/enterprise#125980Project settings no longer show the Time Management section unless the Timesheets app is installed. This prevents users from seeing irrelevant settings and keeps project configuration clearer.
Original PR description
**Steps to reproduce:** - Install the project_forecast module. - Go to Projects -> Open the settings of any project (create one if none exist) -> Settings. - You will see the Time Management section. **Issue:** The project_forecast module was forcefully setting the invisible attribute of group_time_managment to 0. This caused the group to remain visible at all times, even when the Timesheets app was not installed. **Fix:** Remove the forced attribute setting from the project_view. The visibility is already properly managed by the hr_timesheet module, and project_forecast does not depend on timesheet_grid or hr_timesheet. **Merge Till - SaaS-19.1 only, then from SaaS-19.2 : https://github.com/odoo/enterprise/pull/121454** task-6195716 Forward-Port-Of: odoo/enterprise#127802 Forward-Port-Of: odoo/enterprise#121565
Odoo now recognizes five additional response codes introduced by Chile's tax authority for supplier electronic documents. This prevents affected supplier documents from getting stuck during processing and keeps the acceptance/claim workflow aligned with the latest SII rules.
Original PR description
**Before this PR:** After the implementation of Resolution 161 of November 13th 2025, SII responses included keys not supported by the current l10n_cl_edi implementation. This resulted in supplier DTEs not being processed as they were before the change, because the five new keys were not found in Odoo's current `l10n_cl_claim` field, causing that the documents with these responses, were kept in a loop not solved. **After this PR:** The five new values from the resolution, along with their translations, were added to the selector field, fixing the process flow. **SII Reference:** https://www.sii.cl/normativa_legislacion/resoluciones/2025/reso161.pdf (see Event Code, page 5) Forward-Port-Of: odoo/enterprise#121833
This fix removes ambiguity in how Studio approval rule conditions are interpreted. Approval rules with no specific condition now correctly apply to all relevant records, reducing the risk of missed or inconsistent approvals.
Original PR description
Before this commit, there was an ambiguity with the usage of filtered_domain ie ``` self.assertTrue(record.filtered_domain(False)) self.assertFalse(record.filtered_domain(Domain(False))) ``` This is because in that case the API of filtered_domain was not respected After this commit, there is no ambiguity as we cast to a Domain the value we obtain from the rule: - False or None: all records should be impacted by the rule => Domain(True) - otherwise, let the domain do its job opw-6431607 Forward-Port-Of: odoo/enterprise#127676
This fix prevents old, discarded timer views from continuing to update running task or ticket timers in the background. Users should no longer see timers jump, show negative values, or record incorrect elapsed time after stopping, restarting, and reloading a timed record.
Original PR description
A running task/ticket timer can sometimes stutter, show negative values, and track the wrong elapsed time. ### Steps to reproduce On a record with a timesheet timer (for example, a Field Service…
A running task/ticket timer can sometimes stutter, show negative values, and track the wrong elapsed time. ### Steps to reproduce On a record with a timesheet timer (for example, a Field Service task): 1. Start the timer and let it run for about 15-20 seconds. 2. Stop it and confirm the dialog. 3. Start it again. This creates a new `timer_start`. 4. Reload the page. The timer starts jumping every second between two different values. As it keeps running, it can even show negative values such as `00:00:-57`. If the problem does not appear right away, repeat steps 2-4 a few times. It usually shows up after a few stop/start/reload cycles. ### Cause The timer shown in the button bar is the `timer_start_field` widget. It starts a `setInterval` that updates a shared `TimerReactive` object once per second. While a form is loading, Odoo renders it several times in a row (for example: a first render, another when the chatter is loaded, and another when the record data comes back from the server). Rendering a form builds all of its fields to produce the display, so each of these renders creates its own `timer_start_field`. Odoo keeps and mounts only the render that ends up on screen; the earlier ones are thrown away before being mounted. The interval is started while the field renders, from the record observer set up in `setup`, before the field is mounted. So the fields that are later thrown away also start an interval. Those intervals keep running for the rest of the session. Each one updates the same shared `TimerReactive` object using the `timer_start` it was created with. As long as every instance has the same `timer_start`, they all write the same value and the problem stays hidden. After the timer is stopped and started again, the old instances keep the old `timer_start` while the mounted one uses the new one. Every second they overwrite each other's value, so the timer jumps between two different elapsed times. When the instance with the newer `timer_start` writes right after one with an older start, it tries to show a smaller elapsed time than what is already there, and the subtraction in `TimerReactive` produces a negative number of seconds. ### Fix Move the per-second timer update into a `useEffect`. The effect only runs after the field is mounted, and Owl automatically cleans it up when the field is unmounted or when `timer_start` or `timer_pause` change. This means fields that are destroyed before they are mounted never start an interval, so only the mounted field updates the shared timer. `onRecordChange` no longer starts or stops the interval. It only updates the displayed timer value to match the current record. opw-6209405 Forward-Port-Of: odoo/enterprise#126242
Recurring products sold as one-time purchases are now treated as regular sales in stock forecasts. This prevents inventory planning from showing endless future demand for orders that are not subscriptions, improving replenishment accuracy.
Original PR description
When a recurring product (with `allow_one_time_sale = True`) is sold as a one-time purchase (no subscription plan), the stock forecast report and replenishment logic incorrectly treat it as an active…
When a recurring product (with `allow_one_time_sale = True`) is sold as a one-time purchase (no subscription plan), the stock forecast report and replenishment logic incorrectly treat it as an active subscription. This results in infinite projected future outgoing moves for standard sales. This occurs because the logic only checks if `recurring_invoice` is True on the product, ignoring whether the parent order actually has a `plan_id`. This commit fixes the issue by: 1. Updating `_get_stock_subscription_lines` in `sale.order.line` to filter out lines using `_subscription_is_one_time_sale()`. 2. Updating the domains in `stock.forecasted_product_product` to require `order_id.plan_id != False` for subscription forecasts, while correctly routing one-time sales (`order_id.plan_id == False`) back to the standard sale domain. 3. Adapting existing tests to verify that one-time sales do not generate future subscription stock forecasts. Task-6193648 Forward-Port-Of: odoo/enterprise#116889
The IoT device list now opens device details using the standard navigation behavior, restoring pagination after it was previously broken. This helps users browse and manage larger sets of IoT devices without getting stuck or losing access to later pages.
Original PR description
Since #72351, the pagination on IoT devices was broken due to how we were getting to the full device form when clicking on a record. We now change the override to use the existing method from the framework `switchToForm` which handles it better. opw-6058532 Forward-Port-Of: odoo/enterprise#126562 Forward-Port-Of: odoo/enterprise#126486
Dutch Digipoort tax return status updates now continue even if one record is missing its related closing entry. This prevents a single incomplete record from blocking status processing for other tax returns.
Original PR description
The `l10n_nl_reports_sbr_status_info` contains the `l10n_nl_reports_sbr.status.service` class. The class is responsible for fetching the status of sent Digipoort tax returns. The status is then posted as a chatter message to the tax return's closing entry. Issues can arise when one of the status service records is, for whatever reason, missing a closing entry. In such case, the message cannot be posted, resulting in an exception being raised. Since the records are processed in a loop without a try-catch, this causes the whole action to fail. This can lead to one broken record effectively shutting down the whole module's functionality. This PR adds some if-else checks to gracefully handle the case where the closing entry is missing. Related tickets: opw-5901446 and opw-6410082 Forward-Port-Of: odoo/enterprise#127844 Forward-Port-Of: odoo/enterprise#125996
The Field Service product catalog now gives more room to the unit of measure column, making product details easier to read when adding items from a task. This aligns the Enterprise interface with the related Community update and reduces layout issues for users.
Original PR description
Steps to produce: --- - Install `Field service` module. - Create a task and open it. - From the task open the catalog from smart button. Update the Product Catalog UI to match the Community PR changes. community PR: https://github.com/odoo/odoo/pull/267118 opw-6253382 --- Forward-Port-Of: odoo/enterprise#121139
## Issue When filtering projects using the "Timesheets >100%" filter, some projects with negative remaining hours (and with their `is_project_overtime` field set to True) won't be displayed, even though their expected hours are completed. This happens with projects which have tasks set to the "Done" or "Cancelled" state. The timesheets entries in those tasks are not taken into account when searching using the "Timesheets >100%" filter. ## Steps to reproduce 1. Install *Task Logs* (`hr_ti
Original PR description
## Issue When filtering projects using the "Timesheets >100%" filter, some projects with negative remaining hours (and with their `is_project_overtime` field set to True) won't be displayed, even…
## Issue
When filtering projects using the "Timesheets >100%" filter, some projects with negative remaining hours (and with their `is_project_overtime` field set to True) won't be displayed, even though their expected hours are completed.
This happens with projects which have tasks set to the "Done" or "Cancelled" state. The timesheets entries in those tasks are not taken into account when searching using the "Timesheets >100%" filter.
## Steps to reproduce
1. Install *Task Logs* (`hr_timesheet`)
2. Create a Project P (with Timehseets enabled)
3. Set the allocated hours of the project to 3:00 (3 hours)
4. Create two tasks:
- T1: State "In progress", and one timesheet entry of 2:00 (2 hours)
- T2: State "Done", and one timesheet entry of 2:00 (2 hours)
5. Back to the project view, set the filter to "Timesheets >100%"
6. **Project P is not shown, even though the total time spent on the project is 4 hours, completing the allocated hours set on the project.**
## Cause
The `_search_is_project_overtime` method filters out the tasks in "closed" states (Done/Cancelled) when computing the amount of time spent on the project.
https://github.com/odoo/odoo/blob/126b5bdd1e85771549198976f8570cd2ff167608/addons/hr_timesheet/models/project_project.py#L103-L114
This does not match with the behavior of the `_compute_is_project_overtime`, which does not take into account the state of the tasks to determine the value of the field:
https://github.com/odoo/odoo/blob/126b5bdd1e85771549198976f8570cd2ff167608/addons/hr_timesheet/models/project_project.py#L85-L94
This leads to a confusing behavior, where a project can have its `is_project_overtime` field set to True, but will still not be shown when using the "Timsheets >100%", even though that filter is defined as `[("is_project_overtime", "=", True)]`.
The compute method was updated by https://github.com/odoo/odoo/commit/d4252825f52a3172420dcda0ea394e42da9f8853, but the related search method was left unchanged, leading to this slight incoherence between the two methods.
opw-6422173
Forward-Port-Of: odoo/odoo#282338
Forward-Port-Of: odoo/odoo#281997On highly loaded runbots, waiting a single animation frame might not be sufficient for the table deselection to be available in the DOM after a key press. This commit waits for the table to be deselected before continuing the test. runbot-944722 Forward-Port-Of: odoo/odoo#282008
Original PR description
On highly loaded runbots, waiting a single animation frame might not be sufficient for the table deselection to be available in the DOM after a key press. This commit waits for the table to be deselected before continuing the test. runbot-944722 Forward-Port-Of: odoo/odoo#282008
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError: maximum recursion depth exceeded`. **Cause:** - In a previous commit (3e32d7b9eace62dfa7334009707a93967906c726) aimed at fixing stale analytic distribution totals, the assignment loop in `_compute_discount_allocation_needed` was changed from iterating over `self` to `self.move_id.line_ids`. -
Original PR description
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError:…
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError: maximum recursion depth exceeded`. **Cause:** - In a previous commit (3e32d7b9eace62dfa7334009707a93967906c726) aimed at fixing stale analytic distribution totals, the assignment loop in `_compute_discount_allocation_needed` was changed from iterating over `self` to `self.move_id.line_ids`. - While this ensured all lines generated updated distribution ratios, it violated the compute logic: assigning values to records outside the current compute batch (`self`). - By executing `line.discount_allocation_dirty = True` on external sibling lines, the method forced the ORM to trigger out-of-band `write()` calls. These writes re-triggered dependency checks (`_field_will_change`), which invoked the compute method again, leading to a recursive loop. **Fix:** 1. Revert the assignment iteration back to `for line in self:`. 2. To preserve the intention of the previous commit (ensuring all lines recompute their shared distribution pool when one line changes), modify the method's `@api.depends` to be `move_id.line_ids.discount` and `move_id.line_ids.analytic_distribution`. By declaring these relational dependencies, modifying a single line now batches all sibling lines into `self` from the start. This allows the lines to synchronize properly without triggering new ORM writes, eliminating the recursion. opw-6451854 Forward-Port-Of: odoo/odoo#282050
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
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#281962
**Steps to reproduce** - Open any MO - Click on 'catalog' - Click on the three dots button of any product - Try to edit -> Traceback: `"product.product"."state" field is undefined.` **Cause** The `move_raw_ids` field on the MO form sets a context with `form_view_ref: 'mrp.view_mrp_stock_move_operations'`, so that editing a component's `stock.move` line in place opens that dedicated view: https://github.com/odoo/odoo/blob/20db2910d16fa6ebd07794fb9ed3fb1c8f57b0fc/addons/mrp/views/mrp_prod
Original PR description
**Steps to reproduce** - Open any MO - Click on 'catalog' - Click on the three dots button of any product - Try to edit -> Traceback: `"product.product"."state" field is undefined.` **Cause** The…
**Steps to reproduce** - Open any MO - Click on 'catalog' - Click on the three dots button of any product - Try to edit -> Traceback: `"product.product"."state" field is undefined.` **Cause** The `move_raw_ids` field on the MO form sets a context with `form_view_ref: 'mrp.view_mrp_stock_move_operations'`, so that editing a component's `stock.move` line in place opens that dedicated view: https://github.com/odoo/odoo/blob/20db2910d16fa6ebd07794fb9ed3fb1c8f57b0fc/addons/mrp/views/mrp_production_views.xml#L411-L417 The 'Catalog' button lives inside that same field, and calls `action_add_from_catalog_raw`, which delegates to the mixin's `action_add_from_catalog`: https://github.com/odoo/odoo/blob/3dd41395e2e4205fa477eb474d4a4dba0a976154/addons/product/models/product_catalog_mixin.py#L17 Which conserve the `'form_view_ref'` from context: https://github.com/odoo/odoo/blob/3dd41395e2e4205fa477eb474d4a4dba0a976154/addons/product/models/product_catalog_mixin.py#L28 and that action requests its form view with `view_id=False`: https://github.com/odoo/odoo/blob/3dd41395e2e4205fa477eb474d4a4dba0a976154/addons/product/models/product_catalog_mixin.py#L25 Which will try to load `view_mrp_stock_move_operations`, since: https://github.com/odoo/odoo/blob/3dd41395e2e4205fa477eb474d4a4dba0a976154/odoo/addons/base/models/ir_ui_view.py#L3002-L3005 `_get_view` never checks that the resolved view's `model` matches the model it was asked for, so it returns `view_mrp_stock_move_operations`for the `product.product` model: https://github.com/odoo/odoo/blob/3dd41395e2e4205fa477eb474d4a4dba0a976154/addons/mrp/views/stock_move_views.xml#L41 https://github.com/odoo/odoo/blob/3dd41395e2e4205fa477eb474d4a4dba0a976154/addons/stock/views/stock_move_views.xml#L128 `state` doesn't exist on `product.product`, hence the crash. opw-6433620 Forward-Port-Of: odoo/odoo#279811
6 changes
Resolved issues and error corrections
Project settings no longer show the Time Management section unless the Timesheets app is installed. This prevents users from seeing irrelevant configuration options and keeps project settings clearer.
Original PR description
**Steps to reproduce:** - Install the project_forecast module. - Go to Projects -> Open the settings of any project (create one if none exist) -> Settings. - You will see the Time Management section. **Issue:** The project_forecast module was forcefully setting the invisible attribute of group_time_managment to 0. This caused the group to remain visible at all times, even when the Timesheets app was not installed. **Fix:** Remove the forced attribute setting from the project_view. The visibility is already properly managed by the hr_timesheet module, and project_forecast does not depend on timesheet_grid or hr_timesheet. **Merge Till - SaaS-19.1 only, then from SaaS-19.2 : https://github.com/odoo/enterprise/pull/121454** task-6195716 Forward-Port-Of: odoo/enterprise#127802 Forward-Port-Of: odoo/enterprise#121565
This fix prevents an error from appearing in Belgian Blackbox point-of-sale sessions when employee login is turned off. The cashier status icon will no longer behave as clickable unless the cashier selector is actually available, improving reliability for affected PoS users.
Original PR description
Steps: - Install pos_blackbox_be. - Configure a PoS with Blackbox Belgium enabled and `Log in with Employees` disabled. - Open a PoS session and click exactly on the session status circle on the cashier icon. Issue: - A traceback is raised with the following error: `this.cashierSelector is not a function`. Cause: - Installing pos_blackbox_be makes the cashier icon appear clickable by adding the `pe-auto` class to the cashier icon's session status circle, even when `Log in with Employees` is disabled. In this configuration, the cashier selector is unavailable, causing the click handler to fail. Fix: - Remove the unnecessary `pe-auto` class from the cashier icon's session status circle so it is only clickable when employee login is enabled. Task-6369404
Acerta payroll exports now include weekend days when an employee's qualifying leave overlaps a weekend. This ensures Belgian payroll reports match Acerta's expected format and reduces missing leave information in exported files.
Original PR description
## Steps to reproduce: - Install l10n_be_hr_payroll_acerta - Create an employee in a belgian company - Create a sick time off for the created employee that overlaps with a weekend - Export acerta report for the employee - Notice the weekend that overlaps with the time off is not present in the report ## Cause: While exporting the report file we only loop over the created work entries' dates and since weekends doesn't have work entries we don't consider them in the report. ## Fix: When generating the line of a leave's start date we check if the leave overlaps with a WE, we fetch the WE's date and we generate a line for each day of the WE. According to Acerta this is the correct behavior for their reports for specific types of leaves. **opw-6313534** Forward-Port-Of: odoo/enterprise#127596 Forward-Port-Of: odoo/enterprise#124500
The Field Service product catalog now gives more space to the unit of measure column, matching the related Community update. This prevents cramped or cut-off unit information when adding products from a task, making the catalog easier to read.
Original PR description
Steps to produce: --- - Install `Field service` module. - Create a task and open it. - From the task open the catalog from smart button. Update the Product Catalog UI to match the Community PR changes. community PR: https://github.com/odoo/odoo/pull/267118 opw-6253382 --- Forward-Port-Of: odoo/enterprise#121139
Fixed an issue in Documents where clicking inside the "Search More..." dialog while editing document details would unexpectedly close the dialog. Users can now search, sort, and select related contacts or customers without losing their current document selection.
Original PR description
Steps to reproduce: 1. Install Documents 2. In the Documents list view, select a document to display the inspector. 3. Edit a field such as Owner or Customer which uses a Many2one widget. 4. In the…
Steps to reproduce: 1. Install Documents 2. In the Documents list view, select a document to display the inspector. 3. Edit a field such as Owner or Customer which uses a Many2one widget. 4. In the field dropdown, click "Search More..." to open a modal dialog. 5. Click inside the "Search More..." modal (e.g., to sort columns or resize headers). Issue: - The modal dialog immediately closes, and the contact cannot be selected. Root cause: - When an inspector field is edited, the record row is put into edit mode. While in edit mode, the documents list renderer listens for global clicks. Clicking inside the "Search More..." modal dialog targets elements that have `.o_list_renderer` (since the modal dialog renders a list view). Because the click target is within a list renderer but is not a document row, `DocumentsListRenderer.onGlobalClick` executes and clears the selection of the main list view. Clearing the selection unmounts the edited field in the inspector, thereby destroying the modal dialog stack. Solution: - Modify DocumentsListRenderer.onGlobalClick to scope click handling to the current Documents list renderer. Ignore clicks outside this.root.el, so interactions in nested UI such as Search More... do not clear the main selection and destroy the inspector field. opw-6253360 Forward-Port-Of: odoo/enterprise#127557 Forward-Port-Of: odoo/enterprise#119262
When bank synchronization finds no new transactions, Odoo now opens an empty reconciliation view instead of showing transactions from all journals. This prevents confusion for businesses with multiple bank journals and matches the behavior in newer Odoo versions.
Original PR description
Currently, when we fetch zero transaction for an online account through bank synchronization, we open the bank reconciliation view with an empty domain, thus showing every transactions from every journals. This is confusing for the user if they have several bank journals. This commit now shows an empty bank reconciliation widget if no transactions are fetched. Additionally, this aligns with how it works in Odoo 19. [opw-6384718](https://www.odoo.com/mail/message/1138570272) Forward-Port-Of: odoo/enterprise#127833
4 changes
Resolved issues and error corrections
When online bank synchronization finds no new transactions, the reconciliation screen now stays empty instead of showing transactions from all bank journals. This prevents users with multiple bank journals from seeing unrelated transactions and reduces confusion.
Original PR description
Currently, when we fetch zero transaction for an online account through bank synchronization, we open the bank reconciliation view with an empty domain, thus showing every transactions from every journals. This is confusing for the user if they have several bank journals. This commit now shows an empty bank reconciliation widget if no transactions are fetched. Additionally, this aligns with how it works in Odoo 19. [opw-6384718](https://www.odoo.com/mail/message/1138570272) Forward-Port-Of: odoo/enterprise#127833
This change sanitizes some post data before allowing the post, making sure the data received by `message_post` is clean based on the current user. part of task-6452761 Forward-Port-Of: odoo/odoo#280894
Original PR description
This change sanitizes some post data before allowing the post, making sure the data received by `message_post` is clean based on the current user. part of task-6452761 Forward-Port-Of: odoo/odoo#280894
This change cleans up the requested data from `/mail/thread/data` route, ensuring it aligns with what is actually needed depending on the user and thread. part of task-6452761 Forward-Port-Of: odoo/odoo#280713
Original PR description
This change cleans up the requested data from `/mail/thread/data` route, ensuring it aligns with what is actually needed depending on the user and thread. part of task-6452761 Forward-Port-Of: odoo/odoo#280713
Miscellaneous changes
Before this commit: --- Due to changes introduced in the Odoo 18 [`expression.combine`](https://github.com/odoo/odoo/pull/160979/changes#diff-fa4d9268d6e65e19aebec81c46038f0e496b91142588ed4d1c1bce7ff2338f2c) function, an empty domain ([]) is evaluated as True. As a result, [`attendance_domain`](https://github.com/odoo/odoo/blob/1073447ba56e2cc69177ee0ac8eab36d63d907bc/addons/hr_attendance/models/hr_attendance.py#L378), which is initially [], is [OR-ed](https://github.com/odoo/odoo/blob/10734
Original PR description
Before this commit: --- Due to changes introduced in the Odoo 18…
Before this commit:
---
Due to changes introduced in the Odoo 18 [`expression.combine`](https://github.com/odoo/odoo/pull/160979/changes#diff-fa4d9268d6e65e19aebec81c46038f0e496b91142588ed4d1c1bce7ff2338f2c) function, an empty domain ([]) is evaluated as True. As a result, [`attendance_domain`](https://github.com/odoo/odoo/blob/1073447ba56e2cc69177ee0ac8eab36d63d907bc/addons/hr_attendance/models/hr_attendance.py#L378), which is initially [], is [OR-ed](https://github.com/odoo/odoo/blob/1073447ba56e2cc69177ee0ac8eab36d63d907bc/addons/hr_attendance/models/hr_attendance.py#L380) with the date conditions, causing the expression True OR X to always evaluate to True.
Consequently, the date filters are never added to the domain, and the final domain becomes:
[('employee_id', '=', 28)]
This causes all attendance records for the employee to be fetched and processed, regardless of the requested date range, unnecessarily increasing computation time.
### Before the fix :
```python
(Pdb) attendance_domain = []
(Pdb) attendance_date
(datetime.datetime(2026, 8, 3, 18, 30), datetime.date(2026, 8, 4))
(Pdb) attendance_domain = OR([attendance_domain, [('check_in', '>=', attendance_date[0]), ('check_in', '<', attendance_date[0] + timedelta(hours=24)),]])
(Pdb) attendance_domain
[(1, '=', 1)]
(Pdb) attendance_domain = AND([[('employee_id', '=', emp.id)], attendance_domain])
(Pdb) attendance_domain
[('employee_id', '=', 28)]
(Pdb)
```
After this commit:
---
attendance_domain is initialized with FALSE_DOMAIN instead of an empty domain. This ensures that the OR operation correctly incorporates the date conditions, producing the expected domain and limiting the query to the relevant attendance records.
### After the fix :
```python
(Pdb) attendance_domain = expression.FALSE_DOMAIN
(Pdb) attendance_domain
((0, '=', 1),)
(Pdb) attendance_date
(datetime.datetime(2026, 8, 3, 18, 30), datetime.date(2026, 8, 4))
(Pdb) attendance_domain = OR([attendance_domain, [('check_in', '>=', attendance_date[0]), ('check_in', '<', attendance_date[0] + timedelta(hours=24)),]])
(Pdb) attendance_domain
['&', ('check_in', '>=', datetime.datetime(2026, 8, 3, 18, 30)), ('check_in', '<', datetime.datetime(2026, 8, 4, 18, 30))]
(Pdb) attendance_domain = AND([[('employee_id', '=', emp.id)], attendance_domain])
(Pdb) attendance_domain
['&', ('employee_id', '=', 28), '&', ('check_in', '>=', datetime.datetime(2026, 8, 3, 18, 30)), ('check_in', '<', datetime.datetime(2026, 8, 4, 18, 30))]
```
OPW: 6385811
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#28111514 changes
Security fixes and vulnerability patches
The Sign app now blocks users from linking a signature request to records they are not allowed to view. This prevents sensitive information from becoming visible through signed documents and aligns manual edits with existing interface restrictions.
Original PR description
Version: saas-18.3 Reported issue: 1. Marc Demo creates a sign request from a template whose fields are automatically populated from the linked record. 2. It is sent to himself. He does not have…
Version: saas-18.3 Reported issue: 1. Marc Demo creates a sign request from a template whose fields are automatically populated from the linked record. 2. It is sent to himself. He does not have access to all records of a referenced model (e.g. Sales Orders). 3. The "Linked To" (reference_doc) field is edited afterwards to point to a record the signer does not have access to. By the time it's signed, the value of that record becomes visible - so a user can, simply by changing the linked record, see the value of a record they were never authorized to access. Even a Sign Manager could link a request to a record they have no access to and later see its value through it. Issue: `reference_doc` could be set or changed to any record of any allowed model with no validation that the acting user actually has access to it. In the interface, you can only create a signature request from a record you can see, but editing `reference_doc` manually (via write(), RPC, etc.) was not held to the same rule, making it an easy way to leak information about records outside your normal access. Cause: The only restriction was cosmetic, enforced client-side by the record picker widget filtering its search results. Nothing on the server validated the value being written to `reference_doc`. Fix: `write()` now checks that the acting user has read access to the target record before allowing `reference_doc` to be set, raising a ValidationError otherwise, bringing manual edits in line with what the interface already enforces when creating a request. Forward-Port-Of: odoo/enterprise#127870 Forward-Port-Of: odoo/enterprise#127715
Enhancements to existing features
Spreadsheet pivot tables can now include calculated fields based on SQL data, expanding what users can analyze directly in spreadsheets. This improves reporting flexibility for teams that rely on pivot views to explore business data without leaving Odoo.
Original PR description
Task: 6442237 Forward-Port-Of: odoo/enterprise#126645
Belgian payroll meal voucher reports now calculate the total value correctly when vouchers are postponed. This helps payroll teams rely on more accurate reporting for employee benefits and related follow-up.
Original PR description
-Adjust the total value for meal voucher report in case of postponed meal vouchers. Forward-Port-Of: odoo/enterprise#127941
Subscription and rental portal pages now use the refreshed sale order layout, making key details such as rental dates, subscription plans, periods, and invoices easier to find in the sidebar. The update also improves status labels and action placement, creating a clearer and more consistent customer experience across related portal pages.
Original PR description
*: sale_renting, helpdesk, planning_field_service, sign Before this commit, subscription and rental portal pages still relied on the previous sale order layout, with key info (rental dates, plan, start/period, invoices,.) living in the main view as table-based blocks. This commit aligns subscriptions and rentals with the upcoming sale order portal design: rental dates, subscription plan/period and invoices are moved from the main view to the sidebar, the intro row is reworked with restyled status badges and inline metadata, sidebar actions are re-hierarchized.. requires: https://github.com/odoo/odoo/pull/264918 task-5404797
Bank statement reconciliation can now match invoices even when payment references are written with minor formatting differences, such as missing slashes. This helps reduce manual reconciliation work and improves automatic matching accuracy.
Original PR description
Before this commit, the "try_auto_reconcile" algorithm was finding moves when there was a perfect match with either the ref of a move line, the move name, the payment reference and now a sanitize version of the payment ref. For example if an invoice had SO12/1234 as the payment reference, if the statement line has a label SO121234 nothing was found. This commit will then add a new non stored computed field to sanitize the payment ref on the invoice level to help those cases task-6119841
Resolved issues and error corrections
The Helpdesk Stock ticket view now shows the Replace button even when no customer is selected. This keeps the button behavior consistent with related actions and reduces confusion for support teams handling replacements.
Original PR description
Adjust the `invisible` condition to make the button visible even if no customer is selected, for consistency with other buttons --- task-6103996 Forward-Port-Of: odoo/enterprise#124467
A background product used by the point of sale settlement process now includes the required name. This prevents domain checks from failing and helps related discount tests and workflows run reliably.
Original PR description
The dummy product `dummy_product_for_settle_stuff_variant` created by `pos_settle_due` did not have a `name`. This caused `domain.contains` to fail when evaluating product domains, preventing `test_discount_with_reward_product_domain` from running successfully. Task-6221973 Related Community: https://github.com/odoo/odoo/pull/278751
Belgian payroll reporting now allocates severance-related periods using the employee's actual departure date through the theoretical notice end date. Seniority is also calculated without including the notice period, helping produce more consistent payroll declarations across quarters.
Original PR description
- previously, the termination period was split from notice period start to actual departure date, ignoring the theoretical notice duration. Now, it correctly splits from actual departure date to theoretical end date, ensuring proper multi-quarter severance (Code 003) allocation. - Seniority calculation no longer includes the notice period, ensuring consistent results regardless of notice duration Task: 5407737
This fixes an appraisal campaign issue where managers without HR permissions could not start campaigns for employees reporting to them. It also prevents campaigns from accidentally applying to all employees when the selected employee list could not be read.
Original PR description
**Issue**: -User without hr rights is not allowed to launch campaigns for employees under him in the hierarchy. -This issue appears only in master, but it discovered another issue from 19.3, where the value for `employee_ids` was not accessed by the normal user. Thus, appraisals are created for all employees in the list, because not selecting an employee means selecting "All Employees". **Solution**: -SUDOing the read to allow the compute to see what the user has selected. Forward-Port-Of: odoo/enterprise#127391
French VAT refund declaration 3519 now includes the bank account holder's name in the account details. This helps ensure reimbursement declarations contain the required information and reduces the risk of rejected or incomplete submissions.
Original PR description
For reimbursement declarations, the name of the holder of the account is required This commits adds holder's name to the account data zone no-task-id Forward-Port-Of: odoo/enterprise#127922 Forward-Port-Of: odoo/enterprise#127554
Annotated Deferred Revenue Reports can now be exported to XLSX without triggering a server error. This prevents interruptions for accounting users who rely on spreadsheet exports for review and reporting workflows.
Original PR description
**Steps to reproduce:** * Install the **Accounting** module. * Unhide the **Start Date** and **End Date** fields on invoice lines. * Create and post a customer invoice with deferred dates. * Go to…
**Steps to reproduce:** * Install the **Accounting** module. * Unhide the **Start Date** and **End Date** fields on invoice lines. * Create and post a customer invoice with deferred dates. * Go to **Accounting → Reports → Deferred Revenue Report**. * Add an annotation to a deferred revenue line by clicking the **annotate** from three dots next to the account. * Export the report in **XLSX** format. **Observed behavior:** * The export fails with a server error: `UnboundLocalError: cannot access local variable 'annotations_x_offset' where it is not associated with a value` **Cause:** * The variable `annotations_x_offset` is assigned inside the `for header_level_index, header_level in enumerate(options['column_headers'])` loop, which writes the "Annotations" column header for each header level. * The Deferred Revenue Report produces an empty `column_headers` list, so the loop body never executes and `annotations_x_offset` is never assigned. * When the code later tries to write annotation data for each report line, it references the unassigned variable, causing Python to raise `UnboundLocalError`. **Fix:** * Introduce a boolean flag `annotations_header_written = False` before the header loop to explicitly track whether the "Annotations" column header has already been written. * Inside the header loop, set `annotations_header_written = True` after writing the header. * After writing all individual column headers (where `x_offset` already points to the first free column after all data columns), add a fallback: if `report_annotations` is set but `annotations_header_written` is still `False`, assign `annotations_x_offset` from the current `x_offset` and write the "Annotations" header. opw-6354473 Forward-Port-Of: odoo/enterprise#127840 Forward-Port-Of: odoo/enterprise#122768
Fixed an issue where switching to a pivot view through the AI agent could cause the view to crash or open without selected measures. The AI adjustments now wait until the pivot view is ready, preserving default measures when none are specifically requested.
Original PR description
When the AI agent switched from another view to a pivot view, the pivot view could crash or open without any active measures. The AI controller patch applies the agent's adjustments upon receiving…
When the AI agent switched from another view to a pivot view, the pivot view could crash or open without any active measures. The AI controller patch applies the agent's adjustments upon receiving the `APPLY_AI_ADJUST_MODEL` bus event. However, the event could be processed while the pivot model was still executing `_loadData()`. In that case, the following sequence occurred: * `_loadData()` started and awaited. * The controller patch was executed. * The patch called `toggleMeasures()`. * `toggleMeasures()` waited for `_loadData()` to complete. * `_loadData()` finished and updated the metadata with the available measures. * `toggleMeasures()` resumed and wrote back the metadata snapshot it had taken before waiting. Since `toggleMeasures()` operates on a snapshot of the metadata, the measures populated by `_loadData()` were lost when the snapshot replaced the current metadata, leaving the pivot model without its `measures` metadata and causing the view to crash. Prevent this race condition by waiting for the pivot model initialization to complete before applying the AI adjustments. Also preserve the default active measures when the AI agent does not explicitly request any measures instead of clearing them and opening an empty pivot view. task-6384368
The Brazilian Avalara sales localization now correctly requires the related external tax sales component during installation. This prevents setup failures when installing the app manually with automatic dependency installation disabled.
Original PR description
Installing l10n_br_avatax_sale with --skip-auto-install fails for having the incorrect dependencies, fields such as l10n_br_goods_operation_type_id don't exist without pulling in l10n_br_avatax_sale. Issue exists back in 18.0 but removing the fields defeats the purpose of the feature provided by the module and making the change in master due to stable policy. odoo/odoo-bin --addons-path enterprise,odoo/addons,odoo/odoo/addons,design-themes -d oes_runbot --stop-after-init --log-level=test --max-cron-threads=0 -i l10n_br_avatax_sale --skip-auto-install runbot-237866
Code cleanup and technical improvements
This update removes duplicated test code in the Documents and Documents Spreadsheet areas. It does not change business features, but helps keep future maintenance safer and more efficient.
Original PR description
Task-6443853
5 changes
Resolved issues and error corrections
The Journal Audit review now removes group headers once all items inside them have been reviewed. This keeps the list tidy and avoids showing confusing empty groups with a zero count.
Original PR description
Currently, in the Journal Audit review, when a user clicks the "Review" button on a grouped list (e.g., a bill), the line item successfully disappears, but the group header remains visible with a count of (0). This leaves unnecessary empty headers cluttering the view. This introduces a custom `DynamicGroupList` that intercepts the data payload in `_setData` and drops any groups with a count of 0. JS tests added for coverage. Task-6377569
The product catalog opened from Field Service tasks now gives more space to the unit of measure column. This makes product information easier to read and aligns the Enterprise interface with the related Community update.
Original PR description
Steps to produce: --- - Install `Field service` module. - Create a task and open it. - From the task open the catalog from smart button. Update the Product Catalog UI to match the Community PR changes. community PR: https://github.com/odoo/odoo/pull/267118 opw-6253382 --- Forward-Port-Of: odoo/enterprise#121139
Project Forecast no longer shows the Time Management section in project settings unless the Timesheets app manages that section. This avoids confusing users with timesheet-related options when the Timesheets app is not installed.
Original PR description
**Steps to reproduce:** - Install the project_forecast module. - Go to Projects -> Open the settings of any project (create one if none exist) -> Settings. - You will see the Time Management section. **Issue:** The project_forecast module was forcefully setting the invisible attribute of group_time_managment to 0. This caused the group to remain visible at all times, even when the Timesheets app was not installed. **Fix:** Remove the forced attribute setting from the project_view. The visibility is already properly managed by the hr_timesheet module, and project_forecast does not depend on timesheet_grid or hr_timesheet. **Merge Till - SaaS-19.1 only, then from SaaS-19.2 : https://github.com/odoo/enterprise/pull/121454** task-6195716 Forward-Port-Of: odoo/enterprise#127802 Forward-Port-Of: odoo/enterprise#121565
This change updates internal subscription-related tests so they remain aligned with related platform changes. It helps maintain reliability of subscription workflows without changing what business users see or do.
Original PR description
See also: - https://github.com/odoo/odoo/pull/280403
The returns kanban view now supports using the up and down arrow keys to move through return selections. This prevents an error that could interrupt users when navigating returns with the keyboard, making the process smoother and more reliable.
Original PR description
In returns kanban view, a traceback occurs when pressing down. Fix this by adding the support for up/down keyboard navigation for returns selection. task-6281033
7 changes
Enhancements to existing features
Allow resetting sent moves to draft. Ensures a rectificative flow exists or is created. Allow to create an empty rectificative report (if no more invoices to report after being reset to draft). Task: 6273211 Backport of https://github.com/odoo/odoo/commit/801051138621d884ca53324a1befb6de47d83306 This commit also makes minor changes that where done in the 18+ forward ports but not in the 18.0 branch itself (removing 'l10n_fr_pdp_bypass_draft_check' in tests and correcting one comment). F
Original PR description
Allow resetting sent moves to draft. Ensures a rectificative flow exists or is created. Allow to create an empty rectificative report (if no more invoices to report after being reset to draft). Task: 6273211 Backport of https://github.com/odoo/odoo/commit/801051138621d884ca53324a1befb6de47d83306 This commit also makes minor changes that where done in the 18+ forward ports but not in the 18.0 branch itself (removing 'l10n_fr_pdp_bypass_draft_check' in tests and correcting one comment). Forward-Port-Of: odoo/odoo#282260
Resolved issues and error corrections
When an online bank sync finds no new transactions, the reconciliation screen now stays empty instead of showing transactions from all bank journals. This prevents confusion for users managing multiple bank accounts and keeps the experience consistent with newer Odoo versions.
Original PR description
Currently, when we fetch zero transaction for an online account through bank synchronization, we open the bank reconciliation view with an empty domain, thus showing every transactions from every journals. This is confusing for the user if they have several bank journals. This commit now shows an empty bank reconciliation widget if no transactions are fetched. Additionally, this aligns with how it works in Odoo 19. [opw-6384718](https://www.odoo.com/mail/message/1138570272) Forward-Port-Of: odoo/enterprise#127833
This fixes an issue where files sent through WhatsApp could arrive empty when they were stored in cloud storage. WhatsApp now receives a valid link for cloud-stored files, while locally stored files continue to be handled as before.
Original PR description
WhatsApp attachments were delivered as empty (0 byte) files when they were stored through the cloud_storage module. ### Steps to reproduce 1. Install and set up whatsapp and a cloud storage module (e.g. cloud_storage_google). 2. Send a file through WhatsApp. 3. The recipient receives an empty file. ### Cause A cloud stored attachment keeps only a reference to its remote data, so its raw field holds no bytes. The integration uploaded those empty bytes to WhatsApp. ### Fix Use the attachment HTTP stream to generate a long-lived cloud storage URL and pass it to WhatsApp as the media link. Pass ordinary remote attachment URLs directly, and keep uploading local attachment bytes as before. opw-5424132 Related Community PR: odoo/odoo#246443
Sales orders in Mexican localization now exclude CFDI-cancelled down payment invoices when calculating down payment totals. This prevents cancelled and replacement invoices from being counted together, keeping order amounts accurate without manual correction.
Original PR description
### Steps to reproduce the issue: 1. Download Sales, accounting and l10n_mx 2. Go to settings and be sure that in electonic invoicing the PAC is set on Quadrum and the Testing box is activated (no…
### Steps to reproduce the issue: 1. Download Sales, accounting and l10n_mx 2. Go to settings and be sure that in electonic invoicing the PAC is set on Quadrum and the Testing box is activated (no username and no password needed) 4. Create a SO quotation with a product and confirm it 5. Create a downpayment invoice for that quotation and be sure that the Payment way is selected 6. Send to CFDI 7. Click Request Cancel button and select reason 01 8. Then click create replacement invoice, confirm the new one and send it to CFDI 9. Go back to the first invoice created and click request cancel (you will have only 1 choice in the dropdown menu) and confirm it 10. See that in the CFDI page the request is sent 11. Wait 7 min and and then in the CFDI page click "retry" in the line of Cancel in Error state 12. Invoice is cancelled ### Issue before this commit: In the SO we can now see that the down payment invoice amount is still considering both the invoices even if the first one has been canceled. After the original downpayment invoice is successfully cancelled on the SAT side, the Sales Order continues to consider both the original and the replacement downpayment invoices. This results in the Downpayment line on the Sales Order showing a price_unit that is the sum of the two downpayments. ### Cause of the issue: The _compute_price_unit method on the sale.order.line model does not take the Mexican CFDI state (l10n_mx_edi_cfdi_state) into consideration. It only checks if the associated invoices are in a posted state. During the SAT cancellation flow for downpayments, the accounting move often remains in a posted state because of its reconciled payment (the system silently passes the UserError triggered during the accounting cancellation). Since both the cancelled original invoice and the replacement invoice remain posted at the accounting level, the _compute_price_unit logic sums both of them. https://github.com/odoo/odoo/blob/cbabc49d8004b32be12d834dd370672815087a2b/addons/sale/models/sale_order_line.py#L561-L562 ### Reason to introduce the fix: To ensure that Sales Orders accurately reflect the legal and financial reality for Mexican operations without requiring manual adjustments by the user. opw-6334903
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError: maximum recursion depth exceeded`. **Cause:** - In a previous commit (3e32d7b9eace62dfa7334009707a93967906c726) aimed at fixing stale analytic distribution totals, the assignment loop in `_compute_discount_allocation_needed` was changed from iterating over `self` to `self.move_id.line_ids`. -
Original PR description
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError:…
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError: maximum recursion depth exceeded`. **Cause:** - In a previous commit (3e32d7b9eace62dfa7334009707a93967906c726) aimed at fixing stale analytic distribution totals, the assignment loop in `_compute_discount_allocation_needed` was changed from iterating over `self` to `self.move_id.line_ids`. - While this ensured all lines generated updated distribution ratios, it violated the compute logic: assigning values to records outside the current compute batch (`self`). - By executing `line.discount_allocation_dirty = True` on external sibling lines, the method forced the ORM to trigger out-of-band `write()` calls. These writes re-triggered dependency checks (`_field_will_change`), which invoked the compute method again, leading to a recursive loop. **Fix:** 1. Revert the assignment iteration back to `for line in self:`. 2. To preserve the intention of the previous commit (ensuring all lines recompute their shared distribution pool when one line changes), modify the method's `@api.depends` to be `move_id.line_ids.discount` and `move_id.line_ids.analytic_distribution`. By declaring these relational dependencies, modifying a single line now batches all sibling lines into `self` from the start. This allows the lines to synchronize properly without triggering new ORM writes, eliminating the recursion. opw-6451854 Forward-Port-Of: odoo/odoo#282050
### Issue before this commit: When creating a fixed-amount down payment on a sale order containing a fixed tax alongside percentage taxes, the invoiced down payment amount did not match the amount configured by the user. ### Steps to reproduce the issue: 1. Download Sales 2. Create a new tax with Tax Computation as Fixed, amount 8$ and a new tax group name 3. Create a sale order, set 1000$ as price, insert 15% tax and the new tax, confirm it 4. Click 'Create invoice' and create a downpay
Original PR description
### Issue before this commit: When creating a fixed-amount down payment on a sale order containing a fixed tax alongside percentage taxes, the invoiced down payment amount did not match the amount…
### Issue before this commit: When creating a fixed-amount down payment on a sale order containing a fixed tax alongside percentage taxes, the invoiced down payment amount did not match the amount configured by the user. ### Steps to reproduce the issue: 1. Download Sales 2. Create a new tax with Tax Computation as Fixed, amount 8$ and a new tax group name 3. Create a sale order, set 1000$ as price, insert 15% tax and the new tax, confirm it 4. Click 'Create invoice' and create a downpayment with fixed amount of 500$ 5. See that the amount of the downpayment are incorrect. In particular the untaxed amount is 431.78$ instead of 434.78$ and the tax amount is 68.22 instead of 65.22$ ### Cause of the issue: Fixed taxes are not proratable (their amount doesn't scale with price), so the down payment line generation explicitly excludes them when building each down payment line. However, the ratio used to prorate the down payment was computed as self.fixed_amount / order.amount_total, where order.amount_total still included the fixed tax amount. This mismatch meant the fixed tax contributed to the denominator of the ratio but was never represented in the resulting down payment amount, causing the invoiced total to fall short of the requested fixed_amount by a margin proportional to the fixed tax. https://github.com/odoo/odoo/blob/baf2a1ee9d7df408aab8f3b5268a00d1161f9232/addons/sale/wizard/sale_make_invoice_advance.py#L233-L304 ### Reason to introduce the fix: The values inside the downpayement need to match with the ones inserted by the user and of the initial sale order. opw-6410745 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Some features hand a cloud storage download URL to an external service that may fetch it later. The default five-minute lifetime is too short for those flows. ### Steps to reproduce 1. Configure a cloud storage provider (e.g. cloud_storage_google). 2. Upload a large file from the web client, so it is stored in the cloud. 3. Generate a download URL for a consumer that may fetch it after five minutes. 4. The URL expires before the consumer fetches it. ### Cause The Google and Azure
Original PR description
Some features hand a cloud storage download URL to an external service that may fetch it later. The default five-minute lifetime is too short for those flows. ### Steps to reproduce 1. Configure a cloud storage provider (e.g. cloud_storage_google). 2. Upload a large file from the web client, so it is stored in the cloud. 3. Generate a download URL for a consumer that may fetch it after five minutes. 4. The URL expires before the consumer fetches it. ### Cause The Google and Azure providers always use the default download URL lifetime, so callers cannot request a longer-lived URL. ### Fix Read an optional cloud_storage_download_url_time_to_expiry context value when generating a download URL. Keep the existing five-minute lifetime as the default for all current callers. opw-5424132 Related Enterprise PR: odoo/enterprise#105967
2 changes
Resolved issues and error corrections
The correction of previous fix was bad interpreted: [commit](https://github.com/odoo/odoo/commit/f2b7d5aca54c87c9d4995f867e47fa3168065fe7) opw-6261211 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Original PR description
The correction of previous fix was bad interpreted: [commit](https://github.com/odoo/odoo/commit/f2b7d5aca54c87c9d4995f867e47fa3168065fe7) opw-6261211 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Steps to reproduce: - create a vendor credit note with `21% S.Cocont` - post it - open the VAT Return Issue: The reverse charge VAT lands on no grid. 62 and 63 stay empty, only the base reaches 85. Note: Done for the all the other co-contractant taxes: M/S/IG.Cocont at 21/12/6%. opw-6272898
Original PR description
Steps to reproduce: - create a vendor credit note with `21% S.Cocont` - post it - open the VAT Return Issue: The reverse charge VAT lands on no grid. 62 and 63 stay empty, only the base reaches 85. Note: Done for the all the other co-contractant taxes: M/S/IG.Cocont at 21/12/6%. opw-6272898