Daily updates from Odoo
Thursday, August 6, 2026
244 changes
7 changes
Enhancements to existing features
Some more improvements to refine the production process: - Allow computation of workorders' expected duration for a full production. - Remove writing into `qty_produced` while setting workorders states, unless its being marked as done. Task: 6421044 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Original PR description
Some more improvements to refine the production process: - Allow computation of workorders' expected duration for a full production. - Remove writing into `qty_produced` while setting workorders states, unless its being marked as done. Task: 6421044 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Resolved issues and error corrections
[FIX] html_builder: prevent crash on legacy image shapes When the Website Editor encounters an image shape that does not exist in the registry, it fatally crashes upon saving (`TypeError: Cannot read properties of undefined`), blocking the user from saving the page. While an upgrade script exists to remap these shapes ([commit https://github.com/odoo/odoo/commit/f348be018f5740a31754494c905ea2b61bb718be](https://github.com/odoo/upgrade/commit/f348be0dcbc63c1f74f742b562509f81767564c0)), ti
Original PR description
[FIX] html_builder: prevent crash on legacy image shapes When the Website Editor encounters an image shape that does not exist in the registry, it fatally crashes upon saving (`TypeError: Cannot read…
[FIX] html_builder: prevent crash on legacy image shapes When the Website Editor encounters an image shape that does not exist in the registry, it fatally crashes upon saving (`TypeError: Cannot read properties of undefined`), blocking the user from saving the page. While an upgrade script exists to remap these shapes ([commit https://github.com/odoo/odoo/commit/f348be018f5740a31754494c905ea2b61bb718be](https://github.com/odoo/upgrade/commit/f348be0dcbc63c1f74f742b562509f81767564c0)), timeline gaps leave SaaS databases vulnerable. For example, if a client upgraded their database to 17.0 in Feb 2024, they bypassed the migration script merged in Dec 2024. This leaves the legacy shape permanently orphaned inside their modern views. This commit adds a `getImageShape` fallback. Instead of crashing,the editor now defaults to standard values and renders "None" in the UI, allowing the user to select a new shape and save their work. Steps to Reproduce: 1. Install Website. 2. Go to Site -> HTML / CSS Editor. 3. Add `data-shape="web_editor/basic/bsc_organic_2"` to an <img> tag. 4. Click "Edit" to open the Website Builder. 5. Click the image, OR click "Save". 6. JS traceback. [opw-6286044](https://www.odoo.com/odoo/my-support-tasks/6286044?debug=assets) [opw-6291591](https://www.odoo.com/odoo/my-support-tasks/6291591?debug=assets) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280505 Forward-Port-Of: odoo/odoo#270356
The `selection` attribute of `fields.Selection` is not generally translated (unless it is a function instead of a list). For user facing strings, we generally need to translate the value displayed. 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#280476 Forward-Port-Of: odoo/odoo#280094
Original PR description
The `selection` attribute of `fields.Selection` is not generally translated (unless it is a function instead of a list). For user facing strings, we generally need to translate the value displayed. 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#280476 Forward-Port-Of: odoo/odoo#280094
### Steps to reproduce: - In the settings enable: Multi-Steps Routes - Inventory > Configuration > Warehouse Management > Warehouses - Set your warehouse deliveries in two steps - Inventory > Operations > Tranfers > Internal > New - Set the operation type as Pick, set a partner and add Partner: Bob - In the sales & Purchase tab of the partner form set a customer location to be a child of the Customers location: Customers/Bob'Stock - Confirm and validate the Pick for 1 unit of a product
Original PR description
### Steps to reproduce: - In the settings enable: Multi-Steps Routes - Inventory > Configuration > Warehouse Management > Warehouses - Set your warehouse deliveries in two steps - Inventory >…
### Steps to reproduce: - In the settings enable: Multi-Steps Routes - Inventory > Configuration > Warehouse Management > Warehouses - Set your warehouse deliveries in two steps - Inventory > Operations > Tranfers > Internal > New - Set the operation type as Pick, set a partner and add Partner: Bob - In the sales & Purchase tab of the partner form set a customer location to be a child of the Customers location: Customers/Bob'Stock - Confirm and validate the Pick for 1 unit of a product P #### > A ship picking is created but the destination of the related move is still set to the default customer location. ### Note: If the flow is performed by a sale order, the `property_stock_customer` location will appropriately be used as `location_final_id`: https://github.com/odoo/odoo/blob/7609b5805c3704034b4d7813e2f356381ed18771/addons/sale_stock/models/sale_order_line.py#L297 https://github.com/odoo/odoo/blob/7609b5805c3704034b4d7813e2f356381ed18771/addons/sale_stock/models/sale_order_line.py#L306-L309 https://github.com/odoo/odoo/blob/720598d0315dbb91628441078febfd43ffefb431/addons/stock/models/stock_rule.py#L263-L264 So that the bug does not occur in that case. By contrast if the pick move is created manually, we do not set its `location_final_id` and hence do not propagate the info. Even though it looks expected to be set set as location_dest_id of the ship move sas suggested by the `stock.picking.location_dest_id` compute method : https://github.com/odoo/odoo/blob/fe3aea07a1964cd24f4c8ebf2bc93e483eca6b0b/addons/stock/models/stock_picking.py#L990-L1002 opw-6402483 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279616 Forward-Port-Of: odoo/odoo#278838
# Issue The test `test_absence_management_with_timeoff` fails if demo data is enabled. It was introduced by : https://github.com/odoo/odoo/pull/272089 # Cause `self.env.user` has an 'Europe/Brussels' tz when demo data is enabled. This changes the date used in the `search_count` at then end of the test : 2026-01-14 00:00 => 2026-01-13 23:00 So we check at the wrong date runbot-941523 Forward-Port-Of: odoo/odoo#279764
Original PR description
# Issue The test `test_absence_management_with_timeoff` fails if demo data is enabled. It was introduced by : https://github.com/odoo/odoo/pull/272089 # Cause `self.env.user` has an 'Europe/Brussels' tz when demo data is enabled. This changes the date used in the `search_count` at then end of the test : 2026-01-14 00:00 => 2026-01-13 23:00 So we check at the wrong date runbot-941523 Forward-Port-Of: odoo/odoo#279764
[*]=website 1. Sync background shape color with color preset. Steps to reproduce: 1. Go to the website and enter edit mode. 3. Drop any snippet. 4. Add a background shape. 5. Set the background shape color to "o-color-1". 6. Go to theme tab. 7. Change the value of theme color 1 from theme preset. Issue: The background shape color is not updated when the theme color changes. Reason: The background shape color is embedded in the
Original PR description
[*]=website 1. Sync background shape color with color preset. Steps to reproduce: 1. Go to the website and enter edit mode. 3. Drop any snippet. 4. Add a background shape. 5. Set the background shape…
[*]=website
1. Sync background shape color with color preset.
Steps to reproduce:
1. Go to the website and enter edit mode.
3. Drop any snippet.
4. Add a background shape.
5. Set the background shape color to "o-color-1".
6. Go to theme tab.
7. Change the value of theme color 1 from theme preset.
Issue:
The background shape color is not updated when the theme color changes.
Reason:
The background shape color is embedded in the URL of the "**background-image**" style attribute. When the theme color value changes, this URL is not updated. Additionally, the URL uses color variables rather than resolved hexadecimal color values as parameters. As a result, even when an updation occurs, the URL itself remains unchanged, preventing the background shape color from being updated.
2. Sync image shape color with color preset.
Steps to reproduce:
1. Go to the website and enter edit mode.
2. Drop any snippet.
4. Click on the image and add a shape.
5. Set the image shape color to "o-color-1".
6. Go to theme tab.
7. Change the value of theme color 1 from theme preset.
Issue:
The image shape color is not updated when the theme color changes.
Reason:
When the theme color value changes, the SVGs are not re-fetched. Additionally, the image "**shapeColors**" dataset stores the hexadecimal value of the theme color instead of the corresponding CSS variable. As a result, there is no way to determine which theme color was selected (for example, whether `o-color-1` or `o-color-2`), since only the hex value is available.
task-5438314
Forward-Port-Of: odoo/odoo#276135
Forward-Port-Of: odoo/odoo#241968After removing sale_order_many2one widget https://github.com/odoo/odoo/commit/0ecaa6e4c359681daf90c1757bcf71ba5e4d305c , there's no need for multiple sale_order_id definitions in the form view. In this commit, cleaning the redundant field definitions and restrict visibility of Sale Order smart button. task-4661781 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280740 Forward-Port-Of: odoo/odoo#255367
Original PR description
After removing sale_order_many2one widget https://github.com/odoo/odoo/commit/0ecaa6e4c359681daf90c1757bcf71ba5e4d305c , there's no need for multiple sale_order_id definitions in the form view. In this commit, cleaning the redundant field definitions and restrict visibility of Sale Order smart button. task-4661781 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280740 Forward-Port-Of: odoo/odoo#255367
6 changes
Resolved issues and error corrections
The compute method should not depend whether or not the employee is active or not. 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
Original PR description
The compute method should not depend whether or not the employee is active or not. 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
Bug === Since a4efb745e9f4ba24dfa01f1e17e65f1a7cac90a5 , we cannot insert properties. The reason is that we try to get the field definition from the inserted QWeb expression (eg `object.properties.get('property_product', env['product']).name`) which crash. Task-6311655 Forward-Port-Of: odoo/odoo#279373
Original PR description
Bug
===
Since a4efb745e9f4ba24dfa01f1e17e65f1a7cac90a5 , we cannot insert properties. The reason is that we try to get the field definition from the inserted QWeb expression (eg `object.properties.get('property_product', env['product']).name`) which crash.
Task-6311655
Forward-Port-Of: odoo/odoo#279373# Issue The test `test_absence_management_with_timeoff` fails if demo data is enabled. It was introduced by : https://github.com/odoo/odoo/pull/272089 # Cause `self.env.user` has an 'Europe/Brussels' tz when demo data is enabled. This changes the date used in the `search_count` at then end of the test : 2026-01-14 00:00 => 2026-01-13 23:00 So we check at the wrong date runbot-941523 Forward-Port-Of: odoo/odoo#279764
Original PR description
# Issue The test `test_absence_management_with_timeoff` fails if demo data is enabled. It was introduced by : https://github.com/odoo/odoo/pull/272089 # Cause `self.env.user` has an 'Europe/Brussels' tz when demo data is enabled. This changes the date used in the `search_count` at then end of the test : 2026-01-14 00:00 => 2026-01-13 23:00 So we check at the wrong date runbot-941523 Forward-Port-Of: odoo/odoo#279764
See commit messages for details. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Original PR description
See commit messages for details. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Before this commit, an internal user opening the public page of a channel they are not a member of could see Discuss move to another conversation while composing, so the message was sent without its text and the tour timed out on it: FAILED: [15/37] Tour discuss_channel_public_tour.js -> Step .o-mail-Message:has(...:contains('cheese'))[data-persistent]. Element has not been found. TIMEOUT step failed to complete within 10000 ms. This happens because every member of a cha
Original PR description
Before this commit, an internal user opening the public page of a channel they are not a member of could see Discuss move to another conversation while composing, so the message was sent without its…
Before this commit, an internal user opening the public page of a channel they are not a member of could see Discuss move to another conversation while composing, so the message was sent without its text and the tour timed out on it:
FAILED: [15/37] Tour discuss_channel_public_tour.js -> Step
.o-mail-Message:has(...:contains('cheese'))[data-persistent].
Element has not been found.
TIMEOUT step failed to complete within 10000 ms.
This happens because every member of a channel notifies it that its pin state changed, self member or not. The member panel loads the members of the displayed channel right after the page, and as the user has no member there, the channel then concludes it is not pinned and Discuss opens the first pinned channel of the sidebar. The text typed before that stays on the composer of the channel it was typed in, while the attachments and the send go to the new one.
This commit fixes the issue by notifying the channel only when the pin state of self member changes. The tour also checks the name of the displayed conversation is unchanged before sending, as it succeeds in whichever conversation is displayed.
https://runbot.odoo.com/odoo/error/944617After removing sale_order_many2one widget https://github.com/odoo/odoo/commit/0ecaa6e4c359681daf90c1757bcf71ba5e4d305c , there's no need for multiple sale_order_id definitions in the form view. In this commit, cleaning the redundant field definitions and restrict visibility of Sale Order smart button. task-4661781 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280740 Forward-Port-Of: odoo/odoo#255367
Original PR description
After removing sale_order_many2one widget https://github.com/odoo/odoo/commit/0ecaa6e4c359681daf90c1757bcf71ba5e4d305c , there's no need for multiple sale_order_id definitions in the form view. In this commit, cleaning the redundant field definitions and restrict visibility of Sale Order smart button. task-4661781 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280740 Forward-Port-Of: odoo/odoo#255367
8 changes
Resolved issues and error corrections
Steps to reproduce the error: (Python Version: 3.14.X) - Install ``accounting_firm`` industry with demo data Code for server action to generate the error: ```py for i in [1]: try: pass except Exception: pass if i: pass ``` Traceback: ```py ValueError: forbidden opcode(s) in'...': JUMP_BACKWARD_NO_INTERRUPT ``` https://github.com/odoo/industry/blob/701f7595453772e42ce72aa75df346a504e5bd82/accounting_firm/demo/ir_actions_server.xml#L105-L10
Original PR description
Steps to reproduce the error: (Python Version: 3.14.X) - Install ``accounting_firm`` industry with demo data Code for server action to generate the error: ```py for i in [1]: try: pass except…
Steps to reproduce the error: (Python Version: 3.14.X)
- Install ``accounting_firm`` industry with demo data
Code for server action to generate the error:
```py
for i in [1]:
try:
pass
except Exception:
pass
if i:
pass
```
Traceback:
```py
ValueError: forbidden opcode(s) in'...': JUMP_BACKWARD_NO_INTERRUPT
```
https://github.com/odoo/industry/blob/701f7595453772e42ce72aa75df346a504e5bd82/accounting_firm/demo/ir_actions_server.xml#L105-L108
The server action contains a ``for`` loop with a ``try/except`` block followed by additional statements in the loop body,
this combination generates the ``JUMP_BACKWARD_NO_INTERRUPT`` opcode, which is not included in ``_SAFE_OPCODES`` at [1].
When the server action is evaluated by ``safe_eval``, it calls the ``assert_valid_codeobj`` method, which validates the compiled bytecode against ``_SAFE_OPCODES``. Since ``JUMP_BACKWARD_NO_INTERRUPT`` is not present in the allowed opcodes, ``assert_valid_codeobj()`` raises a ``ValueError`` at [2] before the server action is executed .
Solution:
``JUMP_BACKWARD_NO_INTERRUPT`` opcode is added in the ``_SAFE_OPCODES`` and it is also added in the ``_SAFE_QWEB_OPCODES``.
It was added in Python 3.11: https://docs.python.org/3/whatsnew/3.11.html#new-opcodes
``JUMP_BACKWARD_NO_INTERRUPT`` is a control-flow opcode that only changes
the interpreter's execution flow by jumping back to a previous instruction.
It is the equivalent to ``JUMP_BACKWARD`` opcode. Its only semantic difference is
that the interpreter does not perform an interrupt check at that instruction.
It does not introduce any new capabilities or perform operations such as
attribute access, imports, function calls, or object creation.
Ref: https://docs.python.org/3.12/library/dis.html#opcode-JUMP_BACKWARD_NO_INTERRUPT
Similar commit that adds some necessary opcodes:
https://github.com/odoo/odoo/commit/86498d24946e510025add5d24ef0d4bcce8ad05f
[1]: https://github.com/odoo/odoo/blob/2ccbc4660077bd48529e9de43d4309fbfafc75ca/odoo/tools/safe_eval.py#L135
[2]: https://github.com/odoo/odoo/blob/2ccbc4660077bd48529e9de43d4309fbfafc75ca/odoo/tools/safe_eval.py#L244-L246
sentry-7614026125
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#277192### Issue: A partner with VAT set to '/' incorrectly matches a fiscal position with `VAT required`, instead of one without The '/' value is the placeholder suggested by the UI to indicate that the partner is known to have no VAT, but it was treated as a valid VAT by the fiscal position matching logic ### Cause: `_get_fpos_ranking_functions` uses `_get_vat_valid` to rank fiscal positions based on VAT presence `_get_vat_valid` returned `True` for any non-empty VAT value, including '/' Th
Original PR description
### Issue: A partner with VAT set to '/' incorrectly matches a fiscal position with `VAT required`, instead of one without The '/' value is the placeholder suggested by the UI to indicate that the…
### Issue: A partner with VAT set to '/' incorrectly matches a fiscal position with `VAT required`, instead of one without The '/' value is the placeholder suggested by the UI to indicate that the partner is known to have no VAT, but it was treated as a valid VAT by the fiscal position matching logic ### Cause: `_get_fpos_ranking_functions` uses `_get_vat_valid` to rank fiscal positions based on VAT presence `_get_vat_valid` returned `True` for any non-empty VAT value, including '/' The '/' case was not excluded, causing it to be treated as a valid VAT number ### Steps to reproduce: - Install `account` - Create two fiscal positions with auto-apply: -- Name: FP VAT, VAT required: True, sequence: 1 -- Name: FP no VAT, VAT required: False, sequence: 2 - Create a partner with VAT: '/' - Create an Invoice for that partner and check the Fiscal Position Before the fix, `FP VAT` is selected instead of `FP no VAT` opw-6204531 Forward-Port-Of: odoo/odoo#280494 Forward-Port-Of: odoo/odoo#280065
## Issue When logging timesheet from the *Recorded* smart button on a Sale Order containing multiple service items, the recorded hours will be added to the first sale order line, regardless of the task ID set on the timesheet entries. ## Steps to reproduce 1. Install *Sales Timesheet* (`sale_timesheet`) 2. Create a Product P: - *Product Type*: Service - *Create on Order*: Task - *Project*: Any 3. Create a SO: - *Customer*: Any - Add the product P on two different
Original PR description
## Issue When logging timesheet from the *Recorded* smart button on a Sale Order containing multiple service items, the recorded hours will be added to the first sale order line, regardless of the…
## Issue
When logging timesheet from the *Recorded* smart button on a Sale Order containing multiple service items, the recorded hours will be added to the first sale order line, regardless of the task ID set on the timesheet entries.
## Steps to reproduce
1. Install *Sales Timesheet* (`sale_timesheet`)
2. Create a Product P:
- *Product Type*: Service
- *Create on Order*: Task
- *Project*: Any
3. Create a SO:
- *Customer*: Any
- Add the product P on two different lines and give them two different descriptions D1 and D2
- Confirm the SO, this will create two tasks with the names D1 and D2
4. On the SO, click the *Recorded* smart button and create two entries:
1. Task D1, 2 hours spent
2. Tsk D2, 3 hours spent
5. **Back on the SO, there are 5 hours registered for the first SOL (with the description D1), which does not match the entries we created from the smart button.**
It is worth noting that when we create the Timesheets entries from the project itself (instead of the SO's smart button), the hours are correctly distributed among the different SOLs.
## Cause
The SOL linked to the timesheet entry (`account.analytic.line`) is computed by `_compute_so_line`:
https://github.com/odoo/odoo/blob/8b102f500f5a122e99b07a08cc43814e7c6f0f75/addons/sale_timesheet/models/hr_timesheet.py#L79-L82
This method sets the correct SOL under the condition that `is_so_line_edited` is False and `_is_no_billed()` returns True.
When opening the *Recorded* smart button from a SO, the `is_so_line_edited` is set to True by default, even if no SOL was modified.
https://github.com/odoo/odoo/blob/8b102f500f5a122e99b07a08cc43814e7c6f0f75/addons/sale_timesheet/models/sale_order.py#L113-L117
As that value is never set to False, when trying to compute the SOL for the timesheet entry, the entry is skipped and the default SOL (which is the first one) is used instead.
opw-6133473
Forward-Port-Of: odoo/odoo#274759Before this commit, in some cases, the order created for printing cash move was saved to the IndexedDB and then later loaded from IndexedDB, which caused the order gets synced to the backend but missing some required fields, like preset or pricelist. opw-5969602 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#262248
Original PR description
Before this commit, in some cases, the order created for printing cash move was saved to the IndexedDB and then later loaded from IndexedDB, which caused the order gets synced to the backend but missing some required fields, like preset or pricelist. opw-5969602 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#262248
Since https://github.com/odoo/odoo/pull/239898 Opening a livechat session in debug mode as a user without livechat access throws a traceback: `Invalid props for component BadgeTag: onDelete is not a function` This happens because `null` is passed to the `onDelete` prop, while the component validation expects a function. This commit fixes the issue. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280679
Original PR description
Since https://github.com/odoo/odoo/pull/239898 Opening a livechat session in debug mode as a user without livechat access throws a traceback: `Invalid props for component BadgeTag: onDelete is not a function` This happens because `null` is passed to the `onDelete` prop, while the component validation expects a function. This commit fixes the issue. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280679
When we create a database with `-i web --skip-auto-install`, we run into CSS compilation errors due to undefined variables `$black` and `$gray-200`. This commit adds these two definitions. opw-6398398 Forward-Port-Of: odoo/odoo#279753 Forward-Port-Of: odoo/odoo#279287
Original PR description
When we create a database with `-i web --skip-auto-install`, we run into CSS compilation errors due to undefined variables `$black` and `$gray-200`. This commit adds these two definitions. opw-6398398 Forward-Port-Of: odoo/odoo#279753 Forward-Port-Of: odoo/odoo#279287
Steps to reproduce ------------------ 1. Set the company document layout to DIN5008 2. Open a delivery and print the delivery slip The title is missing on the DIN5008 layout, we only have the reference `WH/OUT/00001`. What happens ------------ The DIN5008 layout hides the body title with css and prints its own `h2` instead, from the `din5008_document_title` variable, and uses `o.name` (the picking number) when this variable is not set. The commit 0058d1cf7655 added the title on the
Original PR description
Steps to reproduce ------------------ 1. Set the company document layout to DIN5008 2. Open a delivery and print the delivery slip The title is missing on the DIN5008 layout, we only have the reference `WH/OUT/00001`. What happens ------------ The DIN5008 layout hides the body title with css and prints its own `h2` instead, from the `din5008_document_title` variable, and uses `o.name` (the picking number) when this variable is not set. The commit 0058d1cf7655 added the title on the standard delivery report with `picking_type_id._get_code_report_name()`, but `l10n_din5008_stock` was not updated to set `din5008_document_title`, so on DIN5008 we only get the number. The fix ------- We set it the same way as the other layouts, hence we get back the full title `Delivery Note WH/OUT/00001`. opw-6299248 Forward-Port-Of: odoo/odoo#277444
Miscellaneous changes
load_data() reads product.pricelist.item_ids as a field, which applies _base_domain_item_ids()'s dotted active conditions. Since this runs as the cashier, never sudo, the ORM injects ir.rules into those conditions, forcing a non-hashable subquery that Postgres re-scans once per pricelist item. Cost scales with items times catalog size, freezing session opening on large catalogs. product.pricelist.item is already loaded separately via a plain, indexed pricelist_id domain. Reuse it to build ite
Original PR description
load_data() reads product.pricelist.item_ids as a field, which applies _base_domain_item_ids()'s dotted active conditions. Since this runs as the cashier, never sudo, the ORM injects ir.rules into those conditions, forcing a non-hashable subquery that Postgres re-scans once per pricelist item. Cost scales with items times catalog size, freezing session opening on large catalogs.
product.pricelist.item is already loaded separately via a plain, indexed pricelist_id domain. Reuse it to build item_ids instead of reading the field, avoiding the join/subquery entirely.
before after speedup
item_ids read 475.8s 2.44s ~195x
load_data() (full) unbounded 8.7s hang -> ok
opw-6391847
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#27725410 changes
Enhancements to existing features
Doing an euclidean division on floats with the native operators is unreliable: because of IEEE-754 representation errors, `value1 % value2` can return a spurious remainder (e.g. `50.4 % 16.8 == 16.799999999999997` instead of 0.0) and `int(value1 / value2)` can truncate the quotient one step too low (e.g. `int(0.3 / 0.1) == 2` instead of 3). `float_div` returns the `(quotient, remainder)` pair free of those errors. The key is to never run a lossy `%` or `//` on the raw floats. Instead both ope
Original PR description
Doing an euclidean division on floats with the native operators is unreliable: because of IEEE-754 representation errors, `value1 % value2` can return a spurious remainder (e.g. `50.4 % 16.8 ==…
Doing an euclidean division on floats with the native operators is unreliable: because of IEEE-754 representation errors, `value1 % value2` can return a spurious remainder (e.g. `50.4 % 16.8 == 16.799999999999997` instead of 0.0) and `int(value1 / value2)` can truncate the quotient one step too low (e.g. `int(0.3 / 0.1) == 2` instead of 3). `float_div` returns the `(quotient, remainder)` pair free of those errors. The key is to never run a lossy `%` or `//` on the raw floats. Instead both operands are first snapped onto the precision grid with `float_round` and then scaled to integers: since a grid-snapped value is a multiple of `rounding`, dividing it by `rounding` counts how many grid steps it spans. That division is still noisy (`4.35 / 0.05 == 86.99999999999999`), so the result is passed through `builtins.round` to coerce it to the exact integer step count. The euclidean division itself is then a plain integer `divmod`, which is exact, and the remainder is scaled back to real units. This is why the correction is applied to the inputs and not to the output: rounding the result of a native `%` would only round an already-corrupt value, and would still misreport the quotient in the corner cases the util exists to handle. Dividing by `rounding` is meaningful for any precision, not only powers of ten: the grid step can be `0.05`, `0.25`, `0.5`, `0.03`, ... and `value / step` counts the steps in every case. This mirrors the normalize/denormalize scheme `float_round` already uses internally. The util shares `float_round`'s inherent limitation: the scaled step count must stay representable as an exact `float` integer, so exactness is lost past ~2**53 grid steps (extreme magnitudes at a fine precision). This is the IEEE-754 double-precision ceiling and is well outside any realistic quantity or price range. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280456 Forward-Port-Of: odoo/odoo#277160
Resolved issues and error corrections
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#279464
When we create a database with `-i web --skip-auto-install`, we run into CSS compilation errors due to undefined variables `$black` and `$gray-200`. This commit adds these two definitions. opw-6398398 Forward-Port-Of: odoo/odoo#279753 Forward-Port-Of: odoo/odoo#279287
Original PR description
When we create a database with `-i web --skip-auto-install`, we run into CSS compilation errors due to undefined variables `$black` and `$gray-200`. This commit adds these two definitions. opw-6398398 Forward-Port-Of: odoo/odoo#279753 Forward-Port-Of: odoo/odoo#279287
Before this commit, this test sometimes failed because it couldn't find a dialog containing "camera" within 200ms. In the test scenario, we click to open the BarcodeDialog, which uses the BarcodeVideoScanner. The latter, in its `onMounted`, checks whether it has the necessary permission, which isn't the case as the `getUserMedia` function is mocked in the test to return a rejected promise. As a consequence, the `onError` callback given in props is called, which changes the state of the parent
Original PR description
Before this commit, this test sometimes failed because it couldn't find a dialog containing "camera" within 200ms. In the test scenario, we click to open the BarcodeDialog, which uses the…
Before this commit, this test sometimes failed because it couldn't find a dialog containing "camera" within 200ms. In the test scenario, we click to open the BarcodeDialog, which uses the BarcodeVideoScanner. The latter, in its `onMounted`, checks whether it has the necessary permission, which isn't the case as the `getUserMedia` function is mocked in the test to return a rejected promise. As a consequence, the `onError` callback given in props is called, which changes the state of the parent component, which re-renders itself so display "Unable to access camera" instead of the BarcodeVideoScanner. To make this test more robust, we do 2 things: 1) load the zxing library before running the test, which avoids the BarcodeVideoScanner component to load it in onWillStart. 2) explicitly wait for the 2 animationFrames, as in the scenario, we must wait for the BarcodeDialog to be rendered twice, and those renderings are now synchronous. runbot error-237933 Forward-Port-Of: odoo/odoo#280613
Steps to reproduce ------------------ 1. Set the company document layout to DIN5008 2. Open a delivery and print the delivery slip The title is missing on the DIN5008 layout, we only have the reference `WH/OUT/00001`. What happens ------------ The DIN5008 layout hides the body title with css and prints its own `h2` instead, from the `din5008_document_title` variable, and uses `o.name` (the picking number) when this variable is not set. The commit 0058d1cf7655 added the title on the
Original PR description
Steps to reproduce ------------------ 1. Set the company document layout to DIN5008 2. Open a delivery and print the delivery slip The title is missing on the DIN5008 layout, we only have the reference `WH/OUT/00001`. What happens ------------ The DIN5008 layout hides the body title with css and prints its own `h2` instead, from the `din5008_document_title` variable, and uses `o.name` (the picking number) when this variable is not set. The commit 0058d1cf7655 added the title on the standard delivery report with `picking_type_id._get_code_report_name()`, but `l10n_din5008_stock` was not updated to set `din5008_document_title`, so on DIN5008 we only get the number. The fix ------- We set it the same way as the other layouts, hence we get back the full title `Delivery Note WH/OUT/00001`. opw-6299248 Forward-Port-Of: odoo/odoo#277444
# Issue The test `test_absence_management_with_timeoff` fails if demo data is enabled. It was introduced by : https://github.com/odoo/odoo/pull/272089 # Cause `self.env.user` has an 'Europe/Brussels' tz when demo data is enabled. This changes the date used in the `search_count` at then end of the test : 2026-01-14 00:00 => 2026-01-13 23:00 So we check at the wrong date runbot-941523 Forward-Port-Of: odoo/odoo#279764
Original PR description
# Issue The test `test_absence_management_with_timeoff` fails if demo data is enabled. It was introduced by : https://github.com/odoo/odoo/pull/272089 # Cause `self.env.user` has an 'Europe/Brussels' tz when demo data is enabled. This changes the date used in the `search_count` at then end of the test : 2026-01-14 00:00 => 2026-01-13 23:00 So we check at the wrong date runbot-941523 Forward-Port-Of: odoo/odoo#279764
Before this commit, it was possible that get_limited_partners_loading returned a partner if a module that overrode the method was installed. runbot-944636 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280628
Original PR description
Before this commit, it was possible that get_limited_partners_loading returned a partner if a module that overrode the method was installed. runbot-944636 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280628
A `product.pricelist.item` targeting a product or a template that is not loaded in the PoS was applied to every product of the session. `computeRuleIndexes` deduced which kind of rule an item was from the many2one that resolved to a live record. That getter returns `undefined` when the targeted record is not in the local store, so such an item fell through every branch and ended up in the global rules, overriding the price of unrelated products. The targeted record is legitimately absent i
Original PR description
A `product.pricelist.item` targeting a product or a template that is not loaded in the PoS was applied to every product of the session. `computeRuleIndexes` deduced which kind of rule an item was from the many2one that resolved to a live record. That getter returns `undefined` when the targeted record is not in the local store, so such an item fell through every branch and ended up in the global rules, overriding the price of unrelated products. The targeted record is legitimately absent in two cases: the product was archived and removed by `filter_local_data` while the rule itself was kept, and, on an incremental reload, the item domain drops its product and category filters, so items of products that were never loaded in this PoS might be sent to the client. opw-6344491 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279491
Steps to reproduce: - Install employees and attendance app - Make sure there are 2 companies - Make user's employee record for Company B, but not A - Make company A the default company for user - Enable "attendances from backend" setting - Click on the attendance dot (systray) Current Behavior: The dot disappears and you can't check in Expected Behavior: You are able to check in Other bug scenario: If you have employee records in both Company A and Company B, you can check in.
Original PR description
Steps to reproduce: - Install employees and attendance app - Make sure there are 2 companies - Make user's employee record for Company B, but not A - Make company A the default company for user - Enable "attendances from backend" setting - Click on the attendance dot (systray) Current Behavior: The dot disappears and you can't check in Expected Behavior: You are able to check in Other bug scenario: If you have employee records in both Company A and Company B, you can check in. However, you can never check in for Company B as the default company is always selected in the server code opw-6392301 Forward-Port-Of: odoo/odoo#280168 Forward-Port-Of: odoo/odoo#278377
After removing sale_order_many2one widget https://github.com/odoo/odoo/commit/0ecaa6e4c359681daf90c1757bcf71ba5e4d305c , there's no need for multiple sale_order_id definitions in the form view. In this commit, cleaning the redundant field definitions and restrict visibility of Sale Order smart button. task-4661781 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280740 Forward-Port-Of: odoo/odoo#255367
Original PR description
After removing sale_order_many2one widget https://github.com/odoo/odoo/commit/0ecaa6e4c359681daf90c1757bcf71ba5e4d305c , there's no need for multiple sale_order_id definitions in the form view. In this commit, cleaning the redundant field definitions and restrict visibility of Sale Order smart button. task-4661781 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280740 Forward-Port-Of: odoo/odoo#255367
5 changes
Resolved issues and error corrections
When we create a database with `-i web --skip-auto-install`, we run into CSS compilation errors due to undefined variables `$black` and `$gray-200`. This commit adds these two definitions. opw-6398398 Forward-Port-Of: odoo/odoo#279287
Original PR description
When we create a database with `-i web --skip-auto-install`, we run into CSS compilation errors due to undefined variables `$black` and `$gray-200`. This commit adds these two definitions. opw-6398398 Forward-Port-Of: odoo/odoo#279287
Before this commit, this test sometimes failed because it couldn't find a dialog containing "camera" within 200ms. In the test scenario, we click to open the BarcodeDialog, which uses the BarcodeVideoScanner. The latter, in its `onMounted`, checks whether it has the necessary permission, which isn't the case as the `getUserMedia` function is mocked in the test to return a rejected promise. As a consequence, the `onError` callback given in props is called, which changes the state of the parent
Original PR description
Before this commit, this test sometimes failed because it couldn't find a dialog containing "camera" within 200ms. In the test scenario, we click to open the BarcodeDialog, which uses the…
Before this commit, this test sometimes failed because it couldn't find a dialog containing "camera" within 200ms. In the test scenario, we click to open the BarcodeDialog, which uses the BarcodeVideoScanner. The latter, in its `onMounted`, checks whether it has the necessary permission, which isn't the case as the `getUserMedia` function is mocked in the test to return a rejected promise. As a consequence, the `onError` callback given in props is called, which changes the state of the parent component, which re-renders itself so display "Unable to access camera" instead of the BarcodeVideoScanner. To make this test more robust, we do 2 things: 1) load the zxing library before running the test, which avoids the BarcodeVideoScanner component to load it in onWillStart. 2) explicitly wait for the 2 animationFrames, as in the scenario, we must wait for the BarcodeDialog to be rendered twice, and those renderings are now synchronous. runbot error-237933 Forward-Port-Of: odoo/odoo#280613
The test `waitForAnimationFrame does not trigger update if interaction is not ready yet` fails randomly (and seldom). The timeout and animation frame are treated together and appear at the same time in the verified steps. This is likely due to the timeout being too close to the animation frame and the task queue resolving them back to back. Giving a larger timeout (1s instead of 100ms) should solve the issue. runbot-939992
Original PR description
The test `waitForAnimationFrame does not trigger update if interaction is not ready yet` fails randomly (and seldom). The timeout and animation frame are treated together and appear at the same time in the verified steps. This is likely due to the timeout being too close to the animation frame and the task queue resolving them back to back. Giving a larger timeout (1s instead of 100ms) should solve the issue. runbot-939992
Steps to reproduce: - From the website editor, open the Theme tab. - Upload a custom font. - Open the media dialog and go to the "Documents" tab. Issue: The uploaded font files appeared in the Documents tab. When a zip file was uploaded, every font it contained appeared individually, along with the generated "CSS font face" attachment. Cause: Fonts uploaded through `/website/theme_upload_font` are created as public attachments. The Documents tab of the media dialog lists every public
Original PR description
Steps to reproduce: - From the website editor, open the Theme tab. - Upload a custom font. - Open the media dialog and go to the "Documents" tab. Issue: The uploaded font files appeared in the…
Steps to reproduce: - From the website editor, open the Theme tab. - Upload a custom font. - Open the media dialog and go to the "Documents" tab. Issue: The uploaded font files appeared in the Documents tab. When a zip file was uploaded, every font it contained appeared individually, along with the generated "CSS font face" attachment. Cause: Fonts uploaded through `/website/theme_upload_font` are created as public attachments. The Documents tab of the media dialog lists every public attachment that is not an image or an asset, so the font files (mimetype `font/...`), their font face declaration (mimetype `text/css`) and googleFontMetadata (server caches it as public attachment) were listed. Fix: Exclude those attachments from the Documents tab domain: - whose mimetype starts with `font/`, - whose description matches the font face declarations created in `snippets.options.js`. - whose name equals "googleFontMetadata". task-[4771523](https://www.odoo.com/odoo/project/974/tasks/4771523) Forward-Port-Of: odoo/odoo#275838
Steps: - Install sale_management - Make sure you have 100 quotations with 1 activity each - Open activity view - Default pager displays `1-100/100` - Activity count display `To-do 80` ActivityController uses `useModel` which passes the raw `component.props` to `model.load()`, including the limit from `ir.actions.act_window` (default 80). This value ended up in `fetchActivityData` via `params.limit || this.initialLimit`, overriding `ActivityModel.DEFAULT_LIMIT` (100). The records li
Original PR description
Steps: - Install sale_management - Make sure you have 100 quotations with 1 activity each - Open activity view - Default pager displays `1-100/100` - Activity count display `To-do 80`…
Steps: - Install sale_management - Make sure you have 100 quotations with 1 activity each - Open activity view - Default pager displays `1-100/100` - Activity count display `To-do 80` ActivityController uses `useModel` which passes the raw `component.props` to `model.load()`, including the limit from `ir.actions.act_window` (default 80). This value ended up in `fetchActivityData` via `params.limit || this.initialLimit`, overriding `ActivityModel.DEFAULT_LIMIT` (100). The records list was not affected because `RelationalModel._getNextConfig` never reads `params.limit` (limit is not a `SEARCH_KEY`), so it always loaded 100 records correctly. But `fetchActivityData` used 80, causing a mismatch between the records shown and the activity counts in the column headers. ```js export const SEARCH_KEYS = ["comparison", "context", "domain", "groupBy", "orderBy"]; ``` The fix strips `params.limit` in `ActivityModel.load()` before passing params to `fetchActivityData`, so it falls back to `this.initialLimit (100)`. The pager `onUpdate` handler calls `fetchActivityData` directly with its own `params.limit` and is not affected. However, `ActivityController` never forwards `limit` to the model. This is why we always have `ActivityModel.DEFAULT_LIMIT (100)` without taking into account actions's limit. To fix this we have to add the limit via `this.props.limit`, as `ListController`. `useModelWithSampleData` already had the correct behavior by calling `model.load(getSearchParams(props))` which filters out non-search params like limit. In 19.0 useModel was updated to do the same, so the issue does not exist there. Link to 19.0 fix: https://github.com/odoo/odoo/pull/211697 opw-6281125 Forward-Port-Of: odoo/odoo#273929
7 changes
Resolved issues and error corrections
Steps to reproduce: - Set a Saudi company with a long legal name (e.g. "Golden Oasis Trading and Contracting Company Limited") - Make a POS order and look at the receipt QR code Issue: The QR code is drawn visibly smaller and denser than for a company with a short name, even though the image it sits in is the same 150px box: 90px of code at a 2px module pitch, against 111px at 3px. Cause: The ZATCA payload embeds the seller name, so a longer name needs a higher QR version, i.e. more mo
Original PR description
Steps to reproduce: - Set a Saudi company with a long legal name (e.g. "Golden Oasis Trading and Contracting Company Limited") - Make a POS order and look at the receipt QR code Issue: The QR code is drawn visibly smaller and denser than for a company with a short name, even though the image it sits in is the same 150px box: 90px of code at a 2px module pitch, against 111px at 3px. Cause: The ZATCA payload embeds the seller name, so a longer name needs a higher QR version, i.e. more modules. ZXing's BrowserQRCodeSvgWriter draws each module at a whole number of pixels of the canvas it is given (multiple = floor(canvas / (modules + 8))), so asking it for a fixed 150x150 or 200x200 canvas leaves a leftover margin that varies with the module count. The code shrinks as soon as the module count crosses a multiple of the canvas size. opw-6399878 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
When an order lands in the database with missing lines or payments, the logs only give its name and uuid. That is not enough to tell whether the front end sent an incomplete payload or the write was only partially applied, and the payload dump that would answer it sits behind _logger.debug, which is unusable in production since lowering the whole log level there is not an option. Log the ids of the lines and payments effectively added to an existing order, so a mismatch can be traced back to
Original PR description
When an order lands in the database with missing lines or payments, the logs only give its name and uuid. That is not enough to tell whether the front end sent an incomplete payload or the write was only partially applied, and the payload dump that would answer it sits behind _logger.debug, which is unusable in production since lowering the whole log level there is not an option. Log the ids of the lines and payments effectively added to an existing order, so a mismatch can be traced back to the write that produced it. Add the `point_of_sale.log_order_data` config parameter to log the full order payload at INFO. It stays disabled by default, as the payload contains customer data. Backport of the logging part of eebe9b131a68 and 2446e19bda17. opw-6401146 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Steps to reproduce the error: (Python Version: 3.14.X) - Install ``accounting_firm`` industry with demo data Code for server action to generate the error: ```py for i in [1]: try: pass except Exception: pass if i: pass ``` Traceback: ```py ValueError: forbidden opcode(s) in'...': JUMP_BACKWARD_NO_INTERRUPT ``` https://github.com/odoo/industry/blob/701f7595453772e42ce72aa75df346a504e5bd82/accounting_firm/demo/ir_actions_server.xml#L105-L10
Original PR description
Steps to reproduce the error: (Python Version: 3.14.X) - Install ``accounting_firm`` industry with demo data Code for server action to generate the error: ```py for i in [1]: try: pass except…
Steps to reproduce the error: (Python Version: 3.14.X)
- Install ``accounting_firm`` industry with demo data
Code for server action to generate the error:
```py
for i in [1]:
try:
pass
except Exception:
pass
if i:
pass
```
Traceback:
```py
ValueError: forbidden opcode(s) in'...': JUMP_BACKWARD_NO_INTERRUPT
```
https://github.com/odoo/industry/blob/701f7595453772e42ce72aa75df346a504e5bd82/accounting_firm/demo/ir_actions_server.xml#L105-L108
The server action contains a ``for`` loop with a ``try/except`` block followed by additional statements in the loop body,
this combination generates the ``JUMP_BACKWARD_NO_INTERRUPT`` opcode, which is not included in ``_SAFE_OPCODES`` at [1].
When the server action is evaluated by ``safe_eval``, it calls the ``assert_valid_codeobj`` method, which validates the compiled bytecode against ``_SAFE_OPCODES``. Since ``JUMP_BACKWARD_NO_INTERRUPT`` is not present in the allowed opcodes, ``assert_valid_codeobj()`` raises a ``ValueError`` at [2] before the server action is executed .
Solution:
``JUMP_BACKWARD_NO_INTERRUPT`` opcode is added in the ``_SAFE_OPCODES`` and it is also added in the ``_SAFE_QWEB_OPCODES``.
It was added in Python 3.11: https://docs.python.org/3/whatsnew/3.11.html#new-opcodes
``JUMP_BACKWARD_NO_INTERRUPT`` is a control-flow opcode that only changes
the interpreter's execution flow by jumping back to a previous instruction.
It is the equivalent to ``JUMP_BACKWARD`` opcode. Its only semantic difference is
that the interpreter does not perform an interrupt check at that instruction.
It does not introduce any new capabilities or perform operations such as
attribute access, imports, function calls, or object creation.
Ref: https://docs.python.org/3.12/library/dis.html#opcode-JUMP_BACKWARD_NO_INTERRUPT
Similar commit that adds some necessary opcodes:
https://github.com/odoo/odoo/commit/86498d24946e510025add5d24ef0d4bcce8ad05f
[1]: https://github.com/odoo/odoo/blob/2ccbc4660077bd48529e9de43d4309fbfafc75ca/odoo/tools/safe_eval.py#L135
[2]: https://github.com/odoo/odoo/blob/2ccbc4660077bd48529e9de43d4309fbfafc75ca/odoo/tools/safe_eval.py#L244-L246
sentry-7614026125
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#277192When we create a database with `-i web --skip-auto-install`, we run into CSS compilation errors due to undefined variables `$black` and `$gray-200`. This commit adds these two definitions. opw-6398398 Forward-Port-Of: odoo/odoo#279287
Original PR description
When we create a database with `-i web --skip-auto-install`, we run into CSS compilation errors due to undefined variables `$black` and `$gray-200`. This commit adds these two definitions. opw-6398398 Forward-Port-Of: odoo/odoo#279287
### Steps to reproduce 1. Install *Sales* and *Timesheets* 2. Create a service product: Invoicing Policy = *Based on Timesheets*, Create on Order = *Task* 3. Create a sale order for it (quantity 1) and confirm it 4. On the generated task, log **4.5 h on 15/06** and **3.5 h on 23/07** 5. *Create Invoice* with no timesheet period → 8 h, and post it 6. On that invoice: *Reverse* → *Partial Refund*, set the quantity to **3.5 h** and post it → 4.5 h invoiced 7. Log **1 h on 31/07** → 9 h delivered 8
Original PR description
### Steps to reproduce 1. Install *Sales* and *Timesheets* 2. Create a service product: Invoicing Policy = *Based on Timesheets*, Create on Order = *Task* 3. Create a sale order for it (quantity 1)…
### Steps to reproduce 1. Install *Sales* and *Timesheets* 2. Create a service product: Invoicing Policy = *Based on Timesheets*, Create on Order = *Task* 3. Create a sale order for it (quantity 1) and confirm it 4. On the generated task, log **4.5 h on 15/06** and **3.5 h on 23/07** 5. *Create Invoice* with no timesheet period → 8 h, and post it 6. On that invoice: *Reverse* → *Partial Refund*, set the quantity to **3.5 h** and post it → 4.5 h invoiced 7. Log **1 h on 31/07** → 9 h delivered 8. *Create Invoice* again, with a **Timesheets Period of 01/06 → 31/07** ### Current behavior The invoice bills **9 h**: the 4.5 h that were invoiced and not credited are billed a second time. ### Expected behavior The invoice bills **4.5 h** — the quantity delivered minus the quantity invoiced. ### Cause of the issue Posting a partial credit note clears `timesheet_invoice_id` on every timesheet the reversed invoice had linked (`sale_timesheet/models/account_move.py`, `action_post`), because a credit note carries a quantity and never a set of timesheets, so there is no way to tell which hours it credited. All of those hours therefore become candidates again in `_recompute_qty_to_invoice`, which assigns their sum to `qty_to_invoice` without comparing it to what is still due on the line. ### Fix Timesheet links cannot express a partially invoiced timesheet, so they are used only to select the hours a period concerns, while the quantity that may still be billed is `qty_delivered - qty_invoiced`. The period lookup is capped by that remainder, and kept at zero or above so that an over-invoiced line is corrected by a deliberate credit note rather than as a side effect of invoicing a period. ### Tests Five tests are added to `addons/sale_timesheet/tests/test_sale_timesheet.py`. Three of them fail without the fix: | test | without the fix | | --- | --- | | `test_period_invoice_does_not_rebill_refunded_invoice_hours` | `9.0 != 4.5` | | `test_period_invoice_after_refund_is_computed_per_line` | `4.0 != 1.5` | | `test_period_invoice_after_refund_of_an_over_invoiced_line` | `8.0 != 1.0` | The other two cover behaviour that is not exercised today and that the fix must not break: an over-invoiced line (which must be left out rather than credited, and must not prevent the other lines of the order from being invoiced) and the reversed invoice's own `invoice_date`, which must not influence the quantity billed for a period. The full `sale_timesheet` suite passes (86 tests). Forward-Port-Of: odoo/odoo#280536
Miscellaneous changes
In `settleSO`, two per-line bottlenecks were addressed: 1. `has_valued_move_ids` was called once per order line via a separate RPC, causing N sequential HTTP round-trips. It is now computed server-side inside `read_converted`, which is already called once for all lines. 2. `addLineToCurrentOrder` was awaited per line, yielding to the event loop each iteration and triggering a full Owl re-render for every line. Lines are now created directly, batching all mutations into a single render. `re
Original PR description
In `settleSO`, two per-line bottlenecks were addressed: 1. `has_valued_move_ids` was called once per order line via a separate RPC, causing N sequential HTTP round-trips. It is now computed server-side inside `read_converted`, which is already called once for all lines. 2. `addLineToCurrentOrder` was awaited per line, yielding to the event loop each iteration and triggering a full Owl re-render for every line. Lines are now created directly, batching all mutations into a single render. `recomputeOrderData()` is called once after the loop. `updatePrograms` is moved to a `pos_sale_loyalty` patch on `settleSO` so the loyalty concern belongs to the bridge module. opw-6319922 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
1 change
Resolved issues and error corrections
### Issue before this commit: When generating ZUGFeRD or Factur-X electronic invoices, including an HTML hyperlink (such as an email or website) in the document layout caused the final PDF to fail PDF/A validation. Portals would reject the file with the error: "An annotation dictionary does not contain the key F". ### Steps to reproduce the issue: 1. Download Accounting and l10n_de 2. Go to Contacts > set Tax ID for company Bloem GmbH and also for DE company 3. Activate ZUGFeRD from setti
Original PR description
### Issue before this commit: When generating ZUGFeRD or Factur-X electronic invoices, including an HTML hyperlink (such as an email or website) in the document layout caused the final PDF to fail…
### Issue before this commit: When generating ZUGFeRD or Factur-X electronic invoices, including an HTML hyperlink (such as an email or website) in the document layout caused the final PDF to fail PDF/A validation. Portals would reject the file with the error: "An annotation dictionary does not contain the key F". ### Steps to reproduce the issue: 1. Download Accounting and l10n_de 2. Go to Contacts > set Tax ID for company Bloem GmbH and also for DE company 3. Activate ZUGFeRD from settings 4. Go to settings and click on Configure Document Layout and insert something like <a href="mailto:info@company.de_skr03example.com"> info@company.de_skr03example.com</a> into the footer 5. Create an invoice to Bloem GmbH and send it through ZUGFeRD 6. Download PDF and upload it on https://www.portinvoice.com/ 7. See error: An annotation dictionary does not contain the key F. With the exception of annotation dictionaries whose subtype value is Popup, all annotation dictionaries must contain the key F. ### Cause of the issue: The underlying PDF generation engine creates hyperlinks as PDF annotations but omits the /F (Flags) key. During Odoo's PDF/A conversion process, these annotations were left unmodified. This violates the strict PDF/A specification, which mandates that all non-Popup annotations must explicitly define the /F key. ### Reason to introduce the fix: This fix ensures full PDF/A compliance by iterating through all page annotations and injecting the missing /F key for any annotation that is not a Popup. This allows users to safely include clickable links in their invoice templates without breaking electronic document validation. opw-6388212 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr