Daily updates from Odoo
Friday, July 17, 2026
354 changes
26 changes
New functionality added to Odoo
### Description of the issue/feature this PR addresses: Adds the Saudi ZATCA identification fields (building number, plot identification, identification scheme/number) to the portal billing address form so customers can fill them themselves. ### Current behavior before PR: The fields exist only in the backend; portal customers can't enter them. ### Desired behavior after PR is merged: Saudi-company customers can fill the fields on the portal (shown only when the billing country is SA
Original PR description
### Description of the issue/feature this PR addresses: Adds the Saudi ZATCA identification fields (building number, plot identification, identification scheme/number) to the portal billing address form so customers can fill them themselves. ### Current behavior before PR: The fields exist only in the backend; portal customers can't enter them. ### Desired behavior after PR is merged: Saudi-company customers can fill the fields on the portal (shown only when the billing country is SA; number disabled for the TIN scheme, with server-side validation). task-5188361 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270973
Enhancements to existing features
Following the introduction of role-based activity assignments, this commit reinforces the feature with test coverage and frontend cleanups. Key additions and improvements: - Backend Testing: Added unit tests covering edge cases, including user archiving reassignments, role deletion constraints, and dynamic role/user computations during activity scheduling. - Frontend Testing: Introduced Hoot tests to validate the systray "To Assign" counter and the assignment flow within the Acti
Original PR description
Following the introduction of role-based activity assignments, this commit reinforces the feature with test coverage and frontend cleanups. Key additions and improvements: - Backend Testing: Added unit tests covering edge cases, including user archiving reassignments, role deletion constraints, and dynamic role/user computations during activity scheduling. - Frontend Testing: Introduced Hoot tests to validate the systray "To Assign" counter and the assignment flow within the Activity Assign popover. - UI Consistency: Replaced hardcoded inline styles in the Activity Assign popover with standard Odoo UI utility classes. - JS Architecture: Centralized thread retrieval logic into the frontend Activity model for better encapsulation and component decoupling. Related to Task-6292889 (https://github.com/odoo/odoo/pull/269609) Task-6197665
Test lints should be deterministic so retrying them doesn't make sense. While lints are not the slowest, pylint can take 10~15mn in later branches, and with retrying that's half an hour (and some) to get a failure, which is all that in wasted staging and build time. Forward-Port-Of: odoo/odoo#276600
Original PR description
Test lints should be deterministic so retrying them doesn't make sense. While lints are not the slowest, pylint can take 10~15mn in later branches, and with retrying that's half an hour (and some) to get a failure, which is all that in wasted staging and build time. Forward-Port-Of: odoo/odoo#276600
Resolved issues and error corrections
**Steps to reproduce:** - Create a fiscal position T1, with detect automatically - Create another one T2, without the detect automatically - When looking at the fiscal positions list, make sure T1 is on top, followed by T2 - Go to the PoS settings, check flexible taxes - Put T2 as default and in allowed, don't put T1 in allowed - Go to the PoS, chose a customer - The fiscal position is T1 even though it's not allowed **Why the fix:** Currently, the fiscal position is chosen like thi
Original PR description
**Steps to reproduce:** - Create a fiscal position T1, with detect automatically - Create another one T2, without the detect automatically - When looking at the fiscal positions list, make sure T1 is…
**Steps to reproduce:** - Create a fiscal position T1, with detect automatically - Create another one T2, without the detect automatically - When looking at the fiscal positions list, make sure T1 is on top, followed by T2 - Go to the PoS settings, check flexible taxes - Put T2 as default and in allowed, don't put T1 in allowed - Go to the PoS, chose a customer - The fiscal position is T1 even though it's not allowed **Why the fix:** Currently, the fiscal position is chosen like this in order: - A FP specified on the customer's profile - A FP detected with the detect automatically setting - The default FP from the PoS settings When we have a tie, it's the first one in the fiscal positions list that is chosen. Before this commit, we did not check that the fiscal position was allowed to be used in the PoS, so we just fetched whatever fiscal position fit the best for a given customer and didn't check if we could actually use it. We now make sure that the fiscal position we try to use is allowed in the current PoS, and if it's not we fall back to the default one. opw-6032031 Forward-Port-Of: odoo/odoo#276151 Forward-Port-Of: odoo/odoo#271343
Steps to reproduce: =================== 1. Drop an "Events" block on a website page. 2. In edit mode, try selecting the inner text by clicking multiple times. => Uncaught client error: TypeError: Cannot read properties of undefined (reading 'nodeType'). Root cause: =========== When a mouse selection crosses an uncrossable element, the selection restriction plugin moves the focus to the deepest position of the element sibling adjacent to the uncrossable one. When the first selec
Original PR description
Steps to reproduce: =================== 1. Drop an "Events" block on a website page. 2. In edit mode, try selecting the inner text by clicking multiple times. => Uncaught client error: TypeError:…
Steps to reproduce: =================== 1. Drop an "Events" block on a website page. 2. In edit mode, try selecting the inner text by clicking multiple times. => Uncaught client error: TypeError: Cannot read properties of undefined (reading 'nodeType'). Root cause: =========== When a mouse selection crosses an uncrossable element, the selection restriction plugin moves the focus to the deepest position of the element sibling adjacent to the uncrossable one. When the first selected node is itself an uncrossable element (event cards are `div` elements) that has no previous/next element sibling, `node.previousElementSibling` is null and `tempFocusNode` was never assigned by a previous iteration, so it is undefined. `nodeSize` then reads `nodeType` on undefined and throws. The plugin only exists from saas-19.3, which is why the issue is not reproducible on earlier versions. Fix: ==== When there is no sibling to place the focus on, fall back to the boundary just outside the uncrossable node itself (`leftPos` when selecting left to right, `rightPos` when selecting right to left) instead of calling `nodeSize`/`getDeepestPosition` with undefined. opw-6362955 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#274739
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
When creating or editing a portal billing address, the Company Name field was pre-filled from `commercial_company_name`. For contacts without a parent company, the commercial partner is the contact itself, so the contact name was shown as the company name. Use the partner's actual parent company name instead, so the field stays empty when no company is linked while still showing the existing parent company when one exists. see: https://github.com/odoo/odoo/commit/18a59cf26f2d9400f76deec483
Original PR description
When creating or editing a portal billing address, the Company Name field was pre-filled from `commercial_company_name`. For contacts without a parent company, the commercial partner is the contact itself, so the contact name was shown as the company name. Use the partner's actual parent company name instead, so the field stays empty when no company is linked while still showing the existing parent company when one exists. see: https://github.com/odoo/odoo/commit/18a59cf26f2d9400f76deec483f6ddab87da0c55 Task-6372638 Forward-Port-Of: odoo/odoo#276621 Forward-Port-Of: odoo/odoo#274972
Before this commit: - When importing a **UBL** or **Factur-X (CII)** invoice, Odoo determines whether the document should be imported as an invoice or a credit note based on the `TaxExclusiveAmount (UBL)` /` TaxBasisTotalAmount (Factur-X)`. - In some rare cases, a valid invoice can contain a negative `TaxExclusiveAmount` / `TaxBasisTotalAmount` while still having a positive `TaxInclusiveAmount` / `GrandTotalAmount`. - In such situations, Odoo incorrectly imports the document as a credit note
Original PR description
Before this commit: - When importing a **UBL** or **Factur-X (CII)** invoice, Odoo determines whether the document should be imported as an invoice or a credit note based on the `TaxExclusiveAmount…
Before this commit: - When importing a **UBL** or **Factur-X (CII)** invoice, Odoo determines whether the document should be imported as an invoice or a credit note based on the `TaxExclusiveAmount (UBL)` /` TaxBasisTotalAmount (Factur-X)`. - In some rare cases, a valid invoice can contain a negative `TaxExclusiveAmount` / `TaxBasisTotalAmount` while still having a positive `TaxInclusiveAmount` / `GrandTotalAmount`. - In such situations, Odoo incorrectly imports the document as a credit note. Technical reason: - The method `_get_import_document_amount_sign()` uses `TaxExclusiveAmount` / `TaxBasisTotalAmount `to determine whether the imported document is an invoice or a refund. After this commit: - **UBL** now uses `TaxInclusiveAmount` instead of `TaxExclusiveAmount`, and **Factur-X** now uses `GrandTotalAmount `instead of `TaxBasisTotalAmount` to determine whether the document should be imported as an invoice or a credit note. - Prevent valid invoices with negative `TaxExclusiveAmount` / `TaxBasisTotalAmount` from being incorrectly converted into credit notes. Task-6321262 Forward-Port-Of: odoo/odoo#276307 Forward-Port-Of: odoo/odoo#271829
Before this commit, a crash could occur in kanban views but it required a very precise timing. If 2 renderings of the kanban renderer occurred at the same time, one coming from a group that has just been opened, and one coming from a new groupby being applied in the search view, we tried to scroll to the opened group to ensure that it is in the viewport, but we couldn't find it. Task~6391414 Forward-Port-Of: odoo/odoo#276750 Forward-Port-Of: odoo/odoo#276488
Original PR description
Before this commit, a crash could occur in kanban views but it required a very precise timing. If 2 renderings of the kanban renderer occurred at the same time, one coming from a group that has just been opened, and one coming from a new groupby being applied in the search view, we tried to scroll to the opened group to ensure that it is in the viewport, but we couldn't find it. Task~6391414 Forward-Port-Of: odoo/odoo#276750 Forward-Port-Of: odoo/odoo#276488
The translate button next to a translatable field saves the record before opening the translation dialog for its id. Since https://github.com/odoo/odoo/commit/a85ca9679e3855936afc66b034d05d75f672dd26 it saves record.model.root rather than the record itself. When the field belongs to a new record still edited inside an x2many, for example an answer added in the survey question popup, saving the root only saves the parent and the new line keeps no database id. The dialog then opens with the id set
Original PR description
The translate button next to a translatable field saves the record before opening the translation dialog for its id. Since https://github.com/odoo/odoo/commit/a85ca9679e3855936afc66b034d05d75f672dd26…
The translate button next to a translatable field saves the record before opening the translation dialog for its id. Since https://github.com/odoo/odoo/commit/a85ca9679e3855936afc66b034d05d75f672dd26 it saves record.model.root rather than the record itself. When the field belongs to a new record still edited inside an x2many, for example an answer added in the survey question popup, saving the root only saves the parent and the new line keeps no database id. The dialog then opens with the id set to false and calls update_field_translations on it, which builds WHERE id = false and the database rejects it with operator does not exist: integer = boolean. Such a record gets no id of its own, and after a save and reload there is no reliable way to match the saved line back to the one that was clicked, so the dialog can never open for it. A canTranslate getter in TranslationButton returns false for a new record whose model root is another record, which is exactly a line still edited inside an x2many, and the template only renders the button when it is true. The variant in editable lists, where model.root is a list rather than a record, was handled in https://github.com/odoo/odoo/commit/cb34b318004c3ca9db755d8dbbad429609220df3. Steps to reproduce: 1. Activate a second language in Settings > Translations > Languages 2. Open the Surveys app and create a survey 3. Add a question, then in the Answers tab add a line and type a value 4. Click the EN button next to the answer, fill the second language, and Save => RPC error operator does not exist: integer = boolean from WHERE id = false Ticket [link](https://www.odoo.com/odoo/project.task/6260427) opw-6260427 Forward-Port-Of: odoo/odoo#275401 Forward-Port-Of: odoo/odoo#267781
_reset_inventory() counter balances the stock implied by the move history when a product becomes storable, to reset the valuation of goods received while untracked. It assumed the product had no quants. But unticking Track Inventory does not clear the existing quants, so toggling it off then on again counter balances stock that is still on hand. The quants then desynchronize from their moves and the historical stock and valuation reports show quantities before the product ever existed. Onl
Original PR description
_reset_inventory() counter balances the stock implied by the move history when a product becomes storable, to reset the valuation of goods received while untracked. It assumed the product had no quants. But unticking Track Inventory does not clear the existing quants, so toggling it off then on again counter balances stock that is still on hand. The quants then desynchronize from their moves and the historical stock and valuation reports show quantities before the product ever existed. Only counter balance the part of the move history that is not already on hand. Steps to reproduce: - Create a storable product tracked by lots, 10 units on hand - Untick then re-tick "Track Inventory" on the product - Inventory > Reporting > Inventory at Date, pick a date before the product existed > The report shows 10 units on hand although there was no stock at that date. opw-6373051 Forward-Port-Of: odoo/odoo#275565
opw-6368979 Description of the issue/feature this PR addresses: Update the Worldline Cofidis payment method mapping to match the latest payment product ID defined in the Worldline documentation. Current behavior before PR: The Cofidis payment method was mapped to the outdated payment product ID (3012), causing payment requests to use an incorrect mapping. Desired behavior after PR is merged: The Cofidis payment method is mapped to the correct payment product ID (5129) as per
Original PR description
opw-6368979 Description of the issue/feature this PR addresses: Update the Worldline Cofidis payment method mapping to match the latest payment product ID defined in the Worldline documentation. Current behavior before PR: The Cofidis payment method was mapped to the outdated payment product ID (3012), causing payment requests to use an incorrect mapping. Desired behavior after PR is merged: The Cofidis payment method is mapped to the correct payment product ID (5129) as per the latest Worldline documentation, ensuring payment requests use the correct mapping. Forward-Port-Of: odoo/odoo#276132 Forward-Port-Of: odoo/odoo#275881
Problem: When a user attempts to create a new partner, and before saving the partner, they attempt to create a bank account in the same form view, they will be faced with a validation error for missing partner_id on the bank account. Solution: This commit solves this issue by only allowing the user to modify bank accounts for existing partners (with id). task-6373918 Forward-Port-Of: odoo/odoo#275062
Original PR description
Problem: When a user attempts to create a new partner, and before saving the partner, they attempt to create a bank account in the same form view, they will be faced with a validation error for missing partner_id on the bank account. Solution: This commit solves this issue by only allowing the user to modify bank accounts for existing partners (with id). task-6373918 Forward-Port-Of: odoo/odoo#275062
Hide the "All" option in the Apps search panel to avoid mixing Official Apps and Industries. Task ID: 5222706
Original PR description
Hide the "All" option in the Apps search panel to avoid mixing Official Apps and Industries. Task ID: 5222706
### Steps to Reproduce: - Insert text in the editor and select it. - Open the font size dropdown from the toolbar and select a font size. - Once editor focus returns to the editable area, type any character. - Observe that the editor throws a traceback. ### Purpose of this PR: - When selecting text & changing its font size or font family, typing character deletes selection and empties the formatting element. The editor converts this empty element to a pending format intent, but it pr
Original PR description
### Steps to Reproduce: - Insert text in the editor and select it. - Open the font size dropdown from the toolbar and select a font size. - Once editor focus returns to the editable area, type any…
### Steps to Reproduce: - Insert text in the editor and select it. - Open the font size dropdown from the toolbar and select a font size. - Once editor focus returns to the editable area, type any character. - Observe that the editor throws a traceback. ### Purpose of this PR: - When selecting text & changing its font size or font family, typing character deletes selection and empties the formatting element. The editor converts this empty element to a pending format intent, but it previously discarded the format's parameters (like class names, sizes, or font-family names). When editor later tried to apply format to newly typed character, it crashed because properties were undefined. ### Solution: - Implements a `getProps` method on font-size and font-family format specs to extract their properties before deletion. - Updates `convertEmptyFormatToPendingIntent` to save these properties. - Makes `addStyle` robust against empty properties using optional chaining. task-6377076 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Steps to reproduce: =================== - Ensure `pos_sale` is install. - Try to install `l10n_in_pos` Issue: ====== - `l10n_in_pos` cannot be installed because the XPath no longer matches the parent view. Cause: ====== - Since this PR odoo/odoo#259842, `pos_sale` replaces the `product_id` field with a `column` element in the POS order line view. Because of this, the XPath used by `l10n_in_pos` no longer matches when `pos_sale` is installed. Fix: ==== - Update the XPath to find
Original PR description
Steps to reproduce: =================== - Ensure `pos_sale` is install. - Try to install `l10n_in_pos` Issue: ====== - `l10n_in_pos` cannot be installed because the XPath no longer matches the parent view. Cause: ====== - Since this PR odoo/odoo#259842, `pos_sale` replaces the `product_id` field with a `column` element in the POS order line view. Because of this, the XPath used by `l10n_in_pos` no longer matches when `pos_sale` is installed. Fix: ==== - Update the XPath to find the `product_id` field anywhere inside the order line list, so it works with both the original view and the view modified by `pos_sale`. task-6352053 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
When the pos_discount module is installed while a session is open (the typical case: the user enables "Global Discounts" in the PoS settings while the PoS is running), `_default_discount_value_on_module_install` skipped the configs having a non-closed session (or any rescue session, even a closed one). Those configs ended up with the Global Discount feature enabled but no `discount_product_id`, and opening the PoS then raised "A discount product is needed to use the Global Discount feature." wit
Original PR description
When the pos_discount module is installed while a session is open (the typical case: the user enables "Global Discounts" in the PoS settings while the PoS is running),…
When the pos_discount module is installed while a session is open (the typical case: the user enables "Global Discounts" in the PoS settings while the PoS is running), `_default_discount_value_on_module_install` skipped the configs having a non-closed session (or any rescue session, even a closed one). Those configs ended up with the Global Discount feature enabled but no `discount_product_id`, and opening the PoS then raised "A discount product is needed to use the Global Discount feature." with no way to recover other than manually re-saving the PoS settings. The skip was introduced in 13.0 by 4c4adf472453 because, at the time, `pos.config.write()` refused any modification while a session was open, which made the module installation crash. That blanket restriction has since been narrowed to a few specific fields (`module_pos_restaurant`, `payment_method_ids`, `active`), so writing `discount_product_id` on a config with an open session is now perfectly valid. Remove the obsolete exclusion so that all configs get the default discount product at install time, regardless of their session state. opw-6385274 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276537
Update the XPath of the empty header of the 'Pay Now' button column to the last header of the table. Use a deterministic XPath expression for the 'Pay Now' button column cell. as the last cell of the table. Forward-Port-Of: odoo/odoo#272710
Original PR description
Update the XPath of the empty header of the 'Pay Now' button column to the last header of the table. Use a deterministic XPath expression for the 'Pay Now' button column cell. as the last cell of the table. Forward-Port-Of: odoo/odoo#272710
**Steps to reproduce:** - Make a product with a category - In the category, make the inventory valuation to automated - Set the income and expense account - Set a cost for the product - Make a fiscal position and set it as default for the PoS - In the Account Mapping tab, map the income and expense to two other accounts - Go to the PoS - Make a sale for that product, without invoice - Close the session and in the backend check the session - Check the journal entries - The income acc
Original PR description
**Steps to reproduce:** - Make a product with a category - In the category, make the inventory valuation to automated - Set the income and expense account - Set a cost for the product - Make a fiscal…
**Steps to reproduce:** - Make a product with a category - In the category, make the inventory valuation to automated - Set the income and expense account - Set a cost for the product - Make a fiscal position and set it as default for the PoS - In the Account Mapping tab, map the income and expense to two other accounts - Go to the PoS - Make a sale for that product, without invoice - Close the session and in the backend check the session - Check the journal entries - The income account has been mapped to the fiscal position's - The outcome account stayed the same as in the category's **Why the fix:** When we invoice an order, the income and expense accounts are immediately updated, in a different place than if it has not been invoiced. At the session's closure, we update the accounts for every order that hasn't been invoiced. In this flow, the account mapping defined on the fiscal position was not applied, so we took the one defined on the product's category. The income account was already mapped as we need to do it earlier than the session closure, so it had already been set as the right one before our flow. For the expense account, we only need it at this specific time, so we can map it as the session's closure. We now map the account depending on the fiscal position if we are able to find one, otherwise, we use the category's default as we did before. opw-6171677 Forward-Port-Of: odoo/odoo#276715 Forward-Port-Of: odoo/odoo#266700
When registering a payment for multiple bills from different partners, the `source_currency_id` becomes empty/evaluates differently in the wizard. Because of this, the `currency_conversion_div` `invisible` condition failed, causing the UI to redundantly display the exchange rate even when the currencies were identical (e.g., showing '1 EUR = 1 EUR'). This commit simplifies the invisible condition to only check if the `currency_id` matches the `company_currency_id`, keeping the UI clean. se
Original PR description
When registering a payment for multiple bills from different partners, the `source_currency_id` becomes empty/evaluates differently in the wizard. Because of this, the `currency_conversion_div` `invisible` condition failed, causing the UI to redundantly display the exchange rate even when the currencies were identical (e.g., showing '1 EUR = 1 EUR'). This commit simplifies the invisible condition to only check if the `currency_id` matches the `company_currency_id`, keeping the UI clean. see ent pr- https://github.com/odoo/enterprise/pull/118183 task- 6237870 Forward-Port-Of: odoo/odoo#266072
Registration token now needs to be valid for much longer than 1 hour due to manual verifications Forward-Port-Of: odoo/odoo#276550
Original PR description
Registration token now needs to be valid for much longer than 1 hour due to manual verifications Forward-Port-Of: odoo/odoo#276550
*: website, website_sale `forEach` is a synchronous operation, so it doesn't support promises. We refactor its usage to use `for` loops. task-4794299 Forward-Port-Of: odoo/odoo#276590 Forward-Port-Of: odoo/odoo#275262
Original PR description
*: website, website_sale `forEach` is a synchronous operation, so it doesn't support promises. We refactor its usage to use `for` loops. task-4794299 Forward-Port-Of: odoo/odoo#276590 Forward-Port-Of: odoo/odoo#275262
Before this commit, in the test `test_ticket_price_with_currency_conversion` the exchange rate was not correctly assigned because `rate_ids` was not initialized when running tests on app install. As a result the the rate was left empty and the currency conversion was not applied. This commit creates the rate_ids in the test to make sure the rates are correctly applied. Runbot error: https://runbot.odoo.com/odoo/error/242431 --- I confirm I have signed the CLA and read the PR guidelines
Original PR description
Before this commit, in the test `test_ticket_price_with_currency_conversion` the exchange rate was not correctly assigned because `rate_ids` was not initialized when running tests on app install. As a result the the rate was left empty and the currency conversion was not applied. This commit creates the rate_ids in the test to make sure the rates are correctly applied. Runbot error: https://runbot.odoo.com/odoo/error/242431 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276218 Forward-Port-Of: odoo/odoo#265503
Steps to reproduce: - Create a promotions program with a rule granting 1 point per currency unit spent (minimum 2 items and 50.00 spent) and a reward "10% discount on the cheapest product" for 1 point - Open a PoS session and add 2 products to trigger the program - Keep adding products to the order Issue: A new "10% on the cheapest product" line is added for every product added. In eCommerce and Sales the discount is only applied once. Cause: The rule grants far more points than the r
Original PR description
Steps to reproduce: - Create a promotions program with a rule granting 1 point per currency unit spent (minimum 2 items and 50.00 spent) and a reward "10% discount on the cheapest product" for 1…
Steps to reproduce: - Create a promotions program with a rule granting 1 point per currency unit spent (minimum 2 items and 50.00 spent) and a reward "10% discount on the cheapest product" for 1 point - Open a PoS session and add 2 products to trigger the program - Keep adding products to the order Issue: A new "10% on the cheapest product" line is added for every product added. In eCommerce and Sales the discount is only applied once. Cause: The rule grants far more points than the reward costs, so the reward remains claimable after being applied. The auto-claim loop of `updateRewards` therefore re-applies it on every order change, stacking one discount line per change. The already-applied check in `getClaimableRewards` only covered 'coupons' programs, while `_get_claimable_rewards` in sale_loyalty also skips discount rewards already present on the order lines. Solution: When auto-claiming, skip discount rewards that are already applied on the order, unless they belong to a payment program (ewallet, gift card). The reward can still be claimed manually several times through the Reward button by spending more points. opw-6380421 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276300
Steps to reproduce: - Create a partner without pincode - Create a picking and Challan for that partner - Click print Will result in the following traceback- ```py Traceback (most recent call last): File "<1103>", line 710, in template_l10n_in_ewaybill_report_ewaybill_1103 File "<1103>", line 692, in template_l10n_in_ewaybill_report_ewaybill_1103_content File "<1103>", line 674, in template_l10n_in_ewaybill_report_ewaybill_1103_t_call_0 File "<1103>", line 86, in template_l10n
Original PR description
Steps to reproduce: - Create a partner without pincode - Create a picking and Challan for that partner - Click print Will result in the following traceback- ```py Traceback (most recent call last):…
Steps to reproduce:
- Create a partner without pincode
- Create a picking and Challan for that partner
- Click print
Will result in the following traceback-
```py
Traceback (most recent call last):
File "<1103>", line 710, in template_l10n_in_ewaybill_report_ewaybill_1103
File "<1103>", line 692, in template_l10n_in_ewaybill_report_ewaybill_1103_content
File "<1103>", line 674, in template_l10n_in_ewaybill_report_ewaybill_1103_t_call_0
File "<1103>", line 86, in template_l10n_in_ewaybill_report_ewaybill_1103_t_call_1
File "/home/odoo/odoo18/community/addons/l10n_in_ewaybill/models/l10n_in_ewaybill.py", line 727, in _ewaybill_generate_direct_json
**self._prepare_ewaybill_base_json_payload(),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/odoo18/community/addons/l10n_in_ewaybill_stock/models/l10n_in_ewaybill.py", line 289, in _prepare_ewaybill_base_json_payload
ewaybill_json = super()._prepare_ewaybill_base_json_payload()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/odoo18/community/addons/l10n_in_ewaybill/models/l10n_in_ewaybill.py", line 669, in _prepare_ewaybill_base_json_payload
**prepare_details(
^^^^^^^^^^^^^^^^
File "/home/odoo/odoo18/community/addons/l10n_in_ewaybill/models/l10n_in_ewaybill.py", line 641, in prepare_details
f"{place}{key}": fun(partner, place) if key == "StateCode" else fun(partner)
^^^^^^^^^^^^
File "/home/odoo/odoo18/community/addons/l10n_in_ewaybill/models/l10n_in_ewaybill.py", line 674, in <lambda>
"Pincode": lambda p: int(p.zip) if p.country_id.code == "IN" else 999999,
^^^^^^^^^^
ValueError: invalid literal for int() with base 10: ''
```
In this commit, we resolve the traceback
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#276180Miscellaneous changes
### Description: Processing batch payments for multiple invoices triggers the `_increase_rank` method across numerous partners. Previously, this could lead to Out-Of-Memory (OOM) errors in databases with extensive partner hierarchies (parent/child relationships), primarily due to cascaded writes triggered by `_commercial_sync_to_descendants`. This commit optimizes the rank increment process, significantly reducing both memory consumption and execution time. ### Benchmark: | Partner C
Original PR description
### Description: Processing batch payments for multiple invoices triggers the `_increase_rank` method across numerous partners. Previously, this could lead to Out-Of-Memory (OOM) errors in databases with extensive partner hierarchies (parent/child relationships), primarily due to cascaded writes triggered by `_commercial_sync_to_descendants`. This commit optimizes the rank increment process, significantly reducing both memory consumption and execution time. ### Benchmark: | Partner Count | Time Before | Time After | Memory After | |---------------|-------------|------------|--------------| | 191,946 | 2 min | 47s | 111 Mb | | 393,509 | OOM | 1 min 45 | 216 Mb | ### Reference: opw-5152687 Forward-Port-Of: odoo/odoo#275718 Forward-Port-Of: odoo/odoo#259334
22 changes
New functionality added to Odoo
### Description of the issue/feature this PR addresses: Adds the Saudi ZATCA identification fields (building number, plot identification, identification scheme/number) to the portal billing address form so customers can fill them themselves. ### Current behavior before PR: The fields exist only in the backend; portal customers can't enter them. ### Desired behavior after PR is merged: Saudi-company customers can fill the fields on the portal (shown only when the billing country is SA
Original PR description
### Description of the issue/feature this PR addresses: Adds the Saudi ZATCA identification fields (building number, plot identification, identification scheme/number) to the portal billing address form so customers can fill them themselves. ### Current behavior before PR: The fields exist only in the backend; portal customers can't enter them. ### Desired behavior after PR is merged: Saudi-company customers can fill the fields on the portal (shown only when the billing country is SA; number disabled for the TIN scheme, with server-side validation). task-5188361 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270973
Enhancements to existing features
Test lints should be deterministic so retrying them doesn't make sense. While lints are not the slowest, pylint can take 10~15mn in later branches, and with retrying that's half an hour (and some) to get a failure, which is all that in wasted staging and build time. Forward-Port-Of: odoo/odoo#276600
Original PR description
Test lints should be deterministic so retrying them doesn't make sense. While lints are not the slowest, pylint can take 10~15mn in later branches, and with retrying that's half an hour (and some) to get a failure, which is all that in wasted staging and build time. Forward-Port-Of: odoo/odoo#276600
Move French e-invoicing technical statuses out of the main invoice UI and into the chatter. The PPF status is kept available in debug mode for troubleshooting, while regular users get a concise chatter summary with the PA status, PPF status, and any returned errors. Duplicate Lifecycle XML attachments are no longer posted in the chatter. Task-6273270 Forward-Port-Of: odoo/odoo#276770 Forward-Port-Of: odoo/odoo#269465
Original PR description
Move French e-invoicing technical statuses out of the main invoice UI and into the chatter. The PPF status is kept available in debug mode for troubleshooting, while regular users get a concise chatter summary with the PA status, PPF status, and any returned errors. Duplicate Lifecycle XML attachments are no longer posted in the chatter. Task-6273270 Forward-Port-Of: odoo/odoo#276770 Forward-Port-Of: odoo/odoo#269465
Resolved issues and error corrections
**Description of the problem** The age verification popup snippet (`s_age_verification_popup`) does not work properly when dropped into the `#product_details` element of a product page. In particular, the popup is rendered below the blurred background and cannot be interacted with (it should be rendered above the blur instead). **How to reproduce** 1. Open a product page. 2. Drop `s_age_verification_popup` into the product details. 3. The popup is rendered below the blurred background.
Original PR description
**Description of the problem** The age verification popup snippet (`s_age_verification_popup`) does not work properly when dropped into the `#product_details` element of a product page. In…
**Description of the problem** The age verification popup snippet (`s_age_verification_popup`) does not work properly when dropped into the `#product_details` element of a product page. In particular, the popup is rendered below the blurred background and cannot be interacted with (it should be rendered above the blur instead). **How to reproduce** 1. Open a product page. 2. Drop `s_age_verification_popup` into the product details. 3. The popup is rendered below the blurred background. **Why the problem happens** The `s_age_verification_popup` snippet applies the blur effect to the `#wrapwrap` element. The popup is expected to be rendered above the blur thanks to its `z-index`. However, `z-index` only applies within the same stacking context. A child element cannot be rendered above elements outside its parent's stacking context, regardless of how high its own `z-index` is. Since `#product_details` has a defined `z-index`, it creates a stacking context. As a result, the popup, which is a child of `#product_details`, is rendered below the blur element. **Fix** This commit adds an SCSS rule to `#product_details` so that it no longer creates a stacking context when it contains an open age verification popup. To prevent the stacking context from being created, `z-index` is set to `auto` and `position` to `relative`. task-6358990
**Purpose of this PR:** Before this PR, the Voice detection sensitivity indicator in call settings could get stuck showing the last detected level after clicking "Stop" right after "Test". The `AudioWorkletNode`'s port kept receiving tic messages briefly after `disconnect()`, since disconnecting only unroutes the audio graph and does not stop the worklet from posting pending messages. <img width="546" height="73" alt="voice_test_bug" src="https://github.com/user-attachments/assets/05a10d23
Original PR description
**Purpose of this PR:** Before this PR, the Voice detection sensitivity indicator in call settings could get stuck showing the last detected level after clicking "Stop" right after "Test". The…
**Purpose of this PR:** Before this PR, the Voice detection sensitivity indicator in call settings could get stuck showing the last detected level after clicking "Stop" right after "Test". The `AudioWorkletNode`'s port kept receiving tic messages briefly after `disconnect()`, since disconnecting only unroutes the audio graph and does not stop the worklet from posting pending messages. <img width="546" height="73" alt="voice_test_bug" src="https://github.com/user-attachments/assets/05a10d23-fe60-4a85-b906-bfec6d235ec5" /> Steps to reproduce: 1. Open Voice & Video Settings. 2. Start the Voice detection sensitivity test. 3. Quickly click Stop immediately after clicking Test. 4. It may take a few tries, but eventually the Voice detection sensitivity indicator remains stuck at the last detected level. > [!NOTE] > this is timing-dependent. A tic message must already be in-flight from the worklet thread when `disconnect()` runs, so it won't happen every attempt. This race condition existed in the `disconnect` callback of `_loadAudioWorkletProcessor` since #66611, but stayed silent until #183969 introduced the Voice detection sensitivity feature in call settings, exposing it. This PR clears `port.onmessage` before disconnecting so late tic messages can no longer update the Voice detection sensitivity indicator after monitoring has stopped. Forward-Port-Of: odoo/odoo#275933
Before this commit: If a user, with crm.leads linked to it, decided to request a password reset AND during that password reset process decided to activate the google oauth for their account, it would cause a crash. The reason is, during the password rest + oauth activation, self.env.user is an empty record set, which obviously will fail during the _is_portal check, due to its call to ensure_one() opw-6347228 Forward-Port-Of: odoo/odoo#275375
Original PR description
Before this commit: If a user, with crm.leads linked to it, decided to request a password reset AND during that password reset process decided to activate the google oauth for their account, it would cause a crash. The reason is, during the password rest + oauth activation, self.env.user is an empty record set, which obviously will fail during the _is_portal check, due to its call to ensure_one() opw-6347228 Forward-Port-Of: odoo/odoo#275375
\* : html_editor Commit [1]: Steps to reproduce: replacing image stuck issue when deleted 1. Go to Website > Edit. 2. Add any picture snippet (e.g., Text-Image). 3. Click the 'Replace' button and upload an image. 4. Open the media dialog again and delete the uploaded image. 5. Click the 'Discard' button. 6. Try to save the changes. Issue: - The website gets stuck in the same position and does not allow saving. - In the Python terminal, a missing error warning appears because the
Original PR description
\* : html_editor Commit [1]: Steps to reproduce: replacing image stuck issue when deleted 1. Go to Website > Edit. 2. Add any picture snippet (e.g., Text-Image). 3. Click the 'Replace' button and…
\* : html_editor Commit [1]: Steps to reproduce: replacing image stuck issue when deleted 1. Go to Website > Edit. 2. Add any picture snippet (e.g., Text-Image). 3. Click the 'Replace' button and upload an image. 4. Open the media dialog again and delete the uploaded image. 5. Click the 'Discard' button. 6. Try to save the changes. Issue: - The website gets stuck in the same position and does not allow saving. - In the Python terminal, a missing error warning appears because the image is deleted from both `ir.ui.view` and `ir.attachment`. Expected behaviour: - Saving should be allowed with a default image, that is similar to other images. This commit catch the warning response and replaces the deleted image, allowing the website to save changes without getting stuck. Commit [2]: resolve traceback when leaving edit mode via browser Steps to reproduce: 1. Go to Website > Edit. 2. Open the snippet modal and select any snippet. 3. Press the 'Back' button in your browser. 4. A dialog will appear asking to discard changes; click 'OK'. 5. A traceback error occurs, and an empty space appears in the editor. Issue: - Previously, a commit addressed a similar scenario, but that time the browser had an event listener bind on hashchange. - Now, that `hashchange` event of browser has been replaced with `popstate`, which triggers before the 'window' event listener. - As a result, the editor is left in an unstable state, causing a traceback error. Solution: - This commit ensures the 'window' event executes before the browser event. - It verifies if the editor is open and forces a `skipLoad`, preventing the `route_change` call in the browser. task-4570164 Forward-Port-Of: odoo/odoo#275693 Forward-Port-Of: odoo/odoo#199193
Step to reproduce: - install l10n_sa_edi_pos in fresh db, make sure to have a saudi arabia company - go to sales journal -> zatca page (journal should not be onboarded_ - open pos and make a sale -> print receipt Observaton: - in receipt notice a label "This is not a legal document" - such labels should only appear when we are on phase 2 i.e. we onboard journal Cause: - after the commit [1] we removed dependency b/w account_edi and l10n_sa - Also, visibilty of label dependends on
Original PR description
Step to reproduce: - install l10n_sa_edi_pos in fresh db, make sure to have a saudi arabia company - go to sales journal -> zatca page (journal should not be onboarded_ - open pos and make a sale ->…
Step to reproduce: - install l10n_sa_edi_pos in fresh db, make sure to have a saudi arabia company - go to sales journal -> zatca page (journal should not be onboarded_ - open pos and make a sale -> print receipt Observaton: - in receipt notice a label "This is not a legal document" - such labels should only appear when we are on phase 2 i.e. we onboard journal Cause: - after the commit [1] we removed dependency b/w account_edi and l10n_sa - Also, visibilty of label dependends on - `l10n_sa_not_legal` , `code_sa` and `l10n_gcc_is_settlement` - here we never checked, which phase we are in [1] https://github.com/odoo/odoo/commit/1c463a4d5b513fcf17a9ea0ea26bf50f68102193 fix: - we keep track, if config's journal is onboarderd or not (by presense of csid) and only then we decided to print label (if applicable) Before: <img width="1829" height="1040" alt="image" src="https://github.com/user-attachments/assets/581923c0-513c-45dc-8b77-6ab171dc0522" /> After: <img width="1827" height="1037" alt="image" src="https://github.com/user-attachments/assets/dfdfde21-c17d-4468-9629-9ac1fd6d691c" /> opw-6317777 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Problem: After updating a file name, the link popover still shows the original file name. Cause: The link popover always displays the attachment name instead of the current link content. Solution: Use the link content as the popover title so it reflects the updated file title. Steps to reproduce: - Go to To-Do → Create New. - Upload a file. - Change its title. - Observe that the title shown in the link popover still uses the original file name. task-6213840 --- I confirm I
Original PR description
Problem: After updating a file name, the link popover still shows the original file name. Cause: The link popover always displays the attachment name instead of the current link content. Solution: Use the link content as the popover title so it reflects the updated file title. Steps to reproduce: - Go to To-Do → Create New. - Upload a file. - Change its title. - Observe that the title shown in the link popover still uses the original file name. task-6213840 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276687 Forward-Port-Of: odoo/odoo#264127
When creating or editing a portal billing address, the Company Name field was pre-filled from `commercial_company_name`. For contacts without a parent company, the commercial partner is the contact itself, so the contact name was shown as the company name. Use the partner's actual parent company name instead, so the field stays empty when no company is linked while still showing the existing parent company when one exists. see: https://github.com/odoo/odoo/commit/18a59cf26f2d9400f76deec483
Original PR description
When creating or editing a portal billing address, the Company Name field was pre-filled from `commercial_company_name`. For contacts without a parent company, the commercial partner is the contact itself, so the contact name was shown as the company name. Use the partner's actual parent company name instead, so the field stays empty when no company is linked while still showing the existing parent company when one exists. see: https://github.com/odoo/odoo/commit/18a59cf26f2d9400f76deec483f6ddab87da0c55 Task-6372638 Forward-Port-Of: odoo/odoo#276621 Forward-Port-Of: odoo/odoo#274972
Before this commit: - When importing a **UBL** or **Factur-X (CII)** invoice, Odoo determines whether the document should be imported as an invoice or a credit note based on the `TaxExclusiveAmount (UBL)` /` TaxBasisTotalAmount (Factur-X)`. - In some rare cases, a valid invoice can contain a negative `TaxExclusiveAmount` / `TaxBasisTotalAmount` while still having a positive `TaxInclusiveAmount` / `GrandTotalAmount`. - In such situations, Odoo incorrectly imports the document as a credit note
Original PR description
Before this commit: - When importing a **UBL** or **Factur-X (CII)** invoice, Odoo determines whether the document should be imported as an invoice or a credit note based on the `TaxExclusiveAmount…
Before this commit: - When importing a **UBL** or **Factur-X (CII)** invoice, Odoo determines whether the document should be imported as an invoice or a credit note based on the `TaxExclusiveAmount (UBL)` /` TaxBasisTotalAmount (Factur-X)`. - In some rare cases, a valid invoice can contain a negative `TaxExclusiveAmount` / `TaxBasisTotalAmount` while still having a positive `TaxInclusiveAmount` / `GrandTotalAmount`. - In such situations, Odoo incorrectly imports the document as a credit note. Technical reason: - The method `_get_import_document_amount_sign()` uses `TaxExclusiveAmount` / `TaxBasisTotalAmount `to determine whether the imported document is an invoice or a refund. After this commit: - **UBL** now uses `TaxInclusiveAmount` instead of `TaxExclusiveAmount`, and **Factur-X** now uses `GrandTotalAmount `instead of `TaxBasisTotalAmount` to determine whether the document should be imported as an invoice or a credit note. - Prevent valid invoices with negative `TaxExclusiveAmount` / `TaxBasisTotalAmount` from being incorrectly converted into credit notes. Task-6321262 Forward-Port-Of: odoo/odoo#276307 Forward-Port-Of: odoo/odoo#271829
_reset_inventory() counter balances the stock implied by the move history when a product becomes storable, to reset the valuation of goods received while untracked. It assumed the product had no quants. But unticking Track Inventory does not clear the existing quants, so toggling it off then on again counter balances stock that is still on hand. The quants then desynchronize from their moves and the historical stock and valuation reports show quantities before the product ever existed. Onl
Original PR description
_reset_inventory() counter balances the stock implied by the move history when a product becomes storable, to reset the valuation of goods received while untracked. It assumed the product had no quants. But unticking Track Inventory does not clear the existing quants, so toggling it off then on again counter balances stock that is still on hand. The quants then desynchronize from their moves and the historical stock and valuation reports show quantities before the product ever existed. Only counter balance the part of the move history that is not already on hand. Steps to reproduce: - Create a storable product tracked by lots, 10 units on hand - Untick then re-tick "Track Inventory" on the product - Inventory > Reporting > Inventory at Date, pick a date before the product existed > The report shows 10 units on hand although there was no stock at that date. opw-6373051 Forward-Port-Of: odoo/odoo#275565
Problem: When a user attempts to create a new partner, and before saving the partner, they attempt to create a bank account in the same form view, they will be faced with a validation error for missing partner_id on the bank account. Solution: This commit solves this issue by only allowing the user to modify bank accounts for existing partners (with id). task-6373918 Forward-Port-Of: odoo/odoo#275062
Original PR description
Problem: When a user attempts to create a new partner, and before saving the partner, they attempt to create a bank account in the same form view, they will be faced with a validation error for missing partner_id on the bank account. Solution: This commit solves this issue by only allowing the user to modify bank accounts for existing partners (with id). task-6373918 Forward-Port-Of: odoo/odoo#275062
### Steps to reproduce: - In the settings enable Multi-Steps route - Unarchive the MTO route and set its production rule in MTSO - Create 3 products: P1, P2 and COMP all using the MTO route - Create 2 BOM's, one for P1 and one for P2: 1 X COMP - Put 2 units of COMP in stock and add an empty bom (to trigger a child MO creation in case the MTSO route is triggered) - Create and confirm a sale order for: 1 x P1 and 1 X P2 #### > An MO was generated for both product but P2 also generated a c
Original PR description
### Steps to reproduce: - In the settings enable Multi-Steps route - Unarchive the MTO route and set its production rule in MTSO - Create 3 products: P1, P2 and COMP all using the MTO route - Create…
### Steps to reproduce: - In the settings enable Multi-Steps route - Unarchive the MTO route and set its production rule in MTSO - Create 3 products: P1, P2 and COMP all using the MTO route - Create 2 BOM's, one for P1 and one for P2: 1 X COMP - Put 2 units of COMP in stock and add an empty bom (to trigger a child MO creation in case the MTSO route is triggered) - Create and confirm a sale order for: 1 x P1 and 1 X P2 #### > An MO was generated for both product but P2 also generated a child MO for 1 unit of COMP instead of using the available unit Cause of the issue: The issue happens in the `_prepare_procurement_qty` which incorrectly assess that 1 unit of COMP will be required. The issue has been introduced by commit https://github.com/odoo/odoo/commit/e30fb722c00805e7226d2ee9e3e587b3c2204840 which introduced a dictionary to keep track of units of products that will be used by the confirmation process of other concurrent mtso moves: https://github.com/odoo/odoo/blob/71f0715bd5e29e976a1e8bfa7c4fa6e04735ebd7/addons/stock/models/stock_move.py#L1683-L1689 https://github.com/odoo/odoo/blob/71f0715bd5e29e976a1e8bfa7c4fa6e04735ebd7/addons/stock/models/stock_move.py#L1712-L1715 https://github.com/odoo/odoo/blob/71f0715bd5e29e976a1e8bfa7c4fa6e04735ebd7/addons/stock/models/stock_move.py#L1810-L1814 While by design this propagates the information used by other mtso moves in a common `_action_confirm` stack, the issue that we encounter is that this quantity is only relevant to be substracted to the free_qty when the unit is not yet reserved and hence already accounted negatively in `free_qty`. However, in the present case, confirming the receipt of P1 and P2 will confirm both moves simultaneously, triggering a common `_run_manufacture` to generate both an MO for P1 and for P2. At this point the dictionary `consumed_from_stock_dict` is shared in both MO's confirmation but since the MO's are confirmed sequentially rather than in batch: https://github.com/odoo/odoo/blob/71f0715bd5e29e976a1e8bfa7c4fa6e04735ebd7/addons/mrp/models/stock_rule.py#L122-L125 The confirmation of the MO of P1 will update the `consumed_from_stock_dict` for 1 unit of COMP and will also reserve 1 unit of COMP before the MO of P2 is confirmed (and calls the `_prepare_procurement_qty`) to determine how many units of COMP are till available. This leads to the incorrect conclusion that 1 - 1 = 0 units are still available to fulfill the demand of P2. opw-6370298 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#275539
In [1], the need to build a registry for outgoing messages was removed, but `acquire_cursor` wasn't adapted. As a result, we might end up building more registries than needed, blocking the gevent server CPU. This commit fixes this issue. [1]: https://github.com/odoo/odoo/pull/235746 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
Original PR description
In [1], the need to build a registry for outgoing messages was removed, but `acquire_cursor` wasn't adapted. As a result, we might end up building more registries than needed, blocking the gevent server CPU. This commit fixes this issue. [1]: https://github.com/odoo/odoo/pull/235746 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#276166
Update the XPath of the empty header of the 'Pay Now' button column to the last header of the table. Use a deterministic XPath expression for the 'Pay Now' button column cell. as the last cell of the table. Forward-Port-Of: odoo/odoo#272710
Original PR description
Update the XPath of the empty header of the 'Pay Now' button column to the last header of the table. Use a deterministic XPath expression for the 'Pay Now' button column cell. as the last cell of the table. Forward-Port-Of: odoo/odoo#272710
The partner credit limit warning on quotations and customer invoices depends on which company the user is currently working in, instead of the company of the document itself. Steps to reproduce: - Enable Sale Credit Limit in the settings of My Company (San Francisco) - Set a Credit Limit of 100 on a customer, e.g. Deco Addict - Create a draft quotation of 500 for that customer => The credit limit warning banner is displayed, as expected - Switch the active company to any other company, fo
Original PR description
The partner credit limit warning on quotations and customer invoices depends on which company the user is currently working in, instead of the company of the document itself. Steps to reproduce: -…
The partner credit limit warning on quotations and customer invoices depends on which company the user is currently working in, instead of the company of the document itself. Steps to reproduce: - Enable Sale Credit Limit in the settings of My Company (San Francisco) - Set a Credit Limit of 100 on a customer, e.g. Deco Addict - Create a draft quotation of 500 for that customer => The credit limit warning banner is displayed, as expected - Switch the active company to any other company, for example My Company (Chicago), keeping access to both companies - Open the same quotation again => The warning banner is gone, although neither the quotation nor the customer changed The credit fields used to build the warning are evaluated against the user's active company: credit_limit is a company-dependent field, and credit / credit_to_invoice are computed on the receivables of the current company. When the active company is not the document's company, the warning is checked against the wrong ledger and the wrong limit, so it can disappear on an over-limit customer or show up for a healthy one. Both computes already contain the line that was meant to handle this, but the result of with_company() was discarded, making it a no-op. Assign it, as every other compute in these files already does, so the warning is always evaluated in the document's company. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276308
Registration token now needs to be valid for much longer than 1 hour due to manual verifications Forward-Port-Of: odoo/odoo#276550
Original PR description
Registration token now needs to be valid for much longer than 1 hour due to manual verifications Forward-Port-Of: odoo/odoo#276550
*: website, website_sale `forEach` is a synchronous operation, so it doesn't support promises. We refactor its usage to use `for` loops. task-4794299 Forward-Port-Of: odoo/odoo#276590 Forward-Port-Of: odoo/odoo#275262
Original PR description
*: website, website_sale `forEach` is a synchronous operation, so it doesn't support promises. We refactor its usage to use `for` loops. task-4794299 Forward-Port-Of: odoo/odoo#276590 Forward-Port-Of: odoo/odoo#275262
**Steps to reproduce:** - create storable avco product - set the cost to 10 - set an onhand quantity of 100 in WH/stock - create another warehouse (if you don't already have another one) - create an internal transfer from WH/Stock to WH2 - open the 'stock' view - click on inventory at date - confirm **Current behavior:** the total value is 11.000 **Expected behavior:** total value should be 10.000 **Cause of the issue:** To compute the total_value of the product, _co
Original PR description
**Steps to reproduce:** - create storable avco product - set the cost to 10 - set an onhand quantity of 100 in WH/stock - create another warehouse (if you don't already have another one) - create an…
**Steps to reproduce:** - create storable avco product - set the cost to 10 - set an onhand quantity of 100 in WH/stock - create another warehouse (if you don't already have another one) - create an internal transfer from WH/Stock to WH2 - open the 'stock' view - click on inventory at date - confirm **Current behavior:** the total value is 11.000 **Expected behavior:** total value should be 10.000 **Cause of the issue:** To compute the total_value of the product, _compute_value calls _run_average_batch https://github.com/odoo/odoo/blob/68f258e99f42693131a5309b3606c3b95f93d824/addons/stock_account/models/product.py#L260 Inside run_average_batch we need the qty_available at the time of last manual value (which is when we set the cost to 10 manually) in order to value all this quantity at the value of the manual value. https://github.com/odoo/odoo/blob/68f258e99f42693131a5309b3606c3b95f93d824/addons/stock_account/models/product.py#L435 This qty should be 0 cause we had no onhand quantity when we set the cost to 10. But it's actually going to be 10, here is why : Inside _compute_quantities_dict, because we're asking for a quantity in the past, the computation is current quantity - quantities that went in between the date in the past and now + quantities that went out between the date in the past and now. https://github.com/odoo/odoo/blob/154b49ec6be6230989ee4eb420e7f83b681ff520/addons/stock/models/product.py#L255 So we should have : 100 - 100 (moves_in_res_past) + 0 (moves_out_res_past) = 0 because we have 100 now and between the date we're asking for (the time of the manual value) and now there is one move in (when we set a quantity of 100) and no move out. But moves_out_res_past will actually be 10 for our product instead of 0. That's because in the read_group, our internal move will be considered as a move out and be taken into account https://github.com/odoo/odoo/blob/60cf607d7148ebe389cc8513aa3b82e156cd4400/addons/stock/models/product.py#L233-L234 That's because: When we called _run_average_batch from _compute_value, we called it on 'products_to_value', which is based on 'products', which was computed calling with_valuation_context() https://github.com/odoo/odoo/blob/60cf607d7148ebe389cc8513aa3b82e156cd4400/addons/stock_account/models/product.py#L206 which passes the valued internal location in the context https://github.com/odoo/odoo/blob/60cf607d7148ebe389cc8513aa3b82e156cd4400/addons/stock_account/models/product.py#L366-L370 As wh2 is not internal (it's a view) it's not included in the locations from the context. strict is also set to True Therefore at the beginning of compute_quantities_dict, when we call _get_domain_location to compute domain_move_out_loc (on which domain_move_out_done will be based), https://github.com/odoo/odoo/blob/60cf607d7148ebe389cc8513aa3b82e156cd4400/addons/stock/models/product.py#L165 inside _get_domain_location, because a location is given in the context that's the one we're going to use. https://github.com/odoo/odoo/blob/60cf607d7148ebe389cc8513aa3b82e156cd4400/addons/stock/models/product.py#L365 and because strict is in the context, dest_location_domain_out will be "location_dest_id not in [the list of valued location which does not include wh2]". https://github.com/odoo/odoo/blob/7e95d32d669a7ee7c50b5e665697cb577be0af93/addons/stock/models/product.py#L402-L405 And back in compute_quantities_dict(), domain_move_out_loc will be "location_id in [the list of valued location] and location_dest_id not in [the list of valued location]". Our internal move will therefore be considered as an out move and taken into account in the computation mentioned above. Which explains why quantity will be 10 inside run_average_batch and why the computation of total_value is wrong **fix:** in 19.0 the fix is in the xml to take less risk with regards to stable policy, however starting from 19.1 the fix will be in python opw-6321636 Forward-Port-Of: odoo/odoo#275384 Forward-Port-Of: odoo/odoo#273388
Features or functions removed from Odoo
Description of the issue/feature this PR addresses: Removed obsolete code checking "this.config.self_ordering_mode !== 'qr_code'". Since "qr_code" is not a valid value of the "self_ordering_mode" selection field. The condition is always true. Forward-Port-Of: odoo/odoo#276321 Forward-Port-Of: odoo/odoo#273245
Original PR description
Description of the issue/feature this PR addresses: Removed obsolete code checking "this.config.self_ordering_mode !== 'qr_code'". Since "qr_code" is not a valid value of the "self_ordering_mode" selection field. The condition is always true. Forward-Port-Of: odoo/odoo#276321 Forward-Port-Of: odoo/odoo#273245
Miscellaneous changes
When validating a stock move, we recompute the product's `standard_price` using a strategy that depends on the costing method: - Standard: no update - AVCO: replay the full history of `stock.move` since the last `product.value` - FIFO: fetch remaining `stock.move` records to find the stack and recompute the average from their remaining value and quantity For both FIFO and especially AVCO, this is costly and in most cases unnecessary. Instead, we can compute the new `standard_price` incre
Original PR description
When validating a stock move, we recompute the product's `standard_price` using a strategy that depends on the costing method: - Standard: no update - AVCO: replay the full history of `stock.move` since the last `product.value` - FIFO: fetch remaining `stock.move` records to find the stack and recompute the average from their remaining value and quantity For both FIFO and especially AVCO, this is costly and in most cases unnecessary. Instead, we can compute the new `standard_price` incrementally by adding the incoming value and quantity to the current ones. This is fast because `standard_price` is stored and `qty_available` is based on `stock.quant`. The new price is computed as: new_price = (previous_qty * std_price + added_value) / new_qty_available Forward-Port-Of: odoo/odoo#268092 Forward-Port-Of: odoo/odoo#264165
21 changes
New functionality added to Odoo
### Description of the issue/feature this PR addresses: Adds the Saudi ZATCA identification fields (building number, plot identification, identification scheme/number) to the portal billing address form so customers can fill them themselves. ### Current behavior before PR: The fields exist only in the backend; portal customers can't enter them. ### Desired behavior after PR is merged: Saudi-company customers can fill the fields on the portal (shown only when the billing country is SA
Original PR description
### Description of the issue/feature this PR addresses: Adds the Saudi ZATCA identification fields (building number, plot identification, identification scheme/number) to the portal billing address form so customers can fill them themselves. ### Current behavior before PR: The fields exist only in the backend; portal customers can't enter them. ### Desired behavior after PR is merged: Saudi-company customers can fill the fields on the portal (shown only when the billing country is SA; number disabled for the TIN scheme, with server-side validation). task-5188361 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270973
Enhancements to existing features
Move French e-invoicing technical statuses out of the main invoice UI and into the chatter. The PPF status is kept available in debug mode for troubleshooting, while regular users get a concise chatter summary with the PA status, PPF status, and any returned errors. Duplicate Lifecycle XML attachments are no longer posted in the chatter. Task-6273270 Forward-Port-Of: odoo/odoo#276770 Forward-Port-Of: odoo/odoo#269465
Original PR description
Move French e-invoicing technical statuses out of the main invoice UI and into the chatter. The PPF status is kept available in debug mode for troubleshooting, while regular users get a concise chatter summary with the PA status, PPF status, and any returned errors. Duplicate Lifecycle XML attachments are no longer posted in the chatter. Task-6273270 Forward-Port-Of: odoo/odoo#276770 Forward-Port-Of: odoo/odoo#269465
When importing an xml, in the notes you can have codes. We don't want them to be shown in the form view of invoice. task-6365267 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#274996
Original PR description
When importing an xml, in the notes you can have codes. We don't want them to be shown in the form view of invoice. task-6365267 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#274996
When a barcode scan matches a specific product variant, the product configurator now behaves the same as when the user searches by barcode: - The `always`-mode attribute (e.g. Color) is preselected from the matched variant instead of being hidden or left blank. - The `no_variant`-mode attributes (e.g. Size) are shown for user input. opw-6220883 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276543 Forward-Port-Of: odo
Original PR description
When a barcode scan matches a specific product variant, the product configurator now behaves the same as when the user searches by barcode: - The `always`-mode attribute (e.g. Color) is preselected from the matched variant instead of being hidden or left blank. - The `no_variant`-mode attributes (e.g. Size) are shown for user input. opw-6220883 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276543 Forward-Port-Of: odoo/odoo#265189
Test lints should be deterministic so retrying them doesn't make sense. While lints are not the slowest, pylint can take 10~15mn in later branches, and with retrying that's half an hour (and some) to get a failure, which is all that in wasted staging and build time. Forward-Port-Of: odoo/odoo#276600
Original PR description
Test lints should be deterministic so retrying them doesn't make sense. While lints are not the slowest, pylint can take 10~15mn in later branches, and with retrying that's half an hour (and some) to get a failure, which is all that in wasted staging and build time. Forward-Port-Of: odoo/odoo#276600
Resolved issues and error corrections
Version: --------- - 19.0+ Steps to Reproduce: ----------------------- 1. Install sale_management, purchase, stock modules. 2. Create a storable product with Tracking: By Lot, 3. Create two Purchase Orders, each for 10 units. Receive PO-1 → 10 units with tagged as lot-1 Receive PO-2 → 10 units with tagged as lot-2 4. Create two Sale Orders: SO-1 → deliver 2 units from lot-1 (validate) SO-2 → deliver 4 units from lot-2 (validate) 5. Open Inventory > Reporting > Stock,
Original PR description
Version: --------- - 19.0+ Steps to Reproduce: ----------------------- 1. Install sale_management, purchase, stock modules. 2. Create a storable product with Tracking: By Lot, 3. Create two Purchase…
Version:
---------
- 19.0+
Steps to Reproduce:
-----------------------
1. Install sale_management, purchase, stock modules.
2. Create a storable product with Tracking: By Lot,
3. Create two Purchase Orders, each for 10 units.
Receive PO-1 → 10 units with tagged as lot-1
Receive PO-2 → 10 units with tagged as lot-2
4. Create two Sale Orders:
SO-1 → deliver 2 units from lot-1 (validate)
SO-2 → deliver 4 units from lot-2 (validate)
5. Open Inventory > Reporting > Stock,
click "Total Value", then check the "Remaining Quantity" column
Issue:
-------
Observed : remaining_qty = 10 for lot-2 receipt, 4 for lot-1 receipt
Expected : remaining_qty = 8 for lot-1 receipt (10−2), 6 for lot-2 receipt (10−4)
Cause:
--------
When the "Remaining Quantity" column is computed, the following call
chain executes:
stock.move._compute_remaining_qty()
→ calls product.product._get_remaining_moves()
→ calls product._run_fifo_get_stack() ← HERE is the problem
https://github.com/odoo/odoo/blob/f4d079cc5a9c47672cf1a6747bb073e8e74f7350/addons/stock_account/models/product.py#L372
`_get_remaining_moves` calls `_run_fifo_get_stack()` with NO lot
argument. Inside `_run_fifo_get_stack`, because no lot is given, it
computes the stack size from the TOTAL product qty across all lots:
https://github.com/odoo/odoo/blob/f4d079cc5a9c47672cf1a6747bb073e8e74f7350/addons/stock_account/models/product.py#L583
fifo_stack_size = 14 (10 received lot-1 + 10 received lot-2
− 2 delivered lot-1 − 4 delivered lot-2)
It then builds a domain to find incoming moves with NO lot filter:
https://github.com/odoo/odoo/blob/f4d079cc5a9c47672cf1a6747bb073e8e74f7350/addons/stock_account/models/product.py#L607
https://github.com/odoo/odoo/blob/f4d079cc5a9c47672cf1a6747bb073e8e74f7350/addons/stock_account/models/product.py#L614-L618
```Domain: [('is_in', '=', True), ('product_id', '=', X)]
↳ returns both receipts ordered:
[lot-2 receipt (10 qty), lot-1 receipt (10 qty)]
then walks this list consuming `fifo_stack_size = 14`:
So it take: [move_lot1_receipt(10)] First Lot
remaining_qty_on_first = min(10, 14) = 10
after consuming fifo_stack_size → 14−10=4 left → move_lot1 gets 4
```
So back in `_get_remaining_moves`:
qty_by_move = {
lot-2 receipt → 10, ← wrong (should be 6)
lot-1 receipt → 4, ← wrong (should be 8)
}
- The root cause: `_run_fifo_get_stack` is designed for products that
have one shared FIFO stack. For lot-valuated products, each lot is an
independent inventory layer. Running a single combined stack mixes both
lots together, so the deductions (2 from lot-1, 4 from lot-2) are not
attributed to the correct receipt moves — the algorithm just consumes
from the oldest receipts first with no awareness of which lot was
actually delivered.
Fix:
-----
`_run_fifo_get_stack` already accepts a `lot=` argument that:
- sets `fifo_stack_size = lot.product_qty` (correct per-lot qty)
- adds `('move_line_ids.lot_id', 'in', lot.id)` to the domain
so only the receipts that touched that specific lot are returned
The only missing piece was calling it per lot instead of once globally.
- With the fix, the stack for each lot is built correctly:
lot-1: fifo_stack_size = 8 → lot-1 receipt remaining_qty = 8 ✓
lot-2: fifo_stack_size = 6 → lot-2 receipt remaining_qty = 6 ✓
---
opw-6311341
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#272411### Issue: The 'Schedule an appointment' and 'Next Events' CTA buttons were not updated even when their conditions were satisfied. ### Steps to reproduce: - Install only Website. - In the configurator, choose 'Schedule Appointments' as the main objective. - Complete the setup and create the website. - The CTA button remains 'Contact Us' instead of 'Schedule an appointment'. ### Reason: The `get_cta_data()` method is overridden in specific modules to update the CTA button base
Original PR description
### Issue: The 'Schedule an appointment' and 'Next Events' CTA buttons were not updated even when their conditions were satisfied. ### Steps to reproduce: - Install only Website. - In the configurator, choose 'Schedule Appointments' as the main objective. - Complete the setup and create the website. - The CTA button remains 'Contact Us' instead of 'Schedule an appointment'. ### Reason: The `get_cta_data()` method is overridden in specific modules to update the CTA button based on conditions. However, it is called before those modules are installed, so the overridden logic is never executed. ### Fix: Ensure that `get_cta_data()` is called and the CTA button is updated after the required modules are installed. task-[6383681](https://www.odoo.com/odoo/project/974/tasks/6383681) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#261781
Before this commit, a crash could occur in kanban views but it required a very precise timing. If 2 renderings of the kanban renderer occurred at the same time, one coming from a group that has just been opened, and one coming from a new groupby being applied in the search view, we tried to scroll to the opened group to ensure that it is in the viewport, but we couldn't find it. Task~6391414 Forward-Port-Of: odoo/odoo#276750 Forward-Port-Of: odoo/odoo#276488
Original PR description
Before this commit, a crash could occur in kanban views but it required a very precise timing. If 2 renderings of the kanban renderer occurred at the same time, one coming from a group that has just been opened, and one coming from a new groupby being applied in the search view, we tried to scroll to the opened group to ensure that it is in the viewport, but we couldn't find it. Task~6391414 Forward-Port-Of: odoo/odoo#276750 Forward-Port-Of: odoo/odoo#276488
It can happen that _ref_vat has some lazy translate object. Without the self.env._ the translation would be ignored. (no translation language detected, skipping translation) runbot-941504 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#275561
Original PR description
It can happen that _ref_vat has some lazy translate object. Without the self.env._ the translation would be ignored. (no translation language detected, skipping translation) runbot-941504 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#275561
Problem: After updating a file name, the link popover still shows the original file name. Cause: The link popover always displays the attachment name instead of the current link content. Solution: Use the link content as the popover title so it reflects the updated file title. Steps to reproduce: - Go to To-Do → Create New. - Upload a file. - Change its title. - Observe that the title shown in the link popover still uses the original file name. task-6213840 --- I confirm I
Original PR description
Problem: After updating a file name, the link popover still shows the original file name. Cause: The link popover always displays the attachment name instead of the current link content. Solution: Use the link content as the popover title so it reflects the updated file title. Steps to reproduce: - Go to To-Do → Create New. - Upload a file. - Change its title. - Observe that the title shown in the link popover still uses the original file name. task-6213840 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276687 Forward-Port-Of: odoo/odoo#264127
The cron to send e-invoices might be stuck in an infinite loop if the error received is considered as "Networking error". zeep.exceptions.Fault hinerits from zeep.exceptions.Error so it will be catch as a "Networking error" and will retry to send the invoice in the next cron run. This PR proposes a way to set those documents sent to "error" state if a fault exception is detected. opw-6085114 Forward-Port-Of: odoo/odoo#276225
Original PR description
The cron to send e-invoices might be stuck in an infinite loop if the error received is considered as "Networking error". zeep.exceptions.Fault hinerits from zeep.exceptions.Error so it will be catch as a "Networking error" and will retry to send the invoice in the next cron run. This PR proposes a way to set those documents sent to "error" state if a fault exception is detected. opw-6085114 Forward-Port-Of: odoo/odoo#276225
### Steps to reproduce: - In the settings enable Multi-Steps route - Unarchive the MTO route and set its production rule in MTSO - Create 3 products: P1, P2 and COMP all using the MTO route - Create 2 BOM's, one for P1 and one for P2: 1 X COMP - Put 2 units of COMP in stock and add an empty bom (to trigger a child MO creation in case the MTSO route is triggered) - Create and confirm a sale order for: 1 x P1 and 1 X P2 #### > An MO was generated for both product but P2 also generated a c
Original PR description
### Steps to reproduce: - In the settings enable Multi-Steps route - Unarchive the MTO route and set its production rule in MTSO - Create 3 products: P1, P2 and COMP all using the MTO route - Create…
### Steps to reproduce: - In the settings enable Multi-Steps route - Unarchive the MTO route and set its production rule in MTSO - Create 3 products: P1, P2 and COMP all using the MTO route - Create 2 BOM's, one for P1 and one for P2: 1 X COMP - Put 2 units of COMP in stock and add an empty bom (to trigger a child MO creation in case the MTSO route is triggered) - Create and confirm a sale order for: 1 x P1 and 1 X P2 #### > An MO was generated for both product but P2 also generated a child MO for 1 unit of COMP instead of using the available unit Cause of the issue: The issue happens in the `_prepare_procurement_qty` which incorrectly assess that 1 unit of COMP will be required. The issue has been introduced by commit https://github.com/odoo/odoo/commit/e30fb722c00805e7226d2ee9e3e587b3c2204840 which introduced a dictionary to keep track of units of products that will be used by the confirmation process of other concurrent mtso moves: https://github.com/odoo/odoo/blob/71f0715bd5e29e976a1e8bfa7c4fa6e04735ebd7/addons/stock/models/stock_move.py#L1683-L1689 https://github.com/odoo/odoo/blob/71f0715bd5e29e976a1e8bfa7c4fa6e04735ebd7/addons/stock/models/stock_move.py#L1712-L1715 https://github.com/odoo/odoo/blob/71f0715bd5e29e976a1e8bfa7c4fa6e04735ebd7/addons/stock/models/stock_move.py#L1810-L1814 While by design this propagates the information used by other mtso moves in a common `_action_confirm` stack, the issue that we encounter is that this quantity is only relevant to be substracted to the free_qty when the unit is not yet reserved and hence already accounted negatively in `free_qty`. However, in the present case, confirming the receipt of P1 and P2 will confirm both moves simultaneously, triggering a common `_run_manufacture` to generate both an MO for P1 and for P2. At this point the dictionary `consumed_from_stock_dict` is shared in both MO's confirmation but since the MO's are confirmed sequentially rather than in batch: https://github.com/odoo/odoo/blob/71f0715bd5e29e976a1e8bfa7c4fa6e04735ebd7/addons/mrp/models/stock_rule.py#L122-L125 The confirmation of the MO of P1 will update the `consumed_from_stock_dict` for 1 unit of COMP and will also reserve 1 unit of COMP before the MO of P2 is confirmed (and calls the `_prepare_procurement_qty`) to determine how many units of COMP are till available. This leads to the incorrect conclusion that 1 - 1 = 0 units are still available to fulfill the demand of P2. opw-6370298 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#275539
Before this commit: - When importing a **UBL** or **Factur-X (CII)** invoice, Odoo determines whether the document should be imported as an invoice or a credit note based on the `TaxExclusiveAmount (UBL)` /` TaxBasisTotalAmount (Factur-X)`. - In some rare cases, a valid invoice can contain a negative `TaxExclusiveAmount` / `TaxBasisTotalAmount` while still having a positive `TaxInclusiveAmount` / `GrandTotalAmount`. - In such situations, Odoo incorrectly imports the document as a credit note
Original PR description
Before this commit: - When importing a **UBL** or **Factur-X (CII)** invoice, Odoo determines whether the document should be imported as an invoice or a credit note based on the `TaxExclusiveAmount…
Before this commit: - When importing a **UBL** or **Factur-X (CII)** invoice, Odoo determines whether the document should be imported as an invoice or a credit note based on the `TaxExclusiveAmount (UBL)` /` TaxBasisTotalAmount (Factur-X)`. - In some rare cases, a valid invoice can contain a negative `TaxExclusiveAmount` / `TaxBasisTotalAmount` while still having a positive `TaxInclusiveAmount` / `GrandTotalAmount`. - In such situations, Odoo incorrectly imports the document as a credit note. Technical reason: - The method `_get_import_document_amount_sign()` uses `TaxExclusiveAmount` / `TaxBasisTotalAmount `to determine whether the imported document is an invoice or a refund. After this commit: - **UBL** now uses `TaxInclusiveAmount` instead of `TaxExclusiveAmount`, and **Factur-X** now uses `GrandTotalAmount `instead of `TaxBasisTotalAmount` to determine whether the document should be imported as an invoice or a credit note. - Prevent valid invoices with negative `TaxExclusiveAmount` / `TaxBasisTotalAmount` from being incorrectly converted into credit notes. Task-6321262 Forward-Port-Of: odoo/odoo#276307 Forward-Port-Of: odoo/odoo#271829
_reset_inventory() counter balances the stock implied by the move history when a product becomes storable, to reset the valuation of goods received while untracked. It assumed the product had no quants. But unticking Track Inventory does not clear the existing quants, so toggling it off then on again counter balances stock that is still on hand. The quants then desynchronize from their moves and the historical stock and valuation reports show quantities before the product ever existed. Onl
Original PR description
_reset_inventory() counter balances the stock implied by the move history when a product becomes storable, to reset the valuation of goods received while untracked. It assumed the product had no quants. But unticking Track Inventory does not clear the existing quants, so toggling it off then on again counter balances stock that is still on hand. The quants then desynchronize from their moves and the historical stock and valuation reports show quantities before the product ever existed. Only counter balance the part of the move history that is not already on hand. Steps to reproduce: - Create a storable product tracked by lots, 10 units on hand - Untick then re-tick "Track Inventory" on the product - Inventory > Reporting > Inventory at Date, pick a date before the product existed > The report shows 10 units on hand although there was no stock at that date. opw-6373051 Forward-Port-Of: odoo/odoo#275565
Steps to reproduce: - Create a promotions program with a rule granting 1 point per currency unit spent (minimum 2 items and 50.00 spent) and a reward "10% discount on the cheapest product" for 1 point - Open a PoS session and add 2 products to trigger the program - Keep adding products to the order Issue: A new "10% on the cheapest product" line is added for every product added. In eCommerce and Sales the discount is only applied once. Cause: The rule grants far more points than the r
Original PR description
Steps to reproduce: - Create a promotions program with a rule granting 1 point per currency unit spent (minimum 2 items and 50.00 spent) and a reward "10% discount on the cheapest product" for 1…
Steps to reproduce: - Create a promotions program with a rule granting 1 point per currency unit spent (minimum 2 items and 50.00 spent) and a reward "10% discount on the cheapest product" for 1 point - Open a PoS session and add 2 products to trigger the program - Keep adding products to the order Issue: A new "10% on the cheapest product" line is added for every product added. In eCommerce and Sales the discount is only applied once. Cause: The rule grants far more points than the reward costs, so the reward remains claimable after being applied. The auto-claim loop of `updateRewards` therefore re-applies it on every order change, stacking one discount line per change. The already-applied check in `getClaimableRewards` only covered 'coupons' programs, while `_get_claimable_rewards` in sale_loyalty also skips discount rewards already present on the order lines. Solution: When auto-claiming, skip discount rewards that are already applied on the order, unless they belong to a payment program (ewallet, gift card). The reward can still be claimed manually several times through the Reward button by spending more points. opw-6380421 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276300
Steps to reproduce: 1. install mail 2. Send a voice message to anyone from the discuss app 3. Open the ui in mobile and see the voice messege duration Issue: - time is showing in two lines Solution: - Adjust the spacing of the voice player controls for small screens using responsive Bootstrap utility classes and prevent the duration text from shrinking, ensuring it remains on a single line while preserving the existing layout on larger screens. <table width="100%"> <tr> <th
Original PR description
Steps to reproduce:
1. install mail
2. Send a voice message to anyone from the discuss app
3. Open the ui in mobile and see the voice messege duration
Issue:
- time is showing in two lines
Solution:
- Adjust the spacing of the voice player controls for small screens using responsive Bootstrap utility classes and prevent the duration text from shrinking, ensuring it remains on a single line while preserving the existing layout on larger screens.
<table width="100%">
<tr>
<th>Before</th>
<th>After</th>
</tr>
<tr>
<td align="center">
<img alt="After" src="https://github.com/user-attachments/assets/98f41d1a-9082-4d5c-a34b-c9181e643e0a">
</td>
<td align="center">
<img alt="Before" src="https://github.com/user-attachments/assets/0f879988-b2e6-46c9-ba5a-0f935fde40ec">
</td>
</tr>
</table>
opw-6328609
Forward-Port-Of: odoo/odoo#271768Steps to reproduce: ------------------- - Install `mrp` and `sale_management` modules - Enable Units of Measure from settings - Create a storable product configured as a Kit: - Set UoM to Units - Add component and it's UoM in Kg in Product form. - Create and confirm a Sales Order with the kit product - Validate the generated delivery order - Print the delivery slip Issue: ------ The delivery slip correctly displays component quantities in Kg, but also shows an additional conve
Original PR description
Steps to reproduce: ------------------- - Install `mrp` and `sale_management` modules - Enable Units of Measure from settings - Create a storable product configured as a Kit: - Set UoM to Units - Add…
Steps to reproduce:
-------------------
- Install `mrp` and `sale_management` modules
- Enable Units of Measure from settings
- Create a storable product configured as a Kit:
- Set UoM to Units
- Add component and it's UoM in Kg in Product form.
- Create and confirm a Sales Order with the kit product
- Validate the generated delivery order
- Print the delivery slip
Issue:
------
The delivery slip correctly displays component quantities in Kg,
but also shows an additional converted quantity in Units (e.g., 1000 Units),
which is incorrect and misleading.
Cause:
------
During sale order confirmation, the following flow is executed:
`action_confirm → _action_confirm → _action_launch_stock_rule → _prepare_procurement_values`
In `_prepare_procurement_values`, the `packaging_uom_id` is set from the
sale order line UoM (Units) and propagated to the generated stock move:
https://github.com/odoo/odoo/blob/647febbf46160c000bf11af8325cc80d0916eb67/addons/sale_stock/models/sale_order_line.py#L296
When the delivery (picking) is created, kit components generate stock moves where:
- `product_uom` is defined in the component’s UoM (e.g., Kg)
- `packaging_uom_id` remains in Units (inherited from the sale order line)
While generating the delivery slip, `_get_aggregated_product_quantities`
computes `packaging_quantity` using `packaging_uom_id`:
https://github.com/odoo/odoo/blob/647febbf46160c000bf11af8325cc80d0916eb67/addons/stock/models/stock_move_line.py#L888
In Mrp this calls the template:
`stock_report_delivery_aggregated_move_lines`
https://github.com/odoo/odoo/blob/647febbf46160c000bf11af8325cc80d0916eb67/addons/mrp/report/report_deliveryslip.xml#L60
In this template, a condition renders packaging quantities when
`packaging_uom_id` differs from `product_uom`. As a result, quantities are
converted from the component UoM (Kg) into the packaging UoM (Units).
https://github.com/odoo/odoo/blob/647febbf46160c000bf11af8325cc80d0916eb67/addons/stock/report/report_deliveryslip.xml#L261
For kit components, this conversion is not meaningful and leads to incorrect
values (e.g., Kg → Units resulting in 1000 Units), causing misleading output
in the delivery slip.
Fix:
----
Add a `_compute_packaging_uom_id` override in `sale_mrp` and
`purchase_mrp` that resets `packaging_uom_id` back to
the component's own `product_uom` whenever the move originates from a
phantom BoM line, without touching `sale_line_id`/`purchase_line_id`
themselves.
<details>
<summary>Click here to see the results:</summary>
<p><strong>Before:</strong></p>
<div class="image-row">
<img src="https://github.com/user-attachments/assets/5d8b2154-d794-4ce4-90a6-1a0aeaca8604" />
</div>
<p><strong>After:</strong></p>
<div class="image-row">
<img src="https://github.com/user-attachments/assets/79d708de-19e3-452d-9033-322a297c38e9" />
</div>
</details>
---
opw-6136928
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#262705Update the XPath of the empty header of the 'Pay Now' button column to the last header of the table. Use a deterministic XPath expression for the 'Pay Now' button column cell. as the last cell of the table. Forward-Port-Of: odoo/odoo#272710
Original PR description
Update the XPath of the empty header of the 'Pay Now' button column to the last header of the table. Use a deterministic XPath expression for the 'Pay Now' button column cell. as the last cell of the table. Forward-Port-Of: odoo/odoo#272710
Registration token now needs to be valid for much longer than 1 hour due to manual verifications Forward-Port-Of: odoo/odoo#276550
Original PR description
Registration token now needs to be valid for much longer than 1 hour due to manual verifications Forward-Port-Of: odoo/odoo#276550
*: website, website_sale `forEach` is a synchronous operation, so it doesn't support promises. We refactor its usage to use `for` loops. task-4794299 Forward-Port-Of: odoo/odoo#276590 Forward-Port-Of: odoo/odoo#275262
Original PR description
*: website, website_sale `forEach` is a synchronous operation, so it doesn't support promises. We refactor its usage to use `for` loops. task-4794299 Forward-Port-Of: odoo/odoo#276590 Forward-Port-Of: odoo/odoo#275262
Features or functions removed from Odoo
Description of the issue/feature this PR addresses: Removed obsolete code checking "this.config.self_ordering_mode !== 'qr_code'". Since "qr_code" is not a valid value of the "self_ordering_mode" selection field. The condition is always true. Forward-Port-Of: odoo/odoo#276321 Forward-Port-Of: odoo/odoo#273245
Original PR description
Description of the issue/feature this PR addresses: Removed obsolete code checking "this.config.self_ordering_mode !== 'qr_code'". Since "qr_code" is not a valid value of the "self_ordering_mode" selection field. The condition is always true. Forward-Port-Of: odoo/odoo#276321 Forward-Port-Of: odoo/odoo#273245
2 changes
Enhancements to existing features
No task ID
Original PR description
No task ID
Resolved issues and error corrections
Problem: After updating a file name, the link popover still shows the original file name. Cause: The link popover always displays the attachment name instead of the current link content. Solution: Use the link content as the popover title so it reflects the updated file title. Steps to reproduce: - Go to To-Do → Create New. - Upload a file. - Change its title. - Observe that the title shown in the link popover still uses the original file name. task-6213840 --- I confirm I
Original PR description
Problem: After updating a file name, the link popover still shows the original file name. Cause: The link popover always displays the attachment name instead of the current link content. Solution: Use the link content as the popover title so it reflects the updated file title. Steps to reproduce: - Go to To-Do → Create New. - Upload a file. - Change its title. - Observe that the title shown in the link popover still uses the original file name. task-6213840 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276687 Forward-Port-Of: odoo/odoo#264127
16 changes
Resolved issues and error corrections
Before this commit: If a user, with crm.leads linked to it, decided to request a password reset AND during that password reset process decided to activate the google oauth for their account, it would cause a crash. The reason is, during the password rest + oauth activation, self.env.user is an empty record set, which obviously will fail during the _is_portal check, due to its call to ensure_one() opw-6347228 Forward-Port-Of: odoo/odoo#275375
Original PR description
Before this commit: If a user, with crm.leads linked to it, decided to request a password reset AND during that password reset process decided to activate the google oauth for their account, it would cause a crash. The reason is, during the password rest + oauth activation, self.env.user is an empty record set, which obviously will fail during the _is_portal check, due to its call to ensure_one() opw-6347228 Forward-Port-Of: odoo/odoo#275375
Issue: ---------------------------------------- When generating work entries with the CRON "Generate Missing Work Entries", the name of the work entries is always in English. Steps to reproduce: ---------------------------------------- - Create a new employee, setup a running contract for them - Run the schedule action "Generate Missing Work Entries" - In Payroll > Work Entries, search for the work entries of the new employee - Their name are in French Cause: ----------------------
Original PR description
Issue: ---------------------------------------- When generating work entries with the CRON "Generate Missing Work Entries", the name of the work entries is always in English. Steps to reproduce: ---------------------------------------- - Create a new employee, setup a running contract for them - Run the schedule action "Generate Missing Work Entries" - In Payroll > Work Entries, search for the work entries of the new employee - Their name are in French Cause: ---------------------------------------- When running the cron, `self.env.lang` is `False` so the text aren't translated. Solution: ---------------------------------------- In `_cron_generate_missing_work_entries()` we specify `self.env.user.lang` in the context. As `_cron_generate_missing_work_entries()` uses the root user to run, the language of the work entries will be the one specified on Odoobot. opw-6369109 Forward-Port-Of: odoo/odoo#275952
Steps: - Enable `pos_hr` and configure employees - Open the POS - Log in as an employee - Open the burger menu in the navbar Issue: - The "Create Product" menu is not visible immediately after the employee logs in. - It only appears after refreshing the POS. Cause: - The visibility of the menu is determined when `Navbar` component is mounted. - Since the `Navbar` is mounted only once when the POS UI loads, the value is not updated after employee login. Fix: - Replace the asynch
Original PR description
Steps: - Enable `pos_hr` and configure employees - Open the POS - Log in as an employee - Open the burger menu in the navbar Issue: - The "Create Product" menu is not visible immediately after the employee logs in. - It only appears after refreshing the POS. Cause: - The visibility of the menu is determined when `Navbar` component is mounted. - Since the `Navbar` is mounted only once when the POS UI loads, the value is not updated after employee login. Fix: - Replace the asynchronous permission check with a getter that evaluates product creation rights. - Cache the group access information in `posService` and let the hr override use the getter. Task-6361787 Related PR: https://github.com/odoo/enterprise/pull/123073 Forward-Port-Of: odoo/odoo#274420
Issue: --- If a product template has dynamic attributes, some variants might not exist. For those variants, we are showing wrong stock in the website. To reproduce: 1- Create a product with a dynamic attribute and two values. 2- Publish the product and uncheck sell when out-of-stock and check show product when the qty is less than 5. 3- Create a purchase order with qty = 4 for the first value, so a variant is created for it. 4- Go to the website shop. Open the product. 4 available qty i
Original PR description
Issue: --- If a product template has dynamic attributes, some variants might not exist. For those variants, we are showing wrong stock in the website. To reproduce: 1- Create a product with a dynamic attribute and two values. 2- Publish the product and uncheck sell when out-of-stock and check show product when the qty is less than 5. 3- Create a purchase order with qty = 4 for the first value, so a variant is created for it. 4- Go to the website shop. Open the product. 4 available qty in stock is shown for the first variant which is correct. 5- Select 2nd variant. As you see, still 4 available qty is shown which is wrong. As the out-of-stock sale is unchecked, an out-of-stock warning should be shown. Cause and Fix: --- This is due to skipping when `product_id` is not set which makes `free_qty` and `out_of_stock` not to be updated. opw-6237602 Forward-Port-Of: odoo/odoo#276247 Forward-Port-Of: odoo/odoo#273104
Calling the method in RPC causes an error: ``` TypeError: cannot marshal None unless allow_none is enabled ``` 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#276541
Original PR description
Calling the method in RPC causes an error: ``` TypeError: cannot marshal None unless allow_none is enabled ``` 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#276541
In the Outlook calendar sync, it's possible to have the same attendee twice. That's because the normalized email wasn't used to check for preexisting attendee. To reproduce, sync event with the organizer, also an attendee, using high case in the email such as: ORGANIZER: Mike@organizer.com ATTENDEE: Mike@organizer.com ATTENDEE: John@attendee.com opw-6186606 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270666 For
Original PR description
In the Outlook calendar sync, it's possible to have the same attendee twice. That's because the normalized email wasn't used to check for preexisting attendee. To reproduce, sync event with the organizer, also an attendee, using high case in the email such as: ORGANIZER: Mike@organizer.com ATTENDEE: Mike@organizer.com ATTENDEE: John@attendee.com opw-6186606 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270666 Forward-Port-Of: odoo/odoo#268201
When sending a batch of invoice, we first generate an unique PDF file regrouping every invoice. When at least one invoice needs more than one page to be rendered entirely, the number of pages will be greater than the number of invoices. In this case, we use <hX> tags to split invoices: https://github.com/odoo/odoo/blob/5ba945cdba6be3ff8838e56784ade16c6200de84/odoo/addons/base/models/ir_actions_report.py#L916-L923 This tag is added for PDF files, using the invoice's title: https://g
Original PR description
When sending a batch of invoice, we first generate an unique PDF file regrouping every invoice. When at least one invoice needs more than one page to be rendered entirely, the number of pages will be…
When sending a batch of invoice, we first generate an unique PDF file regrouping every invoice. When at least one invoice needs more than one page to be rendered entirely, the number of pages will be greater than the number of invoices. In this case, we use <hX> tags to split invoices: https://github.com/odoo/odoo/blob/5ba945cdba6be3ff8838e56784ade16c6200de84/odoo/addons/base/models/ir_actions_report.py#L916-L923 This tag is added for PDF files, using the invoice's title: https://github.com/odoo/odoo/blob/5ba945cdba6be3ff8838e56784ade16c6200de84/addons/web/views/report_templates.xml#L627 In some localization modules, this title shouldn't be displayed and it is removed. Example for Chile: https://github.com/odoo/odoo/blob/5ba945cdba6be3ff8838e56784ade16c6200de84/addons/l10n_cl/views/report_invoice.xml#L149 In this case, no `<h3>` tag will be added and an error is raised as we cannot separate invoices. We propose to add an empty `<h3>` tag if the document title is not defined. opw-6281187 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269100
For a logged-in user, some form fields are automatically prefilled from their profile, such as a "Phone" field with their phone number. **Issue:** The prefill configuration is always preserved when we change options of a field. While this is correct when updating few options, but it also happens after the field is repurposed, causing it to inherit a prefill value intended for a different field. **Steps to reproduce:** - Edit the /contactus page's form. - Change the "Name" field's ty
Original PR description
For a logged-in user, some form fields are automatically prefilled from their profile, such as a "Phone" field with their phone number. **Issue:** The prefill configuration is always preserved when…
For a logged-in user, some form fields are automatically prefilled from their profile, such as a "Phone" field with their phone number. **Issue:** The prefill configuration is always preserved when we change options of a field. While this is correct when updating few options, but it also happens after the field is repurposed, causing it to inherit a prefill value intended for a different field. **Steps to reproduce:** - Edit the /contactus page's form. - Change the "Name" field's type to a "URL" or "CC" field. - Save the changes. - The "URL/CC" field is prefilled with the user's name. A field is considered repurposed when: - its type is changed (e.g. from "Phone" to "URL"); - a custom field is converted into an existing field. **Fix:** This commit preserves the prefill only when the field keeps the same name and type. Otherwise, it clears the stale prefill so repurposed fields no longer inherit incorrect values. task-[5976747](https://www.odoo.com/odoo/project/974/tasks/5976747) Forward-Port-Of: odoo/odoo#275812
Before this commit, a many2one field test could sometimes fail because of an unexpected web_name_search in verifySteps. That extra call followed the `.clear()` of the input, which triggers a debounced search. Depending on the timing, that call sometimes occured before the end of the test (and the destroy of the component). Now, it is always performed. runbot error~944206 Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merge
Original PR description
Before this commit, a many2one field test could sometimes fail because of an unexpected web_name_search in verifySteps. That extra call followed the `.clear()` of the input, which triggers a debounced search. Depending on the timing, that call sometimes occured before the end of the test (and the destroy of the component). Now, it is always performed. runbot error~944206 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#276799
hash_sign has become very used all over the place, so more and more tokens depend on `database.secret`, increasing the impact of that secret needing one day to be rotated or being compromised. To avoid making `database.secret` a single point of failure, we would like `hash_sign` to support a custom secret supplied by caller. task-6391264 Forward-Port-Of: odoo/odoo#276474
Original PR description
hash_sign has become very used all over the place, so more and more tokens depend on `database.secret`, increasing the impact of that secret needing one day to be rotated or being compromised. To avoid making `database.secret` a single point of failure, we would like `hash_sign` to support a custom secret supplied by caller. task-6391264 Forward-Port-Of: odoo/odoo#276474
### [FIX] web: fix mocked dates in tests Dates are mocked in all unit tests by default; either to a default static value, or to one defined in the test/suite. To do so, the `window.Date` constructor is overridden to have the arguments auto-filled with the current mock date paramters. Before this commit, an additional offset was always added to the given arguments to reflect: - the time elapsed from the beginning of the test; - any virtual offset added by a helper such as 'advanceTime
Original PR description
### [FIX] web: fix mocked dates in tests Dates are mocked in all unit tests by default; either to a default static value, or to one defined in the test/suite. To do so, the `window.Date` constructor…
### [FIX] web: fix mocked dates in tests Dates are mocked in all unit tests by default; either to a default static value, or to one defined in the test/suite. To do so, the `window.Date` constructor is overridden to have the arguments auto-filled with the current mock date paramters. Before this commit, an additional offset was always added to the given arguments to reflect: - the time elapsed from the beginning of the test; - any virtual offset added by a helper such as 'advanceTime'. The issue is that the spec of the Date constructor wants that the returned date object has to match all given arguments, and any omitted argument will be defaulted to the current date/time. This was not the case in tests, as the offset was always added, regardless of the given arguments. With this commit: only the arguments that are NOT given and that have been defaulted to current (mocked) date/time will be offset by the adequate value. Furthermore: as these mocked parameters are meant to reflect UTC values, the offset now also considers the *actual* browser offset to generate a local date from the mock date parameters. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#275391
Steps to reproduce: 1. Edit/Create an activity type (e.g., "To Do"). 2. Schedule an activity of this type on a journal (or a journal entry/move) with a deadline in the past. 3. Mark the activity as done. 4. Go to the Accounting Dashboard. Notice that the activity is still shown as "overdue" in red on the journal card. Completed activities are not deleted from the database. Instead, they are archived by setting `active = False` and `date_done` is populated. Since the activity dashboard
Original PR description
Steps to reproduce: 1. Edit/Create an activity type (e.g., "To Do"). 2. Schedule an activity of this type on a journal (or a journal entry/move) with a deadline in the past. 3. Mark the activity as done. 4. Go to the Accounting Dashboard. Notice that the activity is still shown as "overdue" in red on the journal card. Completed activities are not deleted from the database. Instead, they are archived by setting `active = False` and `date_done` is populated. Since the activity dashboard query retrieves records via direct SQL, it bypasses Odoo's automatic active filtering on `mail.activity`. As a result, archived (done) activities were incorrectly fetched and displayed on the journal dashboard cards, appearing as overdue. This commit resolves the issue by explicitly adding `AND activity.active = TRUE` to the SQL queries. Task-6142042 Forward-Port-Of: odoo/odoo#274313
Before this commit: - When importing a **UBL** or **Factur-X (CII)** invoice, Odoo determines whether the document should be imported as an invoice or a credit note based on the `TaxExclusiveAmount (UBL)` /` TaxBasisTotalAmount (Factur-X)`. - In some rare cases, a valid invoice can contain a negative `TaxExclusiveAmount` / `TaxBasisTotalAmount` while still having a positive `TaxInclusiveAmount` / `GrandTotalAmount`. - In such situations, Odoo incorrectly imports the document as a credit note
Original PR description
Before this commit: - When importing a **UBL** or **Factur-X (CII)** invoice, Odoo determines whether the document should be imported as an invoice or a credit note based on the `TaxExclusiveAmount…
Before this commit: - When importing a **UBL** or **Factur-X (CII)** invoice, Odoo determines whether the document should be imported as an invoice or a credit note based on the `TaxExclusiveAmount (UBL)` /` TaxBasisTotalAmount (Factur-X)`. - In some rare cases, a valid invoice can contain a negative `TaxExclusiveAmount` / `TaxBasisTotalAmount` while still having a positive `TaxInclusiveAmount` / `GrandTotalAmount`. - In such situations, Odoo incorrectly imports the document as a credit note. Technical reason: - The method `_get_import_document_amount_sign()` uses `TaxExclusiveAmount` / `TaxBasisTotalAmount `to determine whether the imported document is an invoice or a refund. After this commit: - **UBL** now uses `TaxInclusiveAmount` instead of `TaxExclusiveAmount`, and **Factur-X** now uses `GrandTotalAmount `instead of `TaxBasisTotalAmount` to determine whether the document should be imported as an invoice or a credit note. - Prevent valid invoices with negative `TaxExclusiveAmount` / `TaxBasisTotalAmount` from being incorrectly converted into credit notes. Task-6321262 Forward-Port-Of: odoo/odoo#275994 Forward-Port-Of: odoo/odoo#271829
Issue: - After confirming a sale order with a reward applied (e.g. an ewallet reward), unlocking it, and editing it so the reward line's cost changes (e.g. adding a product and re-claiming the reward), the coupon's point balance updates correctly. - However, the corresponding loyalty.history record's used value is never refreshed, so it keeps showing the old cost instead of the new one. Steps to reproduce: - Enable Loyalty and Lock Confirmed Sales in Sales settings. - Create an ewallet ty
Original PR description
Issue: - After confirming a sale order with a reward applied (e.g. an ewallet reward), unlocking it, and editing it so the reward line's cost changes (e.g. adding a product and re-claiming the…
Issue: - After confirming a sale order with a reward applied (e.g. an ewallet reward), unlocking it, and editing it so the reward line's cost changes (e.g. adding a product and re-claiming the reward), the coupon's point balance updates correctly. - However, the corresponding loyalty.history record's used value is never refreshed, so it keeps showing the old cost instead of the new one. Steps to reproduce: - Enable Loyalty and Lock Confirmed Sales in Sales settings. - Create an ewallet type loyalty.program and generate an ewallet for a partner with e.g. 1000 points. - Create a sale order for that partner, add a product worth 100, and claim the ewallet reward (reward line created with points_cost = 100). - Confirm the order. loyalty.history shows used = 100 (correct), card balance shows 900 (correct). - Unlock the order, add a second product worth 100, and claim the reward again (same reward line updates to points_cost = 200). - Lock the order again. - Check the loyalty.card: balance is correctly 800. - Check loyalty.history for that order: used still shows 100 instead of 200. Fix: - Updated _update_loyalty_history() in sale_order.py to create a new history line if none exists for the given card and order combination. - Updated write() in sale_order_line.py to correctly sync history lines when a reward line is modified on a confirmed order, handling both same-coupon updates via delta and coupon changes by subtracting the old coupon cost and adding the new one separately. - Added test_loyalty_history_created_on_post_confirm_reward to verify that a history line is created when a reward is claimed on a confirmed order where no history line existed before. - Added test_loyalty_history_updated_on_points_cost_write to verify that history.used is updated by the correct delta when points_cost changes on a reward line of a confirmed order. Impact: - Ensures a coupon's usage history stays accurate after a confirmed order is unlocked and edited. - Prevents the loyalty.history used field from silently going stale while the actual point balance is correct. - Not scoped to ewallet specifically — since points_cost semantics are the same across program types, this also corrects the same class of staleness for other reward types (discount, gift_card, etc.) when a reward line's cost changes post-confirmation. Forward-Port-Of: odoo/odoo#273688
**Steps to reproduce:** 1. Create a Sales Order. 2. Create a 50% down payment invoice. 3. Create a credit note for the down payment invoice. 4. Reset the credit note to Draft and cancel it. 5. Create the final invoice from the Sales Order. **Issue:** The final invoice is generated for 100% of the order amount, acting as if the down payment invoice does not exist. **Expected behavior:** The final invoice should only include the remaining 50% of the order amount because a valid 50% do
Original PR description
**Steps to reproduce:** 1. Create a Sales Order. 2. Create a 50% down payment invoice. 3. Create a credit note for the down payment invoice. 4. Reset the credit note to Draft and cancel it. 5. Create…
**Steps to reproduce:** 1. Create a Sales Order. 2. Create a 50% down payment invoice. 3. Create a credit note for the down payment invoice. 4. Reset the credit note to Draft and cancel it. 5. Create the final invoice from the Sales Order. **Issue:** The final invoice is generated for 100% of the order amount, acting as if the down payment invoice does not exist. **Expected behavior:** The final invoice should only include the remaining 50% of the order amount because a valid 50% down payment invoice still exists. **Why this happens:** - The `price_unit` on the Sales Order's down payment line is manually updated during `action_post()` based on the sum of posted invoices minus posted credit notes. - When the credit note is posted, `price_unit` drops to 0. However, when that credit note is subsequently reset to draft and cancelled, it triggers `button_cancel()` which only refreshed the line's display name and failed to recalculate `price_unit`. As a result, `price_unit` remained at 0 even though the credit note was no longer active, causing the final invoice to deduct nothing. opw-6373578 Forward-Port-Of: odoo/odoo#275684
Documentation and clarification updates
Adds Corvanis corporate CLA entry so legal/cla can validate contributions from Corvanis contributors. This change only adds: - doc/cla/corporate/corvanis.md - No functional code changes.
Original PR description
Adds Corvanis corporate CLA entry so legal/cla can validate contributions from Corvanis contributors. This change only adds: - doc/cla/corporate/corvanis.md - No functional code changes.
9 changes
Enhancements to existing features
This PR introduces an improvement to the account and l10n_latam_check modules that allows for multiple liquidity lines (one per check) to be recorded in a single journal entry. Currently, a split journal entry is created for each liquidity line, which presents several disadvantages: - Incorrect computation of payment and invoice states - Simplified workflow: By using a single journal entry for all liquidity lines, the workflow is significantly simplified and accounting complexity is redu
Original PR description
This PR introduces an improvement to the account and l10n_latam_check modules that allows for multiple liquidity lines (one per check) to be recorded in a single journal entry. Currently, a split…
This PR introduces an improvement to the account and l10n_latam_check modules that allows for multiple liquidity lines (one per check) to be recorded in a single journal entry. Currently, a split journal entry is created for each liquidity line, which presents several disadvantages:
- Incorrect computation of payment and invoice states
- Simplified workflow: By using a single journal entry for all liquidity lines, the workflow is significantly simplified and accounting complexity is reduced.
- First step towards a refactor: This improvement is a first step towards a broader refactor that will allow adapting the own check flow in payment registration without the need to create journal entries.
Changes made:
- Enhanced _synchronize_to_moves() method: - Allows for the creation and update of multiple liquidity lines within a single journal entry. - Removes excess liquidity lines if _prepare_move_line_default_vals() returns fewer lines than currently exist. - Account type-based counterparty identification: Replaces the previous position-based identification with an approach based on account types. - The starting index for extra line values is now dynamic, determined by the number of liquidity lines
- Removed _l10n_latam_check_split_move method:
- This method is no longer necessary given the new implementation that supports multiple liquidity lines in a single journal entry.
- Modified _prepare_move_line_default_vals() method: - Now returns one liquidity line for each registered own check, simplifying the generation of journal entries.
- Preserved _l10n_latam_check_unlink_split_move() method:
- Maintained for backward compatibility purposes, allowing payments containing split moves to be set to draft status.
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-prResolved issues and error corrections
Before this commit: If a user, with crm.leads linked to it, decided to request a password reset AND during that password reset process decided to activate the google oauth for their account, it would cause a crash. The reason is, during the password rest + oauth activation, self.env.user is an empty record set, which obviously will fail during the _is_portal check, due to its call to ensure_one() opw-6347228 Forward-Port-Of: odoo/odoo#275375
Original PR description
Before this commit: If a user, with crm.leads linked to it, decided to request a password reset AND during that password reset process decided to activate the google oauth for their account, it would cause a crash. The reason is, during the password rest + oauth activation, self.env.user is an empty record set, which obviously will fail during the _is_portal check, due to its call to ensure_one() opw-6347228 Forward-Port-Of: odoo/odoo#275375
When changing the project_id of timesheet without triggering an onchange (in this ticket the client used the mass editing), the task_id would not be set to False if the new project_id was not linked to the curent task. Steps to reproduce: ------------------- * Install hr_timesheet and studio * With Studio activate the mass editing for the timesheet list view * Change the project of all the timesheet entries > Observation: The task id are not set to False Why the fix: ------------ Us
Original PR description
When changing the project_id of timesheet without triggering an onchange (in this ticket the client used the mass editing), the task_id would not be set to False if the new project_id was not linked to the curent task. Steps to reproduce: ------------------- * Install hr_timesheet and studio * With Studio activate the mass editing for the timesheet list view * Change the project of all the timesheet entries > Observation: The task id are not set to False Why the fix: ------------ Use compute instead of onchange opw-6259149
Steps to reproduce: ------------------------------------ 1. Install barcode_gs1_nomenclature Module. 2. Go to Inventory > Configuration > Barcode Nomenclature. 3. Open any barcode nomenclature. Observation: ------------------------------------ The "Is GS1 Nomenclature" field is visible in the list header. Issue: ------------------------------------ The field uses the invisible attribute, which hides the field values but does not hide the corresponding list header, resulting in an e
Original PR description
Steps to reproduce: ------------------------------------ 1. Install barcode_gs1_nomenclature Module. 2. Go to Inventory > Configuration > Barcode Nomenclature. 3. Open any barcode nomenclature. Observation: ------------------------------------ The "Is GS1 Nomenclature" field is visible in the list header. Issue: ------------------------------------ The field uses the invisible attribute, which hides the field values but does not hide the corresponding list header, resulting in an empty header column. Solution: ------------------------------------ Replace the 'invisible' attribute with 'column_invisible' so that both the field values and the corresponding list header are hidden. opw-6390731
When searching on the Website site, using the main search on the navbar, it opens a list view with the results (`website.list_hybrid`) which raises the warning on the logs (2 times): "Unknown directives or unused attributes: {'t-key'} in website.list_hybrid"  This happens after the attribute `t-key` was added to the template [\[1\]] because the template is only use
Original PR description
When searching on the Website site, using the main search on the navbar, it opens a list view with the results (`website.list_hybrid`) which raises the warning on the logs (2 times): "Unknown…
When searching on the Website site, using the main search on the navbar, it opens a list view with the results (`website.list_hybrid`) which raises the warning on the logs (2 times):
"Unknown directives or unused attributes: {'t-key'} in website.list_hybrid"

This happens after the attribute `t-key` was added to the template [\[1\]] because the template is only used in QWeb. The validation for them doesn't include the `t-key` [\[2\]] as one of the "iter_directives" nor has a `_compile_directive_*` method to check and remove it from the validation as it's done with the `t-as` and `t-foreach`.
This also causes the raise of the warnings on tours that use the tour method `searchProduct` (of the module `website_sale`) because it uses the first input with the name of search and happens to be the search on the navbar.

[\[1\]]: https://github.com/odoo/odoo/commit/7b1d82aa
[\[2\]]: https://github.com/odoo/odoo/blob/f52cfb09/odoo/addons/base/models/ir_qweb.py#L1400
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#179017Steps to reproduce: ------------------------- 1. Install hr_timesheet and create a second company (e.g., Company B). 2. Create a global project (no company assigned) with timesheets enabled. 3. Share the project with edit access to a portal user belonging to Company A.4 4. Create a task, switch to Company B, and log a timesheet on the task. 5. Log in as the portal user and try to access the project. Issue: ------- An `Access to unauthorized or invalid companies exception` is raised,
Original PR description
Steps to reproduce: ------------------------- 1. Install hr_timesheet and create a second company (e.g., Company B). 2. Create a global project (no company assigned) with timesheets enabled. 3. Share…
Steps to reproduce: ------------------------- 1. Install hr_timesheet and create a second company (e.g., Company B). 2. Create a global project (no company assigned) with timesheets enabled. 3. Share the project with edit access to a portal user belonging to Company A.4 4. Create a task, switch to Company B, and log a timesheet on the task. 5. Log in as the portal user and try to access the project. Issue: ------- An `Access to unauthorized or invalid companies exception` is raised, preventing the portal user from accessing a project they are legitimately shared on. Cause: ---------- https://github.com/odoo/odoo/blob/b7b3292b6a46c3dbc17aeee0183df0af318bf810/addons/project/controllers/portal.py#L159-L173 During `_prepare_project_sharing_session_info`, hr_timesheet overrides the company determination logic through `_get_project_sharing_company()`. https://github.com/odoo/odoo/blob/b7b3292b6a46c3dbc17aeee0183df0af318bf810/addons/hr_timesheet/controllers/project.py#L13-L18 For global projects, the company is derived from an existing timesheet if one exists. As a result, creating a timesheet in another company causes that company to be injected into the sharing session as the current company. Since the portal user does not have access to that company, opening the project triggers an access error. Solution: ---------- Remove the `_get_project_sharing_company()` override. The base implementation already falls back to the portal user's own company when the project has no company assigned, ensuring the sharing session only contains companies the portal user is allowed to access. https://github.com/odoo/odoo/blob/b7b3292b6a46c3dbc17aeee0183df0af318bf810/addons/project/controllers/portal.py#L141-L142 This allows portal users from Company A to continue accessing global projects without errors. opw-6253960 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#272161
For a logged-in user, some form fields are automatically prefilled from their profile, such as a "Phone" field with their phone number. **Issue:** The prefill configuration is always preserved when we change options of a field. While this is correct when updating few options, but it also happens after the field is repurposed, causing it to inherit a prefill value intended for a different field. **Steps to reproduce:** - Edit the /contactus page's form. - Change the "Name" field's ty
Original PR description
For a logged-in user, some form fields are automatically prefilled from their profile, such as a "Phone" field with their phone number. **Issue:** The prefill configuration is always preserved when…
For a logged-in user, some form fields are automatically prefilled from their profile, such as a "Phone" field with their phone number. **Issue:** The prefill configuration is always preserved when we change options of a field. While this is correct when updating few options, but it also happens after the field is repurposed, causing it to inherit a prefill value intended for a different field. **Steps to reproduce:** - Edit the /contactus page's form. - Change the "Name" field's type to a "URL" or "CC" field. - Save the changes. - The "URL/CC" field is prefilled with the user's name. A field is considered repurposed when: - its type is changed (e.g. from "Phone" to "URL"); - a custom field is converted into an existing field. **Fix:** This commit preserves the prefill only when the field keeps the same name and type. Otherwise, it clears the stale prefill so repurposed fields no longer inherit incorrect values. task-[5976747](https://www.odoo.com/odoo/project/974/tasks/5976747) Forward-Port-Of: odoo/odoo#275812
Before this commit, validating transfers of several companies at once could assign a lot of one company to the move lines of another one, because the search for existing lots used a variable left over from a previous loop (the last move line iterated) instead of the company of the group of lines being checked. Steps to reproduce: - in a multi-company database, create a product tracked by lots - create a lot with the same name for that product in each company - create one receipt per compan
Original PR description
Before this commit, validating transfers of several companies at once could assign a lot of one company to the move lines of another one, because the search for existing lots used a variable left…
Before this commit, validating transfers of several companies at once could assign a lot of one company to the move lines of another one, because the search for existing lots used a variable left over from a previous loop (the last move line iterated) instead of the company of the group of lines being checked. Steps to reproduce: - in a multi-company database, create a product tracked by lots - create a lot with the same name for that product in each company - create one receipt per company with that lot name typed in the detailed operations, select both receipts in the Transfers list view and validate them together The lines of one company are linked to the lot of the other company and the validation is blocked by "Incompatible companies on records". When the lot only exists in one of the companies, the search misses it and the validation fails on the lot uniqueness constraint while recreating a lot that already exists. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Currently, we only load the XML data from the exported POS order in the field `l10n_jo_edi_pos_computed_xml` when`l10n_jo_edi_pos_error` is False. It should be the opposite. The field `l10n_jo_edi_pos_computed_xml` is only used when we call `download_l10n_jo_edi_pos_computed_xml()` from the anchor "Download XML" , and that anchor is only visible when `l10n_jo_edi_pos_error` is True. If the request succeeds, then the XML file will be stored in the field `l10n_jo_edi_pos_xml_attachment_i
Original PR description
Currently, we only load the XML data from the exported POS order in the field `l10n_jo_edi_pos_computed_xml` when`l10n_jo_edi_pos_error` is False. It should be the opposite. The field `l10n_jo_edi_pos_computed_xml` is only used when we call `download_l10n_jo_edi_pos_computed_xml()` from the anchor "Download XML" , and that anchor is only visible when `l10n_jo_edi_pos_error` is True. If the request succeeds, then the XML file will be stored in the field `l10n_jo_edi_pos_xml_attachment_id` anyway. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
4 changes
Resolved issues and error corrections
odoo/odoo#155588 was a workaround to the lack of location permissions in the iOS app, skipping geolocation entirely for check in/out. Since odoo/mobile#118 added support for them, we can now revert that fix so iOS users get prompted for their location again. task-6279460
Original PR description
odoo/odoo#155588 was a workaround to the lack of location permissions in the iOS app, skipping geolocation entirely for check in/out. Since odoo/mobile#118 added support for them, we can now revert that fix so iOS users get prompted for their location again. task-6279460
Once a ZATCA invoice is posted, the "Reset to Draft" button is still shown on the form until the chain index is set on the move. In that window, a user can click it (or force it visible from Studio on an already-accepted invoice) and reset the move to draft, even though it has been -- or is about to be -- submitted to ZATCA. The invoice can then be resubmitted, breaking the ZATCA chain. Steps to reproduce: 1. Configure a SA company and set up ZATCA. 2. Create and post a customer invoice. 3
Original PR description
Once a ZATCA invoice is posted, the "Reset to Draft" button is still shown on the form until the chain index is set on the move. In that window, a user can click it (or force it visible from Studio on an already-accepted invoice) and reset the move to draft, even though it has been -- or is about to be -- submitted to ZATCA. The invoice can then be resubmitted, breaking the ZATCA chain. Steps to reproduce: 1. Configure a SA company and set up ZATCA. 2. Create and post a customer invoice. 3. On the posted invoice, click "Reset to Draft" -> the move becomes draft, while the ZATCA submission still goes through. Fix: - Add a user error on account.move.button_draft if the invoice has l10n_sa_chain_index set task-6208977 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
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
When creating an activity in a custom app made with studio, no image is shown, and instead the alt text is shown with a missing image. This fixes the issue by showing a placeholder icon if no module is found for the activity group. opw-6282451 Previous behavior: <img width="1315" height="568" alt="image" src="https://github.com/user-attachments/assets/1e4fc1d5-3a35-4193-80fc-f6d161776e2e" /> New behavior: <img width="1315" height="568" alt="image" src="https://github.com/user-attac
Original PR description
When creating an activity in a custom app made with studio, no image is shown, and instead the alt text is shown with a missing image. This fixes the issue by showing a placeholder icon if no module is found for the activity group. opw-6282451 Previous behavior: <img width="1315" height="568" alt="image" src="https://github.com/user-attachments/assets/1e4fc1d5-3a35-4193-80fc-f6d161776e2e" /> New behavior: <img width="1315" height="568" alt="image" src="https://github.com/user-attachments/assets/6ed5ca8b-8481-470f-b7c6-dd59c616125e" /> (Original PR: https://github.com/odoo/odoo/pull/268957 Re-based on 17.0 as its the earliest version affected, so it can be forward-ported to future versions)