Monday, August 17, 2026
16 changes · saas-19.1
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
When the quantity of a make-to-order manufacturing order is changed, its finished move is copied to propagate the extra quantity downstream. In some cases (after splitting then merging back productions) that copy is not merged back, so the order ends up with two finished moves for the same product. On validation, two things then go wrong: - _post_inventory writes the produced quantity on each finished move, so both get the full quantity and the production is doubled - mrp_account._cal_pr
Original PR description
When the quantity of a make-to-order manufacturing order is changed, its finished move is copied to propagate the extra quantity downstream. In some cases (after splitting then merging back…
When the quantity of a make-to-order manufacturing order is changed, its finished move is copied to propagate the extra quantity downstream. In some cases (after splitting then merging back productions) that copy is not merged back, so the order ends up with two finished moves for the same product. On validation, two things then go wrong: - _post_inventory writes the produced quantity on each finished move, so both get the full quantity and the production is doubled - mrp_account._cal_price prices the finished move and calls ensure_one(), which raises "Expected singleton" for an average/fifo product, so "Produce All" fails Split the produced quantity across the finished moves with unit_factor (like _set_qty_producing already does), and price them as a whole instead of expecting a single finished move. Steps to reproduce: - Create a BoM for product A with the MTO route, and a component B - Create a Sale Order for 30 units of A, confirm it - Split the MO into 3 productions of 10, merge two of them - On the third, Update Quantity 10 -> 15, then Produce All - The product should be produced once (15, not 30), with A valued in average/fifo it instead of an error. opw-6242504 opw-6310972 opw-6307025 opw-6354739 Forward-Port-Of: odoo/odoo#269254
Steps to produce: --- - Install `sales` module. - Go to Settings and enable `Units of Measure and Packaging`. - Set a long name for `units` UoM. - Create a Sale Order and add a product via the catalog. Issue: --- - Long UoM names are not fully visible in the catalog view. Root cause: --- - The quantity input uses `flex-shrink-0`, preventing it from shrinking when space is limited by a long UoM name. Solution: --- - Allow the quantity input to shrink by removing the `fle
Original PR description
Steps to produce: --- - Install `sales` module. - Go to Settings and enable `Units of Measure and Packaging`. - Set a long name for `units` UoM. - Create a Sale Order and add a product via the…
Steps to produce: --- - Install `sales` module. - Go to Settings and enable `Units of Measure and Packaging`. - Set a long name for `units` UoM. - Create a Sale Order and add a product via the catalog. Issue: --- - Long UoM names are not fully visible in the catalog view. Root cause: --- - The quantity input uses `flex-shrink-0`, preventing it from shrinking when space is limited by a long UoM name. Solution: --- - Allow the quantity input to shrink by removing the `flex-shrink-0` constraint, giving the UoM name more space to remain visible. Before: --- <img width="395" height="46" alt="image" src="https://github.com/user-attachments/assets/33984db4-4c54-477d-b1db-dd0a8191a4a4" /> After: --- <img width="399" height="57" alt="image" src="https://github.com/user-attachments/assets/b0326295-eea3-4aca-ab44-7c581bd5b9e7" /> Enterprise PR: https://github.com/odoo/enterprise/pull/121139 opw-6253382 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#267118