Monday, August 17, 2026
41 changes
6 changes
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
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
8 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
6 changes
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#1259803 changes
Resolved issues and error corrections
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
2 changes
Resolved issues and error corrections
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
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
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
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
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 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
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
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