Daily updates from Odoo
Tuesday, August 18, 2026
13 changes · saas-19.2
Resolved issues and error corrections
The document app's automated checks now reflect improved GIF thumbnail processing. This helps keep quality checks reliable after thumbnails are resized correctly, with no expected change for end users.
Original PR description
Previously, test_document_thumbnail_status asserted that document thumbnails were byte-for-byte identical to the raw GIF content. Following improvements to GIF handling in image_process(), thumbnails are now correctly resized, causing the raw byte assertion to fail. Update the test to assert the presence and status of the thumbnail rather than matching exact raw unresized payload bytes. [odoo/odoo#281883](https://github.com/odoo/odoo/pull/281883) opw-6232841
Project settings no longer show the Time Management section unless the Timesheets app is installed. This avoids confusing users with settings for an app they do not use.
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_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. task-6195716
This fix removes ambiguity in how approval rule conditions are interpreted in Web Studio. Approval rules with no specific condition now reliably apply to all relevant records, reducing the risk of unexpected approval behavior.
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
The IoT device list now opens device details using the standard navigation flow, which restores broken pagination. This helps users browse and manage larger device lists without getting stuck or losing expected navigation behavior.
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#127215 Forward-Port-Of: odoo/enterprise#126486
Currently, when the user scraps a component during a manufacturing order, the system can still consume the same lot/serial number during production, leading to double consumption even when the product is no longer available in inventory. ## Steps to produce: - Install the Manufacturing app. - Enable 'Work Orders' and 'Lots & Serial Numbers' from the settings. - Create two products: 'Car' and 'Engine'. - Configure 'Engine' to be tracked by 'Serial Number' and update its on-hand quantity to
Original PR description
Currently, when the user scraps a component during a manufacturing order, the system can still consume the same lot/serial number during production, leading to double consumption even when the…
Currently, when the user scraps a component during a manufacturing order, the system can still consume the same lot/serial number during production, leading to double consumption even when the product is no longer available in inventory. ## Steps to produce: - Install the Manufacturing app. - Enable 'Work Orders' and 'Lots & Serial Numbers' from the settings. - Create two products: 'Car' and 'Engine'. - Configure 'Engine' to be tracked by 'Serial Number' and update its on-hand quantity to 2 using two different serial numbers. - Create a Bill of Materials (BoM) for 'Car' with 'Engine' as a component. - Create a work center named 'Car Assembly'. - Go to Manufacturing > Configuration > Operations. - Create a new manufacturing operation named 'Engine Insertion' with: - Bill of Materials: 'Car' - Work Center: 'Car Assembly' - Create and confirm a new Manufacturing Order (MO) for 'Car'. - Open the related work order and start the operation. - From Actions > Scrap, scrap the 'Engine' serial number currently reserved for the MO. - Click Produce All. - Open the product view for 'Engine' and check the on-hand quantity. ## Observed behavior: Even after the user scraps the product during the work order, the system still allows the same serial-numbered component to be consumed during production. This results in negative on-hand quantities and creates inconsistencies in inventory and manufacturing data. ## Root cause: When the user starts the work order, the `button_start` function [1] is called. This sets the `qty_producing` field, which triggers its inverse method `_set_qty_producing`. As part of this flow, the component move is marked as picked at [2]. This, in turn, triggers the inverse method of the `picked` field, which marks all move lines in the move as picked, as shown at [3]. Later, when the user scraps the same serial-numbered product, the `do_scrap` function is called. This invokes `_action_done` at [4], which further calls `_action_done` on the move lines at [5]. During this process, `_free_reservation` is executed at [6]. However, because the move lines were previously marked as picked, they no longer satisfy the domain condition at [7]. As a result, they are not unlinked at [8]. This leaves the reservation intact and eventually causes the scrapped component to still be consumed when the user produces the quantity on the manufacturing order. **Why does this issue not occur in saas-19.3?** This issue no longer occurs in saas-19.3 after this [commit](https://github.com/odoo/odoo/commit/bb56600bfc81b9a6573a867df93a706de8154d0a), which removed the manual consumption field. As a consequence, move lines are no longer marked as picked when starting work orders, preventing the inconsistent state that leads to this issue. [1]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/mrp/models/mrp_workorder.py#L659-L660 [2]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/mrp/models/mrp_production.py#L1417-L1421 [3]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/stock/models/stock_move.py#L287-L290 [4]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/stock/models/stock_scrap.py#L152-L158 [5]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/stock/models/stock_move.py#L2125 [6]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/stock/models/stock_move_line.py#L696-L700 [7]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/stock/models/stock_move_line.py#L807-L817 [8]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/stock/models/stock_move_line.py#L851 ## Solution: When a component move line is scrapped, it should be unpicked so that the system can reserve the next available lot/serial number automatically. Once a component has been physically scrapped, it no longer makes sense to keep it marked as picked and reserved. Doing so leaves the MO in an inconsistent state, where the system still considers the scrapped component usable. By unpicking the move line after scrapping, both the system and the user clearly see that there is no longer any reserved quantity associated with that component. This also allows system to automatically pick from next available lot or serial number, ensuring the manufacturing process can continue smoothly. Such behavior is especially important in shop floor scenarios, where mistakes or damages may occur after components have already been registered, and the manufacturing process should continue without interruption. opw-6157406 Forward-Port-Of: odoo/odoo#264589
Validating a negative order manually paid with a standard method (e.g., Card) mistakenly triggers the fast payment fallback. The system wrongly assumed any single negative payment line was an auto-generated unsettled due. As a result, if the first configured payment method is "Customer Account", validation is incorrectly blocked by a popup asking for a customer. This commit restricts the fast payment trigger for negative amounts to only apply when the existing line is specifically a 'pay_l
Original PR description
Validating a negative order manually paid with a standard method (e.g., Card) mistakenly triggers the fast payment fallback. The system wrongly assumed any single negative payment line was an auto-generated unsettled due. As a result, if the first configured payment method is "Customer Account", validation is incorrectly blocked by a popup asking for a customer. This commit restricts the fast payment trigger for negative amounts to only apply when the existing line is specifically a 'pay_later' type. task-6443347
When using multiple devices sharing draft orders, a race condition can happen where one device reuses another device's empty synced draft order. This leads to duplicate UUIDs, which triggers automatic order merging in `sync_from_ui` on the server and clears the table association. To prevent this: - Filter out synced orders (`!order.isSynced`) in `getEmptyOrder()`, `createOrderIfNeeded()`, and `setTable()` when looking for reusable empty orders. - This ensures each terminal only reuses its o
Original PR description
When using multiple devices sharing draft orders, a race condition can happen where one device reuses another device's empty synced draft order. This leads to duplicate UUIDs, which triggers automatic order merging in `sync_from_ui` on the server and clears the table association. To prevent this: - Filter out synced orders (`!order.isSynced`) in `getEmptyOrder()`, `createOrderIfNeeded()`, and `setTable()` when looking for reusable empty orders. - This ensures each terminal only reuses its own locally created, unsynced empty orders, guaranteeing unique UUIDs per device session. task-id: 6296661 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269551
Steps to reproduce ------------------ 1. Configure a product with a subcontracted BoM and a subcontractor. 2. Create a purchase order of 10 units for that product and confirm it. 3. Receive the 10 units. 4. On the receipt, use "Return for Exchange" on 3 units and validate both the return and the exchange receipt. Issue ----- After the exchange, the 3 units stay in the subcontracting location instead of reaching `WH/Stock`, and the received quantity on the purchase order line stays at 7
Original PR description
Steps to reproduce ------------------ 1. Configure a product with a subcontracted BoM and a subcontractor. 2. Create a purchase order of 10 units for that product and confirm it. 3. Receive the 10…
Steps to reproduce ------------------ 1. Configure a product with a subcontracted BoM and a subcontractor. 2. Create a purchase order of 10 units for that product and confirm it. 3. Receive the 10 units. 4. On the receipt, use "Return for Exchange" on 3 units and validate both the return and the exchange receipt. Issue ----- After the exchange, the 3 units stay in the subcontracting location instead of reaching `WH/Stock`, and the received quantity on the purchase order line stays at 7 instead of 10. `mrp_subcontracting` overrides `_prepare_move_default_values` to force the move `location_dest_id` to the subcontractor location for every `is_subcontract` move: https://github.com/odoo/odoo/blob/d9c06a66356dd9d5a50821b8cde6194967353c18/addons/mrp_subcontracting/wizard/stock_picking_return.py#L20-L25 That is correct for the return, but the same override also runs for the exchange re-receipt, an `incoming` picking whose destination should be the stock location from `return_type.default_location_dest_id`: https://github.com/odoo/odoo/blob/d9c06a66356dd9d5a50821b8cde6194967353c18/addons/stock/wizard/stock_picking_return.py#L137-L153 The exchange move then goes from the subcontracting location back to itself, so validating it nets zero and `WH/Stock` never receives the units. Skipping the override when `new_picking.picking_type_id.code` is `incoming` lets the exchange land in stock. The received quantity must also count that receipt. `_should_count_for_quantity_received` only counts `supplier` or `transit` sources: https://github.com/odoo/odoo/blob/d9c06a66356dd9d5a50821b8cde6194967353c18/addons/stock/models/stock_move.py#L330-L331 so the exchange, sourced from the internal subcontracting location, is skipped while the return still subtracts its quantity. Counting subcontracting-sourced moves: https://github.com/odoo/odoo/blob/d9c06a66356dd9d5a50821b8cde6194967353c18/addons/mrp_subcontracting/models/stock_move.py#L312-L314 restores `qty_received` to 10. opw-6410978 Forward-Port-Of: odoo/odoo#282373 Forward-Port-Of: odoo/odoo#279431
The `/shop` product results and facets use different search fields. In particular, facets search raw `website_description` HTML, causing terms such as `weight` to match CSS like `font-weight` and process far more products than are displayed. Use one shared field list for both paths: - `name` - `variants_default_code` - `description_sale` - `description_ecommerce` Stop searching `default_code`, internal `description`, and raw `website_description`. opw-6391984 --- I c
Original PR description
The `/shop` product results and facets use different search fields. In particular, facets search raw `website_description` HTML, causing terms such as `weight` to match CSS like `font-weight` and process far more products than are displayed. Use one shared field list for both paths: - `name` - `variants_default_code` - `description_sale` - `description_ecommerce` Stop searching `default_code`, internal `description`, and raw `website_description`. opw-6391984 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280720
Before this PR, the o_quick_assign button was not the same size as the o_avatar img which makes it look like it's misaligned when there is a company associated with the job offer. task-6092395 | Before | After | |--------|--------| | <img width="1058" height="705" alt="Screenshot 2026-04-20 at 15 19 03" src="https://github.com/user-attachments/assets/31b38b78-591d-4c55-b3e8-b3888484f9a1" /> | <img width="1058" height="705" alt="Screenshot 2026-04-20 at 15 29 40" src="https://github.com/u
Original PR description
Before this PR, the o_quick_assign button was not the same size as the o_avatar img which makes it look like it's misaligned when there is a company associated with the job offer. task-6092395 | Before | After | |--------|--------| | <img width="1058" height="705" alt="Screenshot 2026-04-20 at 15 19 03" src="https://github.com/user-attachments/assets/31b38b78-591d-4c55-b3e8-b3888484f9a1" /> | <img width="1058" height="705" alt="Screenshot 2026-04-20 at 15 29 40" src="https://github.com/user-attachments/assets/6f78190a-be92-4eeb-9a9f-55e8f247bf1c" /> | --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
# How to reproduce - Enable dev mode - In the website editor, add a Video Inner Content to the page - Enable atleast one option that is not "Start at" (e.g. Autoplay) - Click on the Video, and in the Style tab, click on Replace # The problem A traceback is shown # Cause When you replace a video, a pop up appears with the different options associated to that video. These options are rendered with the `VideoOption` component : https://github.com/odoo/odoo/blob/46d3fee5812b8406aac22
Original PR description
# How to reproduce - Enable dev mode - In the website editor, add a Video Inner Content to the page - Enable atleast one option that is not "Start at" (e.g. Autoplay) - Click on the Video, and in the…
# How to reproduce - Enable dev mode - In the website editor, add a Video Inner Content to the page - Enable atleast one option that is not "Start at" (e.g. Autoplay) - Click on the Video, and in the Style tab, click on Replace # The problem A traceback is shown # Cause When you replace a video, a pop up appears with the different options associated to that video. These options are rendered with the `VideoOption` component : https://github.com/odoo/odoo/blob/46d3fee5812b8406aac2244baa76b0daeef358c2/addons/html_editor/static/src/main/media/media_dialog/video_selector.js#L9 The props for that component accept a `value`, which type has been changed from `Boolean` to `String` in : https://github.com/odoo/odoo/commit/ae894892e048bcdf9c3efcf41b53b44f2fd9e88b The issue is that some option's value are defined as boolean : https://github.com/odoo/odoo/blob/46d3fee5812b8406aac2244baa76b0daeef358c2/addons/html_editor/static/src/main/media/media_dialog/video_selector.js#L352 So the props validation fail, but only in dev mode : https://github.com/odoo/odoo/blob/46d3fee5812b8406aac2244baa76b0daeef358c2/addons/web/static/lib/owl/owl.js#L3227-L3231 # Proposed solution When changing the options value in the popup, the trucy values are replaced by "00:00", which is why the traceback only appears when replacing the video : https://github.com/odoo/odoo/blob/46d3fee5812b8406aac2244baa76b0daeef358c2/addons/html_editor/static/src/main/media/media_dialog/video_selector.js#L182-L186 We now use the same trucy represetation in `syncOptionsWithUrl()` opw-6369196 Forward-Port-Of: odoo/odoo#280375 Forward-Port-Of: odoo/odoo#276585
Before this commit, decreasing the quantity of a move whose move lines are expressed in another unit of measure removed the wrong quantity from the lines, because the two conversions between the move unit and the line unit converted a value to its own unit, hence did nothing. Steps to reproduce: - create a product in Units with available stock - create a delivery for 2 Dozen of it and mark it as todo - in the detailed operations, change the unit of the move line to Units (24) - lower the
Original PR description
Before this commit, decreasing the quantity of a move whose move lines are expressed in another unit of measure removed the wrong quantity from the lines, because the two conversions between the move…
Before this commit, decreasing the quantity of a move whose move lines are expressed in another unit of measure removed the wrong quantity from the lines, because the two conversions between the move unit and the line unit converted a value to its own unit, hence did nothing. Steps to reproduce: - create a product in Units with available stock - create a delivery for 2 Dozen of it and mark it as todo - in the detailed operations, change the unit of the move line to Units (24) - lower the move quantity from 2 to 1 Dozen The move line ends up with 23 Units instead of 12: the decrease of 1 Dozen is applied as 1 Unit on the line and considered fully processed. The remaining 11 units stay reserved and counted on the transfer. Convert the remaining decrease from the move unit to the line unit when taking it from a line, and the taken quantity back to the move unit when updating the remaining decrease. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#278148 Forward-Port-Of: odoo/odoo#276774
`cbc:RoundingAmount` was the sum of `raw_total_excluded` over the non-fixed taxes plus the sum of `raw_tax_amount` over all the taxes, so the base of the line was counted once per non-fixed tax, inflating the line total. `cbc:TaxableAmount` was taken from the tax details of each grouping key, which is the base of that specific tax not the net amount of the line expected by JoFotara. Both amounts are now read from the base line tax details (`raw_total_included` and `raw_total_excluded`), so
Original PR description
`cbc:RoundingAmount` was the sum of `raw_total_excluded` over the non-fixed taxes plus the sum of `raw_tax_amount` over all the taxes, so the base of the line was counted once per non-fixed tax, inflating the line total. `cbc:TaxableAmount` was taken from the tax details of each grouping key, which is the base of that specific tax not the net amount of the line expected by JoFotara. Both amounts are now read from the base line tax details (`raw_total_included` and `raw_total_excluded`), so they describe the line itself regardless of the number of taxes set on it. The document level `cbc:TaxableAmount` keeps using the aggregated tax details. Even tho the problems were hidden because in Jordan, a line wouldn't have more than 1 percent tax + 1 fixed tax, it emerged during the development of the fix in this PR: https://github.com/odoo/odoo/pull/279335 Forward-Port-Of: odoo/odoo#281262