Daily updates from Odoo
Thursday, July 16, 2026
335 changes
25 changes
Enhancements to existing features
We remove the "refresh" button used to refresh the customer display screen from the debug windows as it's almost never used. see odoo/enterprise#123483 task-6357970
Original PR description
We remove the "refresh" button used to refresh the customer display screen from the debug windows as it's almost never used. see odoo/enterprise#123483 task-6357970
Resolved issues and error corrections
The Norwegian SAF-T export now uses the correct part of account numbers when assigning official grouping codes. This prevents incorrect reporting values when account numbers are extended, helping businesses produce compliant accounting exports.
Original PR description
Steps to reproduce: - change 1920 Banck account to 19204321 - go in general ledger and export to "SAF-T" Issue: The grouping code is 4321 Grouping code should match official grouping code. As a matter of fact the chart of account seems to match thos grouping account if we slice them correctly. opw-6285078 Forward-Port-Of: odoo/enterprise#122213 Forward-Port-Of: odoo/enterprise#121932
Opening Studio from the Working Files area no longer triggers an error. This prevents an unexpected interruption for users customizing or reviewing accounting return workflows.
Original PR description
Open Studio while on "Working Files" menu and view. Before this commit, the python raised an error becaude at some point `record[False]` (returning the current virtual record) was put in the return values of the onchange. After this commit, there is no error. runbot-error-941248 Forward-Port-Of: odoo/enterprise#124239
A new automated test verifies that overtime is calculated correctly when flexible employees take leave. This helps prevent payroll or attendance errors from returning in future updates.
Original PR description
For PR: https://github.com/odoo/odoo/pull/274831 This commit adds a test case to ensure that overtime is correctly calculated for the flexible employee opw-6259328,6284145 Forward-Port-Of: odoo/enterprise#124051 Forward-Port-Of: odoo/enterprise#123830
This fix makes HR payroll processes consistently ignore inactive records when looking up versioned employee data. It reduces the risk of outdated or archived information affecting payslips or paid time off allocation calculations.
Original PR description
We cannot assume in methods that the active_test is set. Therefore, we should always add active=True in search domains. Forward-Port-Of: odoo/enterprise#124459
Accepted Colombian DIAN credit notes no longer show the option to reset them to draft. This prevents users from reopening documents that have already been approved by the tax authority, helping maintain compliance and data consistency.
Original PR description
Issue: The reset button would still appear for credit notes that were already accepted by the DIAN. Steps to reproduce: Create a credit note, confirm it and send it to DIAN. You will be able to select Reset to Draft even though it shouldn't be possible to convert to draft after accepted by DIAN. Cause: The function to compute if the reset button would appear or not was only taking into account Invoices. Solution: Added credit notes, to the function that verifies if the reset button should appear. opw-6219265 Forward-Port-Of: odoo/enterprise#119696
Fixed extra blank lines appearing between invoice addenda and terms and conditions in Uruguayan electronic invoices. This helps prevent addenda content from being pushed onto a separate page unnecessarily when it still fits on the main document.
Original PR description
## Context When generating a CFE (Comprobante Fiscal Electrónico) that contains both a configured addenda (e.g. bank account details stored in `l10n_uy_edi_addenda_ids`) and terms & conditions from…
## Context
When generating a CFE (Comprobante Fiscal Electrónico) that contains both a configured addenda (e.g. bank account details stored in `l10n_uy_edi_addenda_ids`) and terms & conditions from the invoice's `narration` field, the resulting addenda string could end up with unnecessary blank lines between the two sections, causing the addenda to be rendered on a separate page even when the logical content fits within the 6-line threshold.
## Root Cause
`_l10n_uy_edi_get_addenda` joins both parts without stripping whitespace from either of them first, and adds two lines between addendas and terms and conditions:
addenda = addenda + "\n\n" + term_and_conditions if addenda else term_and_conditions
Two sources independently introduce extra newlines around the separator:
1. **Addenda content** — `_get_legends` returns the raw `content` field value of each addenda record. These fields commonly end with a trailing `\n`, so the addenda string already ends with a newline before the `"\n"` separator is concatenated.
2. **`html2plaintext`** — the `narration` field is stored as HTML. When converted to plain text, `html2plaintext` typically wraps paragraph content in leading/trailing newlines.
The combination of the trailing `\n` from the addenda, the explicit `"\n\n"` separator, and the leading/trailing `\n` from `html2plaintext` produces 2–3 consecutive newlines, which `splitlines()` counts as blank lines.
A realistic 4-line addenda + 1-line narration thus produces **7 lines** instead of the expected 5, crossing the 6-line threshold in `_get_report_params` and triggering `adenda=true` — which forces the addenda onto a separate page unnecessarily.
## Steps to Reproduce
1. Configure a `l10n_uy_edi.addenda` record of type `addenda` with multi-line content (4 lines)
2. Create and confirm an invoice with `narration` set to a short single-line term
3. Generate the CFE PDF via Uruware.
4. Observe that the addenda is rendered on a separate page despite the logical content being only 5 lines.
<img width="1042" height="448" alt="image" src="https://github.com/user-attachments/assets/b538211c-5f37-4648-979d-99cd75cf31c2" />
## Fix
Strip leading and trailing whitespace (including newlines) from both parts before joining them. The ternary is also replaced with an explicit `if/else` for clarity:
def _l10n_uy_edi_get_addenda(self):
addenda = self.l10n_uy_edi_document_id._get_legends("addenda", self)
if self.narration:
term_and_conditions = html2plaintext(self.narration).strip()
if addenda:
addenda = addenda.strip() + "\n" + term_and_conditions
else:
addenda = term_and_conditions
return self._l10n_uy_edi_clean_non_ascii_chars(addenda)
This guarantees exactly one `\n` separator between sections regardless of how the content fields were stored or how `html2plaintext` formatted the narration.
The threshold logic in `_get_report_params` is unchanged: addendas that genuinely exceed 6 lines (after wrapping at 140 chars) continue to be printed on a dedicated page.
Result
<img width="1117" height="456" alt="image" src="https://github.com/user-attachments/assets/3a2d942c-8c37-40f6-bc25-470c0bd25b08" />
Forward-Port-Of: odoo/enterprise#119283Opening a budget report record and then returning to the report list no longer triggers an error. This keeps the analytic budgets audit flow usable and avoids disruption for accounting users.
Original PR description
Problem:
The `budget.report` model had its default sorting (`_order`) set to False. When a user navigates back to the report list view via the breadcrumbs, the web client invokes `web_read_group`, which runs `self._order.split(',')`. Because `_order` is a boolean rather than a string, this raises an AttributeError and throws an RPC_ERROR.
Solution:
Set `_order = 'date desc'` on `budget.report`. Both queries within the `_table_query` UNION ALL expose a `date` column, providing a semantically correct and safe default ordering constraint.
Steps to replicate:
- Go to Accounting > Accounting > Analytic Budgets.
- Select any budget.
- Click 'Audit' on any budget line to land on the budget report view.
- Click to open any individual record.
- Navigate back using the breadcrumbs.
- -> RPC_ERROR: AttributeError: 'bool' object has no attribute 'split'
opw-6372610
Forward-Port-Of: odoo/enterprise#124191At the end of a successful populate session, run VACUUM ANALYZE so PostgreSQL statistics reflect the newly generated data and trigram indexes are updated. Usually, the autovacuum is triggered after bulk creation, but users may run their benchmarks before it has completed. Doing the maintenance explicitly makes populate absorb that waiting time instead. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Original PR description
At the end of a successful populate session, run VACUUM ANALYZE so PostgreSQL statistics reflect the newly generated data and trigram indexes are updated. Usually, the autovacuum is triggered after bulk creation, but users may run their benchmarks before it has completed. Doing the maintenance explicitly makes populate absorb that waiting time instead. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Before this commit, custom Many2XAutocomplete for resource has been removed to simplify the code, the problem is the m2m avatar user component has a custom Many2XAutocomplete for res.users record and that custom behavior is not expected for resource.resource records. This commit defines the standard Many2XAutocomplete component to m2m resource widget to make sure the one from user avatar is not used in resource components. task-[6361082](https://www.odoo.com/odoo/project/4105/tasks/6361082
Original PR description
Before this commit, custom Many2XAutocomplete for resource has been removed to simplify the code, the problem is the m2m avatar user component has a custom Many2XAutocomplete for res.users record and that custom behavior is not expected for resource.resource records. This commit defines the standard Many2XAutocomplete component to m2m resource widget to make sure the one from user avatar is not used in resource components. task-[6361082](https://www.odoo.com/odoo/project/4105/tasks/6361082)
The code view for new mailings could no longer be accessed; instead, the screen would become entirely white.. This was due to a new behavior from the mailing editor that would auto-set fullscreen edition mode on new mailings by recalling that a theme was just selected, and passing on a default fullscreen state to the iframe as a prop. Upon entering the code view, the iframe component gets recreated. The "newly selected theme" state was not reset, however; as a result, the component
Original PR description
The code view for new mailings could no longer be accessed; instead, the screen would become entirely white.. This was due to a new behavior from the mailing editor that would auto-set fullscreen…
The code view for new mailings could no longer be accessed; instead, the screen would become entirely white.. This was due to a new behavior from the mailing editor that would auto-set fullscreen edition mode on new mailings by recalling that a theme was just selected, and passing on a default fullscreen state to the iframe as a prop. Upon entering the code view, the iframe component gets recreated. The "newly selected theme" state was not reset, however; as a result, the component would grow to its fullscreen size as if a theme was just selected. However, as code view mode was active, there was no builder nor content elements, resulting in a white screen. Leaving this white screen was only possible through the <esc> hotkey. Steps to reproduce: - Open the Mailing app in developper mode (?debug=assets, ...) - Create a new mailing - Select the Events theme - In the top-right corner, click on the Code View icon Fix: Upon a code view toggle, isNewlySelectedTheme is reset. task-6350867
Modified `SampleServer._mockWebReadGroup` to intercept `groupby_read_specification`. It now dynamically fetches the requested related fields using `_mockWebSearchReadUnity` and safely injects them into the `__values` payload for each mock group, perfectly mirroring the standard ORM behavior. Task: [6307582](https://www.odoo.com/odoo/project/133/tasks/6307582) Forward-Port-Of: odoo/odoo#272135
Original PR description
Modified `SampleServer._mockWebReadGroup` to intercept `groupby_read_specification`. It now dynamically fetches the requested related fields using `_mockWebSearchReadUnity` and safely injects them into the `__values` payload for each mock group, perfectly mirroring the standard ORM behavior. Task: [6307582](https://www.odoo.com/odoo/project/133/tasks/6307582) Forward-Port-Of: odoo/odoo#272135
When computing taxes for the order, we group the base lines based on a grouping key. One of the components of this key is `is_refund`, and reward lines where considered as refund lines, which caused them to be grouped separately. This caused a discrepency between the tax amount computed in the frontend and the one computed in the backend. Steps to reproduce: ------------------- * Change the rounding method to globally * Create a 21% tax not included in price * Create a product with a pric
Original PR description
When computing taxes for the order, we group the base lines based on a grouping key. One of the components of this key is `is_refund`, and reward lines where considered as refund lines, which caused…
When computing taxes for the order, we group the base lines based on a grouping key. One of the components of this key is `is_refund`, and reward lines where considered as refund lines, which caused them to be grouped separately. This caused a discrepency between the tax amount computed in the frontend and the one computed in the backend. Steps to reproduce: ------------------- * Change the rounding method to globally * Create a 21% tax not included in price * Create a product with a price of 76.01 and the tax created above * Create a loyalty program with a 10% discount * Create a POS order with the product above and apply the loyalty program * Validate the order and generate the invoice > Observation: The invoice states that 0.01$ is still due Why the fix: ------------ In `round_tax_details_tax_amounts`, we try to detect any delta with the expeced tax amount. To do that we group the base lines based on a grouping key. The grouping key `is_refund` was incorrectly set to `true` for reward lines, which caused them to not be grouped. The delta was then not detected and tax amounts were not adjusted. We now make sure not to consider reward lines as refund. opw-6052112 Forward-Port-Of: odoo/odoo#275679 Forward-Port-Of: odoo/odoo#271577
### Steps to reproduce: - Download 'Sales' application - From 'Configuration' > 'Settings', enable 'Promotions, Loyalty & Gift Card' - From 'Products' > 'Gift cards & eWallet', Configure an eWallet program with a top-up product - Have a customer with an existing eWallet balance - Create a new sale order for that customer and add the eWallet top-up product - Pay the order using the customer's eWallet > The order gets discounted by the eWallet, effectively allowing the user to top up their
Original PR description
### Steps to reproduce: - Download 'Sales' application - From 'Configuration' > 'Settings', enable 'Promotions, Loyalty & Gift Card' - From 'Products' > 'Gift cards & eWallet', Configure an eWallet…
### Steps to reproduce:
- Download 'Sales' application
- From 'Configuration' > 'Settings', enable 'Promotions, Loyalty & Gift Card'
- From 'Products' > 'Gift cards & eWallet', Configure an eWallet program with a top-up product
- Have a customer with an existing eWallet balance
- Create a new sale order for that customer and add the eWallet top-up product
- Pay the order using the customer's eWallet
> The order gets discounted by the eWallet, effectively allowing the user to top up their balance
using the balance itself (infinite money glitch).
### Cause of Issue:
When computing the discountable amount for payment programs (like eWallets and gift cards), `_discountable_order` includes the total order amount. However, it did not exclude the program's own top-up products (`trigger_product_ids`) from the discountable lines.
### Fix:
If an order consists solely of top-up products, attempting to apply the eWallet now correctly raises a `UserError` ("There is nothing to discount").
opw-6341410
Forward-Port-Of: odoo/odoo#276141
Forward-Port-Of: odoo/odoo#273419**Steps to reproduce:** - Create a product A, tracked by lots - Create a kit product, include a component A - Change the UoM to 0.5 - Go to the PoS, order this kit product - Also order the component A, with a quantity of 2 - Pay for it, ask for an invoice - Go to the created picking - The Demand column is correctly computed and is 0.5 - The Quantity column is wrong and is 2 **Why the fix:** When getting the data from https://github.com/odoo/odoo/blob/e0d84c7fbb270d0d1f82572daefa
Original PR description
**Steps to reproduce:** - Create a product A, tracked by lots - Create a kit product, include a component A - Change the UoM to 0.5 - Go to the PoS, order this kit product - Also order the component…
**Steps to reproduce:** - Create a product A, tracked by lots - Create a kit product, include a component A - Change the UoM to 0.5 - Go to the PoS, order this kit product - Also order the component A, with a quantity of 2 - Pay for it, ask for an invoice - Go to the created picking - The Demand column is correctly computed and is 0.5 - The Quantity column is wrong and is 2 **Why the fix:** When getting the data from https://github.com/odoo/odoo/blob/e0d84c7fbb270d0d1f82572daefa96c2978d3785/addons/point_of_sale/models/stock_picking.py#L283 we always get the component's line, as the move's product is the component, even if it used to be the kit product's move. This is because when exploding a kit's moves, it gets the kit's component as a product instead of keeping the kit product. This was introducing a weird behavior because we took the quantity from the component line, and not from the kit line, meaning the kit would always have the same quantity as the component. We now check if the move is actually a kit product's move, and if it is we adapt the qty to correct one by fetching the correct line's qty, and adapting it with the correct UoM. Changing the line in itself would not work, as the kit itself is not tracked by lots, so we would not enter https://github.com/odoo/odoo/blob/e0d84c7fbb270d0d1f82572daefa96c2978d3785/addons/point_of_sale/models/stock_picking.py#L284 and the move line would not be correctly created. opw-6153000 Forward-Port-Of: odoo/odoo#276098 Forward-Port-Of: odoo/odoo#262551
…nding When cash rounding is enabled with "Only for cash payment methods", an order partially paid in cash and completed with an online payment could neither request the correct online amount nor be marked as paid. Steps to reproduce: - Enable cash rounding (e.g. 0.05, HALF-UP) with "Only for cash payment methods" - Create an order with a total of 15.28 - Add a cash payment of 10.00, then an online payment for the remainder The frontend requests 5.28 for the online payment, but as so
Original PR description
…nding When cash rounding is enabled with "Only for cash payment methods", an order partially paid in cash and completed with an online payment could neither request the correct online amount nor be…
…nding When cash rounding is enabled with "Only for cash payment methods", an order partially paid in cash and completed with an online payment could neither request the correct online amount nor be marked as paid. Steps to reproduce: - Enable cash rounding (e.g. 0.05, HALF-UP) with "Only for cash payment methods" - Create an order with a total of 15.28 - Add a cash payment of 10.00, then an online payment for the remainder The frontend requests 5.28 for the online payment, but as soon as the order contained a cash payment the server rounded the whole order total: get_and_set_online_payments_data() returned an unpaid amount of 5.30 (15.30 - 10.00), so the validation failed with "Invalid online payments". Even once the online payment of 5.28 was processed, the order remained stuck in draft with the money captured: _is_pos_order_paid() compared the paid amount (15.28) against the rounded total (15.30). Only the part of the order actually settled in cash must be rounded: non-cash payments (card, online, ...) always pay their exact share. - get_amount_unpaid() now returns the exact residual of the order when the rounding only applies to cash payment methods. - _get_rounded_amount() now only rounds the amount not covered by non-cash payments, resolving its old TODO. Cash-only orders and orders where the cash payment settles the rounded remainder are unaffected. opw-6314690 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#275472 Forward-Port-Of: odoo/odoo#275305
When an order is validated, the state is set to "paid" and a sync to the server is attempted. If the network dropped during that sync, the order could be permanently lost: the 300ms IndexedDB debounce had not yet fired, so the paid order lived only in memory, and no guard prevented the cashier from accidentally closing or refreshing the tab in that window. opw-6237823 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#27515
Original PR description
When an order is validated, the state is set to "paid" and a sync to the server is attempted. If the network dropped during that sync, the order could be permanently lost: the 300ms IndexedDB debounce had not yet fired, so the paid order lived only in memory, and no guard prevented the cashier from accidentally closing or refreshing the tab in that window. opw-6237823 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#275159 Forward-Port-Of: odoo/odoo#267799
When multiple invoices were sent to MyInvois in a single batch and at least one document failed validation, the failure branch of _myinvois_submit_documents added self.invoice_ids (all invoices in the batch) to invoice_to_cancel instead of the current record's invoices. Every sibling in the batch was then cancelled locally, even those whose own MyInvois submission had been accepted and moved to in_progress. The account.move ended up in state 'cancel' while its myinvois.document stayed 'valid'
Original PR description
When multiple invoices were sent to MyInvois in a single batch and at least one document failed validation, the failure branch of _myinvois_submit_documents added self.invoice_ids (all invoices in the batch) to invoice_to_cancel instead of the current record's invoices. Every sibling in the batch was then cancelled locally, even those whose own MyInvois submission had been accepted and moved to in_progress. The account.move ended up in state 'cancel' while its myinvois.document stayed 'valid', which violates the intended synchronization between the two records and blocked users from posting the credit note. Scope the cancellation to record.invoice_ids so only the invoice tied to the failing document is cancelled. 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#274727
Since commit d069ce59e28fda2bc25fb89fd01ee7b988a30fa0, the automatic invoice setting has been made company-specific. However, the settings view was not updated to indicate that this option is company-dependent, which could be confusing for users. Update the settings view to display the multi-company icon next to the automatic invoice setting, making it clear that the value is configured per company. Before | After -- | -- <img width="497" height="151" alt="image" src="https://github.com/
Original PR description
Since commit d069ce59e28fda2bc25fb89fd01ee7b988a30fa0, the automatic invoice setting has been made company-specific. However, the settings view was not updated to indicate that this option is company-dependent, which could be confusing for users. Update the settings view to display the multi-company icon next to the automatic invoice setting, making it clear that the value is configured per company. Before | After -- | -- <img width="497" height="151" alt="image" src="https://github.com/user-attachments/assets/7dd3ae93-235d-48c1-ac67-02728533ea6d" /> | <img width="483" height="153" alt="image" src="https://github.com/user-attachments/assets/fc68be88-4abb-4a94-bb1e-6a0cf05c46e6" /> </body></html> --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
### [FIX] portal: fix parent company handling Before this commit, users could not update the company name from the portal, and creating a company resulted in a regular contact instead of a company. This happened because the additional_values passed to mark the parent as is_company=True were overridden to False when the customer did not have a VAT number. This commit sets is_company after creating the company, ensuring the parent contact is always created as a company. ### [FIX] portal:
Original PR description
### [FIX] portal: fix parent company handling Before this commit, users could not update the company name from the portal, and creating a company resulted in a regular contact instead of a company. This happened because the additional_values passed to mark the parent as is_company=True were overridden to False when the customer did not have a VAT number. This commit sets is_company after creating the company, ensuring the parent contact is always created as a company. ### [FIX] portal: make commercial fields editable Before this commit, there was no way to edit commercial fields after a user entered a company name in the address form. Setting a company name created a parent company, and editing commercial fields was blocked because the customer had a parent record. This commit allows commercial fields to be edited again for customer addresses whose parent company has only a single direct child. Forward-Port-Of: odoo/odoo#276015 Forward-Port-Of: odoo/odoo#275207
When a "Products" snippet is dropped above the variant selector on a product page, selecting a variant displays one of the snippet's products instead of the chosen variant (its image/price take over the page). Steps to reproduce =================== 1. Create a product with 2+ variants and publish it. 2. Edit the product page, drag any block above the variant selector and add the "Products" dynamic snippet, then save. 3. Select a variant. => The page shows the snippet's first product inste
Original PR description
When a "Products" snippet is dropped above the variant selector on a product page, selecting a variant displays one of the snippet's products instead of the chosen variant (its image/price take over…
When a "Products" snippet is dropped above the variant selector on a product page, selecting a variant displays one of the snippet's products instead of the chosen variant (its image/price take over the page).
Steps to reproduce
===================
1. Create a product with 2+ variants and publish it.
2. Edit the product page, drag any block above the variant selector and add the "Products" dynamic snippet, then save.
3. Select a variant. => The page shows the snippet's first product instead of the variant.
Root cause
==========
`ProductPage._getCombinationInfo` reads the product ids from `parent.querySelector('button[name="add_to_cart"]')`, with `parent` being the whole `.js_product`. `querySelector` returns the first match in DOM order, and the dynamic "Products" snippet's cards reuse the same `button[name="add_to_cart"]` markup with their own product ids. When the snippet sits above the variants, its button comes first, so `/website_sale/get_combination_info` is called with the snippet product's ids and the page is updated with that product's data.
The interaction was introduced in saas-19.1 (See [1]) and the lookup switched from the unique `#add_to_cart` id to the by-name selector in (See [2]), which is what started matching the snippet's cards.
Fix
===
Pick the first `add_to_cart` button that is not inside a product card (`.oe_product_cart`), i.e. the main product's button.
[1]: https://github.com/odoo/odoo/commit/4682748e6e3c#diff-7e1a99da9e95d0c4df79ee4d7aa718e46bcb8b7f1ed78cde58782e075c833cd1R326
[2]: https://github.com/odoo/odoo/commit/1c732cf75a4a4faa960d6a98f08ae9dbe99b2b69#diff-7e1a99da9e95d0c4df79ee4d7aa718e46bcb8b7f1ed78cde58782e075c833cd1R329
opw-6248285
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#272540
Forward-Port-Of: odoo/odoo#268518Problem: When posting expenses, if the expense domain is set as mandatory in any of the analytic plans, users can still submit expenses without entering an analytic distribution. This only happens when the expense is auto-approved on submission, which happens when the expense's employee is also the expense's manager or when the employee doesn't have an expense manager. Steps to reproduce: 1. Create an analytic plan with optional default applicability 2. Add an applicability rule with the d
Original PR description
Problem: When posting expenses, if the expense domain is set as mandatory in any of the analytic plans, users can still submit expenses without entering an analytic distribution. This only happens…
Problem: When posting expenses, if the expense domain is set as mandatory in any of the analytic plans, users can still submit expenses without entering an analytic distribution. This only happens when the expense is auto-approved on submission, which happens when the expense's employee is also the expense's manager or when the employee doesn't have an expense manager. Steps to reproduce: 1. Create an analytic plan with optional default applicability 2. Add an applicability rule with the domain set as Expense and set its applicability as mandatory. 3. Create a new expense 4. Set the expense's manager the same as the expense's employee. 5. Don't enter any analytic distribution. 6. Submit the expense 7. Notice how the expense is submitted and auto-approved without any error, even though no analytic distribution is entered and the analytic plan has a mandatory rule for expenses. Cause: The validation of the analytic distribution was only triggered on the approval of the expense, but when the expense is auto-approved on submission, the validation is not triggered at all. Solution: Move the validation of the analytic distribution to the do_approve method, which gets called both when an expense is approved and when it's auto-approved on submission. opw-6187340 Forward-Port-Of: odoo/odoo#270268
The reporting labels "Difference" and "Balance" are confusing because "Difference" tracks system-qualified overtime while "Balance" represents accepted overtime hours. There is also a lack of consistency across views. This commit renames these fields to "Worked Extra Hours" and "Validated Extra Hours" to harmonize the naming everywhere task-6352142 Description of the issue/feature this PR addresses: Confusing and inconsistent naming for extra hours Current behavior before PR: - Rep
Original PR description
The reporting labels "Difference" and "Balance" are confusing because "Difference" tracks system-qualified overtime while "Balance" represents accepted overtime hours. There is also a lack of consistency across views. This commit renames these fields to "Worked Extra Hours" and "Validated Extra Hours" to harmonize the naming everywhere task-6352142 Description of the issue/feature this PR addresses: Confusing and inconsistent naming for extra hours Current behavior before PR: - Reporting uses "Difference" and "Balance". - Views use inconsistent labels. Desired behavior after PR is merged: Labels are consistently named "Worked Extra Hours" and "Validated Extra Hours" everywhere. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#275944 Forward-Port-Of: odoo/odoo#273631
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
Miscellaneous changes
Commit [1] introduces the concept of "global" cache for optimization. However when loading a bundle inside a secondary document such as an iframe, containing source files that are already loaded in the main document, those source files are not properly added in the secondary document. Example: load `web.assets_web` inside an iframe (this use case can happen in `mass_mailing` where we have to wrap some component inside a sandboxed iframe in order to display unsafe content (poorly sanitize
Original PR description
Commit [1] introduces the concept of "global" cache for optimization. However when loading a bundle inside a secondary document such as an iframe, containing source files that are already loaded in…
Commit [1] introduces the concept of "global" cache for optimization. However when loading a bundle inside a secondary document such as an iframe, containing source files that are already loaded in the main document, those source files are not properly added in the secondary document. Example: load `web.assets_web` inside an iframe (this use case can happen in `mass_mailing` where we have to wrap some component inside a sandboxed iframe in order to display unsafe content (poorly sanitized), such as emails). The issue is caused by an inconsistent usage of the `globalCache`: `getBundle` adds `bundleName` key to JS and CSS libs, but `computeAssetCaches` adds `url` keys to link or script elements. After this commit: - `globalCache` is only used in `getBundle` to map bundleNames to their libs - `loadCSS` and `loadJS` only use the documents caches to map urls to their element in the respective document. [1]: https://github.com/odoo/odoo/commit/5b1dc282b0f09c0fe6dcf9910a3a29cfd01d66a3 Forward-Port-Of: odoo/odoo#276239
15 changes
Enhancements to existing features
LEGAL REQUIREMENTS - As of January 2026, the 9% VAT will increase to 12%. PURPOSE - For each 9% VAT, add 12% VAT with the same tax tag and descriptions, so in the VAT report, it's put under the same lines. - And add the missing taxes from the sheet provided in the task description. Related PR: https://github.com/odoo/enterprise/pull/101773 Task-5269617 Forward-Port-Of: odoo/odoo#276414 Forward-Port-Of: odoo/odoo#239388
Original PR description
LEGAL REQUIREMENTS - As of January 2026, the 9% VAT will increase to 12%. PURPOSE - For each 9% VAT, add 12% VAT with the same tax tag and descriptions, so in the VAT report, it's put under the same lines. - And add the missing taxes from the sheet provided in the task description. Related PR: https://github.com/odoo/enterprise/pull/101773 Task-5269617 Forward-Port-Of: odoo/odoo#276414 Forward-Port-Of: odoo/odoo#239388
Resolved issues and error corrections
`completeActiveField` crashes with "Cannot read properties of undefined (reading 'activeFields')" when the `extra` argument carries a `.related` object but the target `activeField` does not have one. This happens when a many2one field appears twice in the same view tree with different widget configurations — one plain, one with `relatedFields`. The concrete trigger: 1. `stock.picking.batch` has an x2many `picking_ids` whose inline list/kanban view contains `partner_id` as a plain many2one.
Original PR description
`completeActiveField` crashes with "Cannot read properties of undefined (reading 'activeFields')" when the `extra` argument carries a `.related` object but the target `activeField` does not have one.…
`completeActiveField` crashes with "Cannot read properties of undefined (reading 'activeFields')" when the `extra` argument carries a `.related` object but the target `activeField` does not have one.
This happens when a many2one field appears twice in the same view tree with different widget configurations — one plain, one with `relatedFields`. The concrete trigger:
1. `stock.picking.batch` has an x2many `picking_ids` whose inline list/kanban view contains `partner_id` as a plain many2one. `extractFieldsFromArchInfo` creates an activeField for `partner_id` with no `.related` property.
2. `website_sale_stock` inherits the `stock.picking` form view and adds a second `partner_id` node with `widget="pickup_location_many2one"`. That widget declares `relatedFields` (`pickup_location_data`), which `Field.parseFieldNode` converts into a synthetic `views.default`. When `extractFieldsFromArchInfo` processes the inline form view of `picking_ids`, the resulting activeField for `partner_id` gets a `.related` object from those fields.
3. `extractFieldsFromArchInfo` then merges the form view fields into the list view fields via `completeActiveFields`. For `partner_id` the field already exists in the list's activeFields (without `.related`), so `completeActiveField` is called. It checks `if (extra.related)` — true — then immediately accesses `activeField.related.activeFields`, which is undefined → crash.
The sibling function `patchActiveFields` already handles this exact scenario correctly:
activeField.related = activeField.related || { activeFields: {}, fields: {} };
Apply the same defensive initialisation in `completeActiveField`.
Part-of: odoo/odoo#160187
Related: odoo/enterprise#59935
Related: odoo/upgrade#6315
Backport-of odoo/odoo@03c0d6F
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#276489The `bus subscription is refreshed when channel is joined` test is sometimes failing. This test doesn't make sense: it opens the command palette and wait for a subscription to be made. However, a subscription is only done when needed (opening the thread or being a member of the channel). The step was satisfied by luck. This commit fixes the test to reflect production code: the subscription is made once the channel is opened. runbot-941462 Description of the issue/feature this PR a
Original PR description
The `bus subscription is refreshed when channel is joined` test is sometimes failing. This test doesn't make sense: it opens the command palette and wait for a subscription to be made. However, a subscription is only done when needed (opening the thread or being a member of the channel). The step was satisfied by luck. This commit fixes the test to reflect production code: the subscription is made once the channel is opened. runbot-941462 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#274741
Previously: 1.`purchase_cdnur_regular` section was assigned to credit/debit notes of: - import of goods - import of services without RCM However: - import of goods should be handled through bill of supply - import of services without RCM is not possible Therefore, with this commit, such journal items are moved to `purchase_out_of_scope`. 2.`purcha
Original PR description
Previously:
1.`purchase_cdnur_regular` section was assigned to credit/debit notes of:
- import of goods
- import of services without RCM However:
- import of goods should be handled through bill of supply
- import of services without RCM is not possible Therefore, with this commit, such journal items are moved to `purchase_out_of_scope`.
2.`purchase_imp_services` section included import of services both with and
without RCM. Since import of services without RCM is not possible, those
journal items are now moved to `purchase_out_of_scope`.
3.Credit/debit notes of import of services with RCM were previously moved to
`purchase_out_of_scope`, which was incorrect. With this commit, they are now
correctly moved to `purchase_imp_services`.
task-6330737
Forward-Port-Of: odoo/odoo#276301
Forward-Port-Of: odoo/odoo#272453We cannot assume in methods that the active_test is set. Therefore, we should always add active=True in search domains. 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#276540
Original PR description
We cannot assume in methods that the active_test is set. Therefore, we should always add active=True in search domains. 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#276540
If we are in a case of a salary simulation, we don't care about future public holidays. The unlink done in _delete_future_public_holidays_timesheets was causing some cache invalidations which were messing up with the original offer. Forward-Port-Of: odoo/odoo#276520
Original PR description
If we are in a case of a salary simulation, we don't care about future public holidays. The unlink done in _delete_future_public_holidays_timesheets was causing some cache invalidations which were messing up with the original offer. Forward-Port-Of: odoo/odoo#276520
Loading a certificate could raise an unhandled exception instead of failing gracefully. Clearing the content, uploading a bundle with a corrupted certificate block, or handling certificates with unsupported signature algorithms or malformed extensions all could end up in a traceback. Guard those paths to ensure loading errors are handled. opw-6370529 Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: --- I
Original PR description
Loading a certificate could raise an unhandled exception instead of failing gracefully. Clearing the content, uploading a bundle with a corrupted certificate block, or handling certificates with unsupported signature algorithms or malformed extensions all could end up in a traceback. Guard those paths to ensure loading errors are handled. opw-6370529 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#276193 Forward-Port-Of: odoo/odoo#275325
When creating an event in Odoo and syncing it to Outlook, the event appeared in the correct slot on Outlook's calendar grid, but its detail panel showed start/end times labelled as UTC, causing a mismatch between the user's wall-clock time and what was displayed in Microsoft Outlook. The sync now sends the event in the organizer's local timezone with a matching timezone label, so Outlook displays the same wall-clock time and timezone that was entered. task-6167258 Description of the issue/
Original PR description
When creating an event in Odoo and syncing it to Outlook, the event appeared in the correct slot on Outlook's calendar grid, but its detail panel showed start/end times labelled as UTC, causing a mismatch between the user's wall-clock time and what was displayed in Microsoft Outlook. The sync now sends the event in the organizer's local timezone with a matching timezone label, so Outlook displays the same wall-clock time and timezone that was entered. task-6167258 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#276556 Forward-Port-Of: odoo/odoo#262488
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
The "bus subscription is refreshed when channel is joined/left" tests were flaky: - `mockDate` needs 2 digit date/time parts. The format used here didn't always produce them, so it silently fell back to a past date. That was enough to make the "left" test pass even without actually leaving. - The "left" test never actually left the channel: a confirm dialog blocked it. - The "join" test never actually joined the channel. - The tests expected `runAllTimers` to guarantee that every initial sub
Original PR description
The "bus subscription is refreshed when channel is joined/left" tests were flaky: - `mockDate` needs 2 digit date/time parts. The format used here didn't always produce them, so it silently fell back…
The "bus subscription is refreshed when channel is joined/left" tests were flaky: - `mockDate` needs 2 digit date/time parts. The format used here didn't always produce them, so it silently fell back to a past date. That was enough to make the "left" test pass even without actually leaving. - The "left" test never actually left the channel: a confirm dialog blocked it. - The "join" test never actually joined the channel. - The tests expected `runAllTimers` to guarantee that every initial subscription was done, but thats not the case, making the number of `subscribe` calls non-deterministic (e.g. flushing calls to `bus_service.add` but not ensuring the worker received them through its message port, and triggered the debounced `updateChannels`). Fixing the tests exposed a real bug: `memberBusSubscription` is meant to trigger a refresh whenever membership changes relative to the bus start time. As a boolean, "member, no refresh needed" and "not a member" are indistinguishable (both `false`), so leaving a channel joined before the bus started never changed the value and never triggered a refresh. This PR add a third state so membership and non-membership stay distinguishable regardless of when the bus started. runbot-941462 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#276205 Forward-Port-Of: odoo/odoo#275938
**PROBLEM** `_cron_migrate_local_to_cloud_storage()` is used to migrate attachment to a cloud storage. It delete the attachment from the database, rendering it innacessible from the server-side. The problem is it can delete attachment used in business logic. **STEP TO REPRODUCE** 1. Install and configure cloud_storage + a provider (e.g. cloud_storage_google) and cloud_storage_migration. 2. Install l10n_mx_edi and stamp a customer invoice. The CFDI XML is stored as an ir.attachment that:
Original PR description
**PROBLEM** `_cron_migrate_local_to_cloud_storage()` is used to migrate attachment to a cloud storage. It delete the attachment from the database, rendering it innacessible from the server-side. The…
**PROBLEM** `_cron_migrate_local_to_cloud_storage()` is used to migrate attachment to a cloud storage. It delete the attachment from the database, rendering it innacessible from the server-side. The problem is it can delete attachment used in business logic. **STEP TO REPRODUCE** 1. Install and configure cloud_storage + a provider (e.g. cloud_storage_google) and cloud_storage_migration. 2. Install l10n_mx_edi and stamp a customer invoice. The CFDI XML is stored as an ir.attachment that: - is referenced by a business Many2one l10n_mx_edi.document.attachment_id (copied into account.move.l10n_mx_edi_cfdi_attachment_id), - has res_field = NULL (record attachment, not a field binary), - is posted to the chatter → it has a row in message_attachment_rel. 3. Add account.move to cloud_storage_migration_message_models and set a low enough cloud_storage_min_file_size. 4. Run the cron _cron_migrate_local_to_cloud_storage. 5. Open the invoice and trigger any recompute of the EDI chain (e.g. Update Payments, a reconciliation, or re-stamping). **FIX** Filters out attachment linked to a model listed by the already existing function `_get_cloud_storage_unsupported_models()` opw-6347198 Forward-Port-Of: odoo/odoo#275945
## Steps to reproduce: - Install sale_timesheet - Create a timesheet with a Sale order item linked to it - Change the sale order item on that timesheet - Change the project linked to the timesheet to a non-billable project - Notice the sale order item still linked to the timesheet ## Cause: When computing the so_line we filter out the records that has is_so_line_edited as true, so when changing the SOL before changing the project we don't reset so_line field when setting a non-bill
Original PR description
## Steps to reproduce: - Install sale_timesheet - Create a timesheet with a Sale order item linked to it - Change the sale order item on that timesheet - Change the project linked to the timesheet to a non-billable project - Notice the sale order item still linked to the timesheet ## Cause: When computing the so_line we filter out the records that has is_so_line_edited as true, so when changing the SOL before changing the project we don't reset so_line field when setting a non-billable project. ## Fix: We reset the is_so_line_edited field to false when changing the project to a non-billable one. opw-6311549 Forward-Port-Of: odoo/odoo#276386 Forward-Port-Of: odoo/odoo#275919
Before this commit, when making a test print from the POS backend, the following issues would occur: - Very slow response - Missing cut, and extra 'A' character is printed This commit fixes both these issues. The slow response is avoided by not performing the network tests when they aren't used in the printed receipt. The cut issue is solved by appending a newline character to the message. task-6391141 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/s
Original PR description
Before this commit, when making a test print from the POS backend, the following issues would occur: - Very slow response - Missing cut, and extra 'A' character is printed This commit fixes both these issues. The slow response is avoided by not performing the network tests when they aren't used in the printed receipt. The cut issue is solved by appending a newline character to the message. task-6391141 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276524
`_l10n_tr_nilvera_add_pdf_to_invoice` writes the response from `client.request('GET', '.../pdf')` directly into `ir.attachment.raw`. The Nilvera client sets `Accept: application/json` on the session and calls `response.json()` by default, so the returned value is a Python `str` holding the base64-encoded PDF body, not raw binary bytes. The previous code wrote to the base64-aware `datas` field, which auto-decoded its input. An earlier fix switched to `raw` to work around a `binascii.Error` fro
Original PR description
`_l10n_tr_nilvera_add_pdf_to_invoice` writes the response from `client.request('GET', '.../pdf')` directly into `ir.attachment.raw`. The Nilvera client sets `Accept: application/json` on the session…
`_l10n_tr_nilvera_add_pdf_to_invoice` writes the response from `client.request('GET', '.../pdf')` directly into `ir.attachment.raw`. The Nilvera client sets `Accept: application/json` on the session and calls `response.json()` by default, so the returned value is a Python `str` holding the base64-encoded PDF body, not raw binary bytes.
The previous code wrote to the base64-aware `datas` field, which auto-decoded its input. An earlier fix switched to `raw` to work around a `binascii.Error` from Python 3.14's stricter base64 validation in the `datas` auto-decode path. That switch silently changed what ends up on disk (`datas` decodes its input, `raw` does not)
Storing that string in the binary `raw` field encodes it as UTF-8, so the file on disk ends up as the literal ASCII of the base64 text. The attachment is served as `application/pdf` but the browser receives base64 ASCII and cannot preview or download the PDF.
Call `b64decode(response)` before storing so the attachment contains the actual PDF bytes.
OPW-6302803
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#274931
Forward-Port-Of: odoo/odoo#270759Miscellaneous changes
Fix field name typo. @qrtl QT6381 Forward-Port-Of: odoo/odoo#276119
Original PR description
Fix field name typo. @qrtl QT6381 Forward-Port-Of: odoo/odoo#276119
14 changes
New functionality added to Odoo
This module adds extra functions on the point of sale for l10n_tw_edi_ecpay, passing values from pos order to invoice for creating Taiwan E-invoice task-5122414 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#255980
Original PR description
This module adds extra functions on the point of sale for l10n_tw_edi_ecpay, passing values from pos order to invoice for creating Taiwan E-invoice task-5122414 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#255980
Enhancements to existing features
Servers would return a 403 because we annoy them for downloading the WSDL/XSD at every call. opw-6237180 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#276345 Forward-Port-Of: odoo/odoo#267482
Original PR description
Servers would return a 403 because we annoy them for downloading the WSDL/XSD at every call. opw-6237180 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#276345 Forward-Port-Of: odoo/odoo#267482
Resolved issues and error corrections
Loading a certificate could raise an unhandled exception instead of failing gracefully. Clearing the content, uploading a bundle with a corrupted certificate block, or handling certificates with unsupported signature algorithms or malformed extensions all could end up in a traceback. Guard those paths to ensure loading errors are handled. opw-6370529 Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: --- I
Original PR description
Loading a certificate could raise an unhandled exception instead of failing gracefully. Clearing the content, uploading a bundle with a corrupted certificate block, or handling certificates with unsupported signature algorithms or malformed extensions all could end up in a traceback. Guard those paths to ensure loading errors are handled. opw-6370529 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#276193 Forward-Port-Of: odoo/odoo#275325
Previously: 1.`purchase_cdnur_regular` section was assigned to credit/debit notes of: - import of goods - import of services without RCM However: - import of goods should be handled through bill of supply - import of services without RCM is not possible Therefore, with this commit, such journal items are moved to `purchase_out_of_scope`. 2.`purcha
Original PR description
Previously:
1.`purchase_cdnur_regular` section was assigned to credit/debit notes of:
- import of goods
- import of services without RCM However:
- import of goods should be handled through bill of supply
- import of services without RCM is not possible Therefore, with this commit, such journal items are moved to `purchase_out_of_scope`.
2.`purchase_imp_services` section included import of services both with and
without RCM. Since import of services without RCM is not possible, those
journal items are now moved to `purchase_out_of_scope`.
3.Credit/debit notes of import of services with RCM were previously moved to
`purchase_out_of_scope`, which was incorrect. With this commit, they are now
correctly moved to `purchase_imp_services`.
task-6330737
Forward-Port-Of: odoo/odoo#276301
Forward-Port-Of: odoo/odoo#272453..., pos_restaurant, pos_self_order, pos_urban_piper --- During the refactor of the receipt printing system, some CSS classes were no longer loaded with preparation tickets. As a result, preparation tickets lost part of their original styling. To restore the expected rendering, ensure all required classes are properly loaded while keeping the loading minimal. Additionally, some receipt data were missing after the refactor and some UI elements could be improved. This commit restores t
Original PR description
..., pos_restaurant, pos_self_order, pos_urban_piper --- During the refactor of the receipt printing system, some CSS classes were no longer loaded with preparation tickets. As a result, preparation tickets lost part of their original styling. To restore the expected rendering, ensure all required classes are properly loaded while keeping the loading minimal. Additionally, some receipt data were missing after the refactor and some UI elements could be improved. This commit restores the missing data and improves the overall UI. It also fixes an issue where customer notes were not printed on the receipt. Templates checked: * point_of_sale.pos_order_change_receipt * point_of_sale.pos_order_change_receipt_line --- Task: https://www.odoo.com/odoo/project/1737/tasks/6133403 Refacto: https://github.com/odoo/odoo/pull/244395
CertificateAdapter presents a client certificate stored in the database instead of on disk when opening an HTTPS connection (used by the l10n_es EDI modules verifactu, sii and tbai). It loaded that certificate on the connection path of requests 2.31, but requests >= 2.32 changed that path (no longer calls get_connection()), so the step was skipped and the call crashed with: "TypeError: expected str, bytes or os.PathLike object, not certificate" Odoo pins requests 2.31.0 (max depending on Pyth
Original PR description
CertificateAdapter presents a client certificate stored in the database instead of on disk when opening an HTTPS connection (used by the l10n_es EDI modules verifactu, sii and tbai). It loaded that…
CertificateAdapter presents a client certificate stored in the database instead of on disk when opening an HTTPS connection (used by the l10n_es EDI modules verifactu, sii and tbai). It loaded that certificate on the connection path of requests 2.31, but requests >= 2.32 changed that path (no longer calls get_connection()), so the step was skipped and the call crashed with: "TypeError: expected str, bytes or os.PathLike object, not certificate" Odoo pins requests 2.31.0 (max depending on Python version), but online databases can use the version shipped by the OS (2.32.x on recent Ubuntu 26). Set the certificate up when the adapter is created instead of on that connection call. That step runs the same on every requests version, so the fix works both before and after 2.32. Steps to reproduce: - Spanish company with Veri*Factu and a certificate, on a server running requests >= 2.32 (saas-19.3 database for exemple on ubuntu 26) - Post a customer invoice and send it to Veri*Factu. => TypeError Reference: https://github.com/psf/requests/blob/f361ead047be5cb873174218582f7d8b9fcd9f49/HISTORY.md?plain=1#L146 Ticket [link](https://www.odoo.com/odoo/project.task/6366028) opw-6366028 Forward-Port-Of: odoo/odoo#275324
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
When creating an event in Odoo and syncing it to Outlook, the event appeared in the correct slot on Outlook's calendar grid, but its detail panel showed start/end times labelled as UTC, causing a mismatch between the user's wall-clock time and what was displayed in Microsoft Outlook. The sync now sends the event in the organizer's local timezone with a matching timezone label, so Outlook displays the same wall-clock time and timezone that was entered. task-6167258 Description of the issue/
Original PR description
When creating an event in Odoo and syncing it to Outlook, the event appeared in the correct slot on Outlook's calendar grid, but its detail panel showed start/end times labelled as UTC, causing a mismatch between the user's wall-clock time and what was displayed in Microsoft Outlook. The sync now sends the event in the organizer's local timezone with a matching timezone label, so Outlook displays the same wall-clock time and timezone that was entered. task-6167258 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#276556 Forward-Port-Of: odoo/odoo#262488
Click Working Files menu, then open studio. Before this commit there was an error, because the accounting code tried to check access rights on an new record (no id) After this commit there is no crash. runbot-error-941248 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#276269
Original PR description
Click Working Files menu, then open studio. Before this commit there was an error, because the accounting code tried to check access rights on an new record (no id) After this commit there is no crash. runbot-error-941248 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#276269
### Steps to reproduce: - Open the Todo app - In the editor, insert the following content: `<p>a</p><div class="oe_unbreakable"><br></div><p>b</p>` - Double-click the empty unbreakable node - Open the color picker and hover over a color - Toolbar and color picker get closed ### Root cause: - Hovering a color in an empty unbreakable node replaces the `<br>` with a `<font data-oe-zws-empty-inline>` containing a ZWS (`\u200b`). This triggers a selectionchange where `isToolbarVisible()`
Original PR description
### Steps to reproduce: - Open the Todo app - In the editor, insert the following content: `<p>a</p><div class="oe_unbreakable"><br></div><p>b</p>` - Double-click the empty unbreakable node - Open…
### Steps to reproduce: - Open the Todo app - In the editor, insert the following content: `<p>a</p><div class="oe_unbreakable"><br></div><p>b</p>` - Double-click the empty unbreakable node - Open the color picker and hover over a color - Toolbar and color picker get closed ### Root cause: - Hovering a color in an empty unbreakable node replaces the `<br>` with a `<font data-oe-zws-empty-inline>` containing a ZWS (`\u200b`). This triggers a selectionchange where `isToolbarVisible()` finds no `<br>` and no visible text, returns false, and closes the toolbar — which reverts the preview, reopens the toolbar, and causes a flicker loop. ### Solution: - Instead of calling `fillEmpty()` (which inserts a ZWS placeholder) on empty blocks containing `<br>` preserve the `<br>` element by appending it directly inside the `<font>` tag which keeps toolbar open. task-6312933 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#271507
**PROBLEM** `_cron_migrate_local_to_cloud_storage()` is used to migrate attachment to a cloud storage. It delete the attachment from the database, rendering it innacessible from the server-side. The problem is it can delete attachment used in business logic. **STEP TO REPRODUCE** 1. Install and configure cloud_storage + a provider (e.g. cloud_storage_google) and cloud_storage_migration. 2. Install l10n_mx_edi and stamp a customer invoice. The CFDI XML is stored as an ir.attachment that:
Original PR description
**PROBLEM** `_cron_migrate_local_to_cloud_storage()` is used to migrate attachment to a cloud storage. It delete the attachment from the database, rendering it innacessible from the server-side. The…
**PROBLEM** `_cron_migrate_local_to_cloud_storage()` is used to migrate attachment to a cloud storage. It delete the attachment from the database, rendering it innacessible from the server-side. The problem is it can delete attachment used in business logic. **STEP TO REPRODUCE** 1. Install and configure cloud_storage + a provider (e.g. cloud_storage_google) and cloud_storage_migration. 2. Install l10n_mx_edi and stamp a customer invoice. The CFDI XML is stored as an ir.attachment that: - is referenced by a business Many2one l10n_mx_edi.document.attachment_id (copied into account.move.l10n_mx_edi_cfdi_attachment_id), - has res_field = NULL (record attachment, not a field binary), - is posted to the chatter → it has a row in message_attachment_rel. 3. Add account.move to cloud_storage_migration_message_models and set a low enough cloud_storage_min_file_size. 4. Run the cron _cron_migrate_local_to_cloud_storage. 5. Open the invoice and trigger any recompute of the EDI chain (e.g. Update Payments, a reconciliation, or re-stamping). **FIX** Filters out attachment linked to a model listed by the already existing function `_get_cloud_storage_unsupported_models()` opw-6347198 Forward-Port-Of: odoo/odoo#275945
## Steps to reproduce: - Install sale_timesheet - Create a timesheet with a Sale order item linked to it - Change the sale order item on that timesheet - Change the project linked to the timesheet to a non-billable project - Notice the sale order item still linked to the timesheet ## Cause: When computing the so_line we filter out the records that has is_so_line_edited as true, so when changing the SOL before changing the project we don't reset so_line field when setting a non-bill
Original PR description
## Steps to reproduce: - Install sale_timesheet - Create a timesheet with a Sale order item linked to it - Change the sale order item on that timesheet - Change the project linked to the timesheet to a non-billable project - Notice the sale order item still linked to the timesheet ## Cause: When computing the so_line we filter out the records that has is_so_line_edited as true, so when changing the SOL before changing the project we don't reset so_line field when setting a non-billable project. ## Fix: We reset the is_so_line_edited field to false when changing the project to a non-billable one. opw-6311549 Forward-Port-Of: odoo/odoo#276386 Forward-Port-Of: odoo/odoo#275919
Before this commit, when making a test print from the POS backend, the following issues would occur: - Very slow response - Missing cut, and extra 'A' character is printed This commit fixes both these issues. The slow response is avoided by not performing the network tests when they aren't used in the printed receipt. The cut issue is solved by appending a newline character to the message. task-6391141 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/s
Original PR description
Before this commit, when making a test print from the POS backend, the following issues would occur: - Very slow response - Missing cut, and extra 'A' character is printed This commit fixes both these issues. The slow response is avoided by not performing the network tests when they aren't used in the printed receipt. The cut issue is solved by appending a newline character to the message. task-6391141 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276524
Miscellaneous changes
Fix field name typo. @qrtl QT6381 Forward-Port-Of: odoo/odoo#276119
Original PR description
Fix field name typo. @qrtl QT6381 Forward-Port-Of: odoo/odoo#276119
4 changes
Enhancements to existing features
Add the missing 2912, 2913, 2914, and 2915 accounts from the 2026 PCG so the French chart matches the balance sheet impairment mappings for tangible fixed assets. task-6226138 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#265160
Original PR description
Add the missing 2912, 2913, 2914, and 2915 accounts from the 2026 PCG so the French chart matches the balance sheet impairment mappings for tangible fixed assets. task-6226138 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#265160
Resolved issues and error corrections
Steps to reproduce: - Go to Website - Upload an image in the company logo (navbar) - Open the website editor and click on the logo - Open the image info panel Current behavior: When clicking on the website logo in the editor, the size shown is always a constant number ~5.9kB, regardless of the actual size of the uploaded logo. The correct size is visible in the browser DOM. This gives users the wrong impression that their image is being heavily compressed or losing quality when it isn'
Original PR description
Steps to reproduce: - Go to Website - Upload an image in the company logo (navbar) - Open the website editor and click on the logo - Open the image info panel Current behavior: When clicking on the…
Steps to reproduce: - Go to Website - Upload an image in the company logo (navbar) - Open the website editor and click on the logo - Open the image info panel Current behavior: When clicking on the website logo in the editor, the size shown is always a constant number ~5.9kB, regardless of the actual size of the uploaded logo. The correct size is visible in the browser DOM. This gives users the wrong impression that their image is being heavily compressed or losing quality when it isn't. Reason: When clicking the logo, the editor tries to find the original, unprocessed version of the image so it can support cropping and other edits. It does this by asking the server to match the image's URL to a stored attachment. The website logo is served through a dynamic link (`/web/image/website/<id>/logo/<name>`) that isn't tied to a regular attachment record the way normal content images are, since it isn't uploaded through the usual media picker. Because of this, the server can't find a matching original, and the editor is left without a valid image source to work with. As a fallback, the editor tries to load a placeholder path instead of a real image. This request fails and silently resolves to Odoo's generic "image not found" placeholder. All further processing (and the size calculation) then happens on this small placeholder image instead of the actual logo, which is why the size shown never changes. Fix: When `get_image_info` does not return a usable `original`, `loadImageInfo` now falls back to using the image's own current src as `originalSrc`, instead of leaving it unset. This ensures `loadImage` always receives a valid, resolvable URL, so image processing (and the size shown) reflects the actual logo. opw-6260496 Forward-Port-Of: odoo/odoo#273542
CertificateAdapter presents a client certificate stored in the database instead of on disk when opening an HTTPS connection (used by the l10n_es EDI modules verifactu, sii and tbai). It loaded that certificate on the connection path of requests 2.31, but requests >= 2.32 changed that path (no longer calls get_connection()), so the step was skipped and the call crashed with: "TypeError: expected str, bytes or os.PathLike object, not certificate" Odoo pins requests 2.31.0 (max depending on Pyth
Original PR description
CertificateAdapter presents a client certificate stored in the database instead of on disk when opening an HTTPS connection (used by the l10n_es EDI modules verifactu, sii and tbai). It loaded that…
CertificateAdapter presents a client certificate stored in the database instead of on disk when opening an HTTPS connection (used by the l10n_es EDI modules verifactu, sii and tbai). It loaded that certificate on the connection path of requests 2.31, but requests >= 2.32 changed that path (no longer calls get_connection()), so the step was skipped and the call crashed with: "TypeError: expected str, bytes or os.PathLike object, not certificate" Odoo pins requests 2.31.0 (max depending on Python version), but online databases can use the version shipped by the OS (2.32.x on recent Ubuntu 26). Set the certificate up when the adapter is created instead of on that connection call. That step runs the same on every requests version, so the fix works both before and after 2.32. Steps to reproduce: - Spanish company with Veri*Factu and a certificate, on a server running requests >= 2.32 (saas-19.3 database for exemple on ubuntu 26) - Post a customer invoice and send it to Veri*Factu. => TypeError Reference: https://github.com/psf/requests/blob/f361ead047be5cb873174218582f7d8b9fcd9f49/HISTORY.md?plain=1#L146 Ticket [link](https://www.odoo.com/odoo/project.task/6366028) opw-6366028 Forward-Port-Of: odoo/odoo#275324
### Steps to reproduce: - Open the Todo app - In the editor, insert the following content: `<p>a</p><div class="oe_unbreakable"><br></div><p>b</p>` - Double-click the empty unbreakable node - Open the color picker and hover over a color - Toolbar and color picker get closed ### Root cause: - Hovering a color in an empty unbreakable node replaces the `<br>` with a `<font data-oe-zws-empty-inline>` containing a ZWS (`\u200b`). This triggers a selectionchange where `isToolbarVisible()`
Original PR description
### Steps to reproduce: - Open the Todo app - In the editor, insert the following content: `<p>a</p><div class="oe_unbreakable"><br></div><p>b</p>` - Double-click the empty unbreakable node - Open…
### Steps to reproduce: - Open the Todo app - In the editor, insert the following content: `<p>a</p><div class="oe_unbreakable"><br></div><p>b</p>` - Double-click the empty unbreakable node - Open the color picker and hover over a color - Toolbar and color picker get closed ### Root cause: - Hovering a color in an empty unbreakable node replaces the `<br>` with a `<font data-oe-zws-empty-inline>` containing a ZWS (`\u200b`). This triggers a selectionchange where `isToolbarVisible()` finds no `<br>` and no visible text, returns false, and closes the toolbar — which reverts the preview, reopens the toolbar, and causes a flicker loop. ### Solution: - Instead of calling `fillEmpty()` (which inserts a ZWS placeholder) on empty blocks containing `<br>` preserve the `<br>` element by appending it directly inside the `<font>` tag which keeps toolbar open. task-6312933 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#271507
3 changes
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#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#269465
Resolved issues and error corrections
Rather than assign `log_target` and blow up if the third branch is taken, use a `defaultdict` and just increment the count in each branch on hit. The continue in the third branch is not strictly necessary, but that way it protects us if anyone decides to add post-processing which also doesn't account for the third branch. Forward-Port-Of: odoo/odoo#276481
Original PR description
Rather than assign `log_target` and blow up if the third branch is taken, use a `defaultdict` and just increment the count in each branch on hit. The continue in the third branch is not strictly necessary, but that way it protects us if anyone decides to add post-processing which also doesn't account for the third branch. Forward-Port-Of: odoo/odoo#276481
Issue: ---------------------------------------- When resetting the mail template "Settings: 2Fa New Login" an error is raised. Steps to reproduce: ---------------------------------------- - Search for the mail template "Settings: 2Fa New Login" - Click "Reset Template" - Traceback Also happens when changing the template and saving. Cause: ---------------------------------------- Since b6b05f14ee37b5901abb8c5ee046727a28b9d94e there is an assert in the method `_get_totp_mail_code(
Original PR description
Issue: ---------------------------------------- When resetting the mail template "Settings: 2Fa New Login" an error is raised. Steps to reproduce: ---------------------------------------- - Search for the mail template "Settings: 2Fa New Login" - Click "Reset Template" - Traceback Also happens when changing the template and saving. Cause: ---------------------------------------- Since b6b05f14ee37b5901abb8c5ee046727a28b9d94e there is an assert in the method `_get_totp_mail_code()` to ensure it's used during 2FA. But when passing by `_check_can_be_rendered()` we render the template not in sudo and `_get_totp_mail_code()` resulting in a traceback. Solution: ---------------------------------------- Rendering the template in sudo in `_check_can_be_rendered()`. As this method is only to make sure the template has no broken qweb ([src](999df6d4a3b5d21648e5e09757661714e99e1154)), rendering in sudo should not be an issue. opw-6300337
22 changes
Enhancements to existing features
The Sendcloud delivery setting previously called "Use Batch Shipping" is now named "Use Multicollo". This aligns the wording in Odoo with Sendcloud terminology, reducing customer confusion without changing the underlying functionality.
Original PR description
In order to avoid confusion for the customer, "Use Batch Shipping" was renamed to "Use Multicollo".This way it is consistent with the terminology used by Sendcloud. task-6048477 Forward-Port-Of: odoo/enterprise#123195 Forward-Port-Of: odoo/enterprise#122133
Timesheet Assistant suggestions can now be selected more quickly by dragging over them while holding the mouse button. This also prevents Ctrl-clicking a suggestion from accidentally opening a new Odoo page, making bulk actions smoother and less disruptive.
Original PR description
Currently when the user uses ctrl + click on a suggestion, a new odoo page is opened. This is an undesirable side effect and it is removed in this commit. Also, users have to manually click on each suggestion when they want to remove them in batch, or create one timesheet for a bunch of suggestion. This commit lets user hover over suggestion with the mouse button pressed to select them. task-6385047
Updates improve the AI-powered website builder workflow, including its configuration data, builder integration, and supporting utilities. This should help users create or adjust website content more smoothly with AI assistance, though the change appears to be work in progress.
The settings pages for Planning and Field Service have been reorganized into clearer sections such as Planning, Operations, Sales & Billing, and Analytics. This makes configuration easier for users as the number of available options has grown, helping teams find the right setup choices faster.
Original PR description
Reorganize the settings view for Planning and Field Service into intuitive sub-categories (Planning, Operations, Sales & Billing, Analytics). As the available options have grown significantly, this restructuring guides the user more effectively through the configuration steps. - Planning: Employee Unavailabilities, Project Planning, Recurring Shifts - Operations: Field Service, Stock by Vehicle, Worksheets, Equipment, Customer Report, Geolocation - Sales & Billing: Billing, Website Form, Products, Quotations, Travel Fees - Analytics: Customer Ratings task:6172495
Accounting users can now open the related budget directly from an account move form using a new smart button. This saves time when reviewing financial entries and makes it easier to trace how entries affect budgets through analytic lines.
Original PR description
On the account move form view, add a smart button that redirects to the linked budget (linked through the analytic lines) task-6307932
Resolved issues and error corrections
Live field service maps no longer recalculate routes when the current user's position changes, because those routes are based on worker pins instead. This reduces unnecessary processing and helps preserve routing service tokens without changing the visible route behavior.
Original PR description
The routing fetching in `updateUserPosition` of the `MapModel` should not be triggered for the live map. Right now, when changing user position, the routes are fetched again. However, for the live map, this should have no effect on routes, as they start from the user pins instead of the current user's position. This will avoid computing all over routes and preserve tokens. task-6307279 Forward-Port-Of: odoo/enterprise#123536 Forward-Port-Of: odoo/enterprise#123294
This fix restores supported flows where portal users need to archive or unarchive documents when the system explicitly grants elevated permission. It also prevents errors in cases where no documents are present, making document handling more reliable.
Original PR description
In #116886, we fixed the blocking of portal users to (un)archive documents, but it appears that some flows did rely on it and we were lacking a way of supporting it. Task-6205627 Forward-Port-Of: odoo/enterprise#123171 Forward-Port-Of: odoo/enterprise#123015
Subscription products that also allow one-time purchases now display the original price with a strikethrough next to the Buy Once price. This makes discounts or price comparisons clearer for shoppers and helps avoid confusion on product pages.
Original PR description
Version - saas-19.1 Steps to reproduce: - Enable 'Accept One Time Sale' on a subscription product - Open the product page on the website Issue: For subscription products with one time sale enabled, the original price was not shown as a strikethrough next to the Buy Once price. Fix: - Captured and exposed the original price to the template before it gets overwritten during subscription price processing - Added the missing strikethrough element to the Buy Once section of the product page Task ID - 6260207 Forward-Port-Of: odoo/enterprise#123977 Forward-Port-Of: odoo/enterprise#119488
Luxembourg payroll now uses the correct official salary index values for contracts starting from May 2025 and June 2026. This prevents incorrect contract signature index values and helps ensure related payroll calculations reflect current legal parameters.
Original PR description
## Issue When creating or browsing an existing contract in a Luxembourgish company, the current index shown is the one from September 2023 (which was up-to-date until Mai 2025). If the contract is…
## Issue
When creating or browsing an existing contract in a Luxembourgish company, the current index shown is the one from September 2023 (which was up-to-date until Mai 2025). If the contract is created after Mai 2025, the "Index on Contract Signature" field is also wrong.
## Steps to reproduce
1. Install *Luxembourg - Payroll* (`l10n_lu_hr_payroll`)
2. Using a Luxembourgish company, create a new contract for an employee
- *Contract Start Date*: Any date after 05/01/2025 (Mai 1st 2025)
3. __The *Index on Contract Signature* shows `944.43`, which is the index from September 2023. It does not match with the most recent indices.__
## Cause
The two most recent indices are missing from the [`rule_parameter_lu_index`](https://github.com/odoo/enterprise/blob/885edbc270a86ab76e0a6eff4acb5767c0fe29d1/l10n_lu_hr_payroll/data/rule_parameters/general_rules_data.xml#L4-L58). These indices are:
- `968.04` from 2025-05-01 (Mai 1st 2025)
- `992.24` from 2026-06-01 (June 1st 2026)
These values were taken from [here](https://salary.lu/en/tools/social-parameters/indexation-of-salaries) and double-checked [here](https://lustat.statec.lu/?lc=en&tm=DF_C1201&pg=0&snb=1).
## Tests modification
Updating the latest index had an impact on the tests from the `l10n_lu_hr_payroll_account` (testing the salary rules). In fact, the current index (`l10n_lu_current_index`) is [used to compute the indexed wage](https://github.com/odoo/enterprise/blob/7892d035ddb968d67a3e8da0daf91292bd8fb499/l10n_lu_hr_payroll/models/hr_contract.py#L24-L33) (`l10n_lu_indexed_wage`), which is then used to compute most lines in the payslip (e.g., the `WAGE_SUPPLEMENT_70`).
https://github.com/odoo/enterprise/blob/7892d035ddb968d67a3e8da0daf91292bd8fb499/l10n_lu_hr_payroll/data/salary_rules/hr_salary_rule_data.xml#L70-L74
Since the latest index is not the same as when those tests were written, the values are not correct anymore. To prevent this, time was frozen to 01/01/2024 to use the expected index (944.43, from September 2023).
opw-6330790
Forward-Port-Of: odoo/enterprise#122147This fixes incorrect invoice statuses for field service sales orders that include zero-price lines. Existing zero-price quotation lines can now remain invoiceable, while zero-price materials added during field service work are correctly treated as included, preventing orders from staying incorrectly marked as still to invoice.
Original PR description
## [FIX] industry_fsm_sale: fix invoice status for zero price lines ### Issue: Without Anglo-Saxon accounting, the system incorrectly sets the invoice status of all zero price sales order lines…
## [FIX] industry_fsm_sale: fix invoice status for zero price lines
### Issue:
Without Anglo-Saxon accounting, the system incorrectly sets the invoice status of all zero price sales order lines linked to an FSM task to 'no'
This includes pre-existing lines that were already present on the quotation before confirmation. If a zero price line is pre-existing, it should follow the standard flow and be marked as 'to invoice'
Only lines added as materials from the field service task at a zero price should be considered included in the price and marked as 'no'
### Cause:
In `SaleOrderLine._compute_invoice_status`, the system forced `invoice_status = 'no'` for all zero price lines when Anglo-Saxon accounting was disabled
It failed to check if the lines were actually materials added via the FSM task or original quotation lines
### Fix:
A new `material_sale_lines` compute field is added to `project.task` to distinctly isolate and track lines added specifically as materials during the task execution
In `SaleOrderLine._compute_invoice_status`, the logic is updated to ensure that only zero price lines identified as FSM materials are set to 'no' when Anglo-Saxon accounting is disabled
Other pre-existing zero price lines properly remain as 'to invoice'
### Steps to reproduce:
- Install `industry_fsm_sale`
- In Settings > Users & Companies > Companies > Any company, add the field Anglo-Saxon using Studio (In 19.0+)
- Disable Anglo-Saxon on the current company
- Create a product Service (Fixed Price, that create a task in a Field Service Project)
- Create and confirm a Sale Order with the Service and one product with unit price 0
Before the fix, the pre-existing SO line with price 0 is
incorrectly considered as not to invoice ('no')
opw-6169802
------------------------------
## [FIX] industry_fsm_sale: sync sale order invoice status
### Issue:
When a sale order contains FSM material lines with a price of zero and Anglo-Saxon accounting is disabled, the overall sale order invoice status remains stuck on 'to invoice' even after all other invoiceable lines are fully invoiced
### Cause:
The standard `SaleOrder._compute_invoice_status` does not handle FSM business rules regarding zero price material lines that are marked as `invoice_status = 'no'`
Because these lines are never technically invoiced, the global order status fails to transition to 'invoiced'
### Fix:
Override `SaleOrder._compute_invoice_status` to recompute the status of confirmed orders linked to FSM tasks
We use the task's `material_sale_lines` to filter out material components
If all lines on the order are either 'invoiced' or are zero price FSM material lines with Anglo-Saxon disabled, the global sale order status is forced to 'invoiced'
### Steps to reproduce:
- Install `industry_fsm_sale`
- In Settings > Users & Companies > Companies > Any company, add the field Anglo-Saxon using Studio (In 19.0+)
- Disable Anglo-Saxon on the current company
- Create a product Service (Fixed Price, that create a task in a Field Service Project)
- Create and confirm a Sale Order with the Service and one product with unit price 0
- Add a Product from the Task (Use a price 0 product, or set the unit price to 0 on the SO)
- Create the invoice for the Sale Order
Before the fix, the Service and Pre-existing product are invoiced, but in the Other Info Tab of the SO, the status stays on 'To Invoice' instead of 'Fully Invoiced'
opw-6169802
Forward-Port-Of: odoo/enterprise#124190
Forward-Port-Of: odoo/enterprise#119238The timesheet activity assistant no longer shows an empty Unmatched section when all items in that section are filtered out as away-from-keyboard events. This keeps the assistant cleaner and avoids confusing users with a header that has no visible content.
Original PR description
The Unmatched group's header renders even when its only entries are afk events, since those are filtered out at display time but still counted when checking if the group has content. With this PR, we first check if a group has visible content before displaying the header Task-6348666 Forward-Port-Of: odoo/enterprise#123286 Forward-Port-Of: odoo/enterprise#122858
Payslip creation now correctly includes employees assigned to branch companies when working from a parent company. This helps Belgian payroll teams select the right employees without manual workarounds or missing staff from payroll processing.
Original PR description
Bug: employees registered on branch companies don't appear in the
employee_id field when creating a payslip from the parent company.
Reason: the domain used ('company_id', '=', company_id) which only
matches the exact company, not its children.
Solution: replaced '=' with 'child_of' to include all descendant
companies in the hierarchy.
task - 6299634
Forward-Port-Of: odoo/enterprise#122381
Forward-Port-Of: odoo/enterprise#120974This fixes several Hong Kong IRD payroll report details found during certification testing. Reports now better match IRD requirements for amendment grouping, assessment year calculation, income totals, and required departure information.
Original PR description
As we now have complete support for IRD reports (in master), we started to try to get our system certified by the IRD. A first submission highlighted a few issues that we are now fixing. - There is only two types of XML. Original and 'adjustment'. The adjustment XML mix all three adjustment type in one file, which isn't what we did until now. - In 56E, RTN_ASS_YR must be based on the contract start date and the tax year - In 56F, RTN_ASS_YR must be based on the contract end date and the tax year. Also, the reason of departure MUST be set if departure type is 'other' - In 56G, RTN_ASS_YR must be based on the contract end date and the tax year. task-6309992
Payroll rule parameters are now safely copied before use, preventing one process from accidentally altering cached values used by another. This reduces the risk of hard-to-trace payroll calculation issues caused by shared internal data.
Original PR description
Cached functions with `@ormcache` should not return immutable values, yet `_get_parameter_from_code()` could return dicts/sets/lists/etc. It could lead to very obscure bugs such as: ```python def…
Cached functions with `@ormcache` should not return immutable values, yet `_get_parameter_from_code()` could return dicts/sets/lists/etc.
It could lead to very obscure bugs such as:
```python
def some_innocent_code():
category_dict = self.env["hr.rule.parameter"]._get_parameter_from_code('l10n_be_work_entry_categories')
incapacity_codes = category_dict['partial_incapacity']
incapacity_codes |= category_dict['total_incapacity']
# ... then use incapacity_codes
def print_rule_param():
print(self.env["hr.rule.parameter"]._get_parameter_from_code('l10n_be_work_entry_categories')['partial_incapacity'])
print_rule_param() # OrderedSet(['LEAVE281'])
some_innocent_code()
print_rule_param() # OrderedSet(['LEAVE281', 'LEAVE264', 'LEAVE266', 'LEAVE217', 'LEAVE218', 'LEAVE219', 'MEDIC01'])
```
The solution was to either deepcopy the returned value each time, or to change all the rule parameters to their frozen equivalent. Since we don't have access to frozen objects in rule parameters's xml definitions, we opted for the deepcopy approach.
task-6329380
Forward-Port-Of: odoo/enterprise#124134
Forward-Port-Of: odoo/enterprise#123058Invoice tax recalculations through Avatax now refresh the pre-tax base amount each time, instead of keeping an older cached value. This helps ensure invoice line totals stay consistent with the latest tax calculation returned by Avatax.
Original PR description
Previously, when recomputing taxes via Avatax, `manual_tax_amounts` was cleared and repopulated from the fresh API response, but `manual_total_excluded_currency` was only set if it was None. This meant that after the first Avatax call, the pre-tax base amount was never refreshed from subsequent API responses, even though the tax amounts were. This inconsistency could cause stale pre-tax base amounts to persist on invoice lines across recomputations, even when Avatax returned a different base amount. By resetting `manual_total_excluded_currency` to `None` at the start of each recomputation, mirroring what is already done for `manual_tax_amounts`, we ensure that the pre-tax base is always taken from the current Avatax response rather than a previously cached value. opw-6235597 Forward-Port-Of: odoo/enterprise#124152 Forward-Port-Of: odoo/enterprise#123802
Users with the Invoicing & Banks accounting role can now open the Accounting app without encountering an access error. The change restores a missing read permission needed to load accounting screens, bringing behavior back in line with previous versions.
Original PR description
**Description of the issue/feature this PR addresses:** Users in the 'Accounting / Invoicing & Banks' group were encountering an access error when attempting to open the Accounting app. This was…
**Description of the issue/feature this PR addresses:** Users in the 'Accounting / Invoicing & Banks' group were encountering an access error when attempting to open the Accounting app. This was caused by a missing read access right for the `account.return.type` model that is present in previous versions (e.g. saas-19.3). Without this permission, the basic accounting group (`account.group_account_basic`) is blocked from loading the necessary accounting views. This commit restores the `access_account_return_type_basic` rule, granting read access to the basic accounting group so they can access the app without errors. opw-6377912 **Steps to reproduce:** - Sign in as Mitchell Admin - Settings > Users & Companies > Users > Mitchell Admin > Access Rights > Accounting > Accounting > change to ‘Invoicing & Banks’ - Attempt to access the Accounting app > observe Access Error **Current behavior before PR:** - Users in the 'Accounting / Invoicing & Banks' group are unable to access the Accounting app due to Access Error **Desired behavior after PR is merged:** - Users in the 'Accounting / Invoicing & Banks' are able to access the Accounting app Forward-Port-Of: odoo/enterprise#124174
A payroll correction action now applies to all relevant payslips when used from the popup, regardless of which option is selected. This helps payroll teams avoid missed payslips and reduces the need for manual follow-up when processing corrections in batches.
Original PR description
**What:** - Corrected the method logic 'action_keep_wrong_version' to make sure that it works for both option in the view popup. task-6356957 Forward-Port-Of: odoo/enterprise#124089 Forward-Port-Of: odoo/enterprise#122625
This fix prevents an error when users remove the start or end date from a Field Service planning shift. The system now checks that required date values are present before recalculating break time, making shift editing more reliable.
Original PR description
before: when removing the start and end date of a shift, a trace back happens in the `_onchange_break_time` cause: it depends on the start and end date values, so it breaks when they are falsy after: apply a guard to the `_onchange_break_time` function to check on those fields to avoid breaking it --- task-6361418 Forward-Port-Of: odoo/enterprise#123926 Forward-Port-Of: odoo/enterprise#123273
This change adjusts internal automated tests for Field Service planning with sales and timesheets so they run at the correct stage and avoid false warnings. It helps keep quality checks stable without changing day-to-day product behavior for users.
Original PR description
Before this commit, the `TestFsmFlowSaleAtInstall.test_fsm_flow` test throws a warning because of chart template in accounting, the reason is because all tests using accounting test class have to be executed in post_install to avoid having unexpected issue. This commit moves the test in post_install and skip the test is `planning_field_service_sale_stock` module is installed because the behavior tested is altered when that module is installed. runbot-error-240998 Forward-Port-Of: odoo/enterprise#123357 Forward-Port-Of: odoo/enterprise#122306
The Documents search panel was updated to stay compatible with a recent platform change. This prevents crashes when users interact with sortable items in the Documents search panel, improving reliability without changing business workflows.
Original PR description
The web `SearchPanel` root is migrated from the OWL2 compat `useRef` to an OWL3 `signal.ref()` (a function). `DocumentsSearchPanel` passes the inherited `this.root` to `useNestedSortable`, whose `draggable_hook_builder` validates `ref` as an object and reads `ref.el` — so the raw signal function crashed with:
```
Error in hook useNestedSortable: invalid type for property "ref" in parameters: expected { object } and got function.
```
Fix: pass an object ref whose `el` getter reads the signal, keeping `.el` reactive.
Companion of odoo/odoo#272029.This update fixes an issue that could prevent users from confirming all signatures in the Sign app. It keeps the signature dialog aligned with a related platform change so the signing flow continues to work reliably.
Original PR description
Companion PR to odoo/odoo#274208. That PR converts `NameAndSignature.signatureRef` from an OWL2 compat `useRef` (`.el` accessor) to a `signal.ref` (callable). The `SignNameAndSignature` subclass here still read `this.signatureRef.el` inside `updateFrame`, which became `undefined` and threw "Cannot read properties of undefined (reading 'width')" on the "Sign all" confirm. Read the canvas element via `this.signatureRef()` instead. Companion PR: odoo/odoo#274208
Code cleanup and technical improvements
Several Point of Sale localization and payment screens were updated to stay compatible with Odoo's next web interface framework. This is an internal modernization that helps maintain stability during the Owl 3 migration without changing day-to-day business workflows.
Original PR description
`* = ["l10n_de_pos_cert", "l10n_mx_edi_pos", "l10n_pe_edi_pos", "pos_tyro"]` As part of the migration from `owl 2` to `owl 3`, this commit replaces uses of `t-custom-model` with `t-model` or `t-model.proxy`. Community: https://github.com/odoo/odoo/pull/271520
14 changes
Enhancements to existing features
The bank reconciliation screen now finds eligible accounting entries more efficiently on large databases. This reduces long wait times when loading reconciliation suggestions, helping accounting teams work faster without changing their workflow.
Original PR description
The bank reconciliation widget's `search_read` on `account.move.line` use `search_account_id.reconcile` to filter reconcilable accounts. This caused PostgreSQL to prefer an ordered index scan on `_date_name_id_idx`, walking ~3M rows for ~100 matches (40+ seconds on large db). Solution : match the approach already applied on the Python side in `_get_default_amls_matching_domain` This lets the planner use the existing _unreconciled_index partial index instead. opw-6328640
Resolved issues and error corrections
This fixes a compatibility issue that caused PDF flattening in the Sign app to fail in Python 3.10 environments. It helps keep automated builds stable and ensures signed PDF handling continues to work as expected on the supported version.
Original PR description
The flatten_pdf helper was written against the snake_case pypdf API (append_pages_from_reader, .pages, get_object), but that API does not exist on PyPDF2 1.26.0, the camelCase-only release still pinned on Python 3.10 in 19.0, so runbot builds on 3.10 failed with an AttributeError on 'BrandedFileWriter' object has no attribute 'append_pages_from_reader'. Switching to the camelCase spelling (appendPagesFromReader, getNumPages/getPage, getObject) fixes it. RunbotError: https://runbot.odoo.com/odoo/runbot.build.error/941405
Creating an Australian payroll super account could fail because the system used inconsistent internal references when checking employee super contribution proportions. This fix makes that check consistent, preventing the error and allowing payroll configuration to proceed normally.
Original PR description
Version - 19.0 Steps to reproduce(runbot): - Install `l10n_au_hr_payroll` - Go to Payroll -> Configuration -> Super Accounts - Create a Super Account by filling all the required fields - You will…
Version - 19.0
Steps to reproduce(runbot):
- Install `l10n_au_hr_payroll`
- Go to Payroll -> Configuration -> Super Accounts
- Create a Super Account by filling all the required fields
- You will encounter the issue
**TraceBack:**
```
('hr.menu_hr_employee_payroll', 485, 'Employees > Employees', 756):
Traceback (most recent call last):
File "/tmp/tmpao31_hxw/migrations/base/tests/test_mock_crawl.py", line 346, in crawl_menu
self.mock_action(action_vals)
File "/tmp/tmpao31_hxw/migrations/base/tests/test_mock_crawl.py", line 377, in mock_action
return self.mock_act_window(action)
File "/tmp/tmpao31_hxw/migrations/base/tests/test_mock_crawl.py", line 537, in mock_act_window
mock_method(model, view, fields_list, domain, group_by)
File "/tmp/tmpao31_hxw/migrations/base/tests/test_mock_crawl.py", line 570, in mock_view_form
[data] = record.read(fields_list)
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 3490, in read
return self._read_format(fnames=fields, load=load)
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 3747, in _read_format
vals[name] = convert(record[name], record, use_display_name)
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 6688, in __getitem__
return self._fields[key].__get__(self)
File "/home/odoo/src/odoo/19.0/odoo/orm/fields.py", line 1744, in __get__
self.compute_value(recs)
File "/home/odoo/src/odoo/19.0/odoo/orm/fields.py", line 1915, in compute_value
records._compute_field_value(self)
File "/home/odoo/src/odoo/19.0/addons/mail/models/mail_thread.py", line 484, in _compute_field_value
return super()._compute_field_value(field)
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 4954, in _compute_field_value
determine(field.compute, self)
File "/home/odoo/src/odoo/19.0/odoo/orm/fields.py", line 81, in determine
return needle(*args)
File "/home/odoo/src/enterprise/19.0/l10n_au_hr_payroll/models/hr_employee.py", line 118, in _compute_proportion_warnings
proportions[emp.id] * 100,
KeyError: 4
```
Issue:
During the refactoring from `read_group()` to `_read_group()`, the `proportions` dictionary became keyed by `hr.employee` records instead of employee IDs. While the condition was updated to use `proportions.get(emp)`, the warning message still accessed the dictionary using `proportions[emp.id]`, resulting in a `KeyError`.
Fix:
Use the employee record consistently when accessing the `proportions` dictionary after switching to `_read_group()`.
Task ID - 6390373
Refrence:
https://github.com/odoo/enterprise/pull/72217/changes#diff-8ae3564d54e47eec919ef273d44f6276d8c6df60e21c5c8c8f7292b1c38cf541R460This fixes an error that could occur when updating payments on Mexican CFDI invoices after navigating from a grouped accounting dashboard view. The change prevents unrelated screen filters from interfering with document attachment creation, making the payment update process more reliable.
Original PR description
Issue: The `default_type` context can leak into documents creation with invalid values (e.g., 'sale' for documents.document.type), causing a ValueError. Steps to reproduce: - Use a Mexican company with CFDI credentials configured. - Install the documents_account module and create a folder for journals where you will place customer payments. - Create an invoice with "payment policy = PPD", and send it to CFDI. - Create a bank transaction and reconcile it with the invoice. - Go to the Accounting Dashboard, remove current filters, and group by "Type" (this injects default_type into the context). - From there, enter the "Sales" journal and open the invoice. - Click on the "Update Payments" button. - Result: `ValueError: Wrong value for documents.document.type: 'sale'` Fix: Clean context from the `default_*` keys when creating the attachment of the document. opw-6141172 Forward-Port-Of: odoo/enterprise#124074
Belgian blackbox POS devices are now registered only after the POS configuration has a signed order, preventing devices from being locked before any sale occurs. The update also improves handling of cost center names and makes blackbox error messages clearer for users.
Original PR description
- `log_device` registered the device as soon as a blackbox POS was opened, so a device was locked before making any sale. Register it only once the config has at least one signed order, and add unit tests covering both branches. - correctly `trim()` cost center - display details inside syntax error & invalid input blackbox error popups
Studio approval checks now handle rules that reference restricted fields without causing an access error for users who lack those permissions. This prevents valid actions, such as confirming a sales order, from being blocked unexpectedly while keeping approval logic intact.
Original PR description
Issue: A studio.approval.rule.domain includes a related field that calls an access rights group that the user who used the action isn't apart of, Is blocked by the filtered_domain. To Replicate: 1) Install studio, sale, Accounting and make sure "account_followup" is installed 2) create a related field on the sales.order form related to "customer -> follow up status" 3) Save 4) Create a "Studio Approval Rule" (studio.approval.rule) with a domain using the new related studio field -> method : "action_confirm" -> approver:admin 5) create a test user with no accounting access rights 6) in an incognito browser try and create a sales order, and then confirm it. it will throw the access rights error Fix: add a sudo to the filtered_domain opw-6316069
Subscription product tiles in the online shop now show discounts based on the correct recurring price rather than the one-time sale price. This prevents customers from seeing misleading monthly prices before opening the product page.
Original PR description
Steps to reproduce: =================== 1. Create a subscription product, allow one-time sale, sale price 5 2. Add a recurring price 10/month 3. On the pricelist, add an advanced rule: -10% for the…
Steps to reproduce: =================== 1. Create a subscription product, allow one-time sale, sale price 5 2. Add a recurring price 10/month 3. On the pricelist, add an advanced rule: -10% for the monthly plan 4. Open the shop page and look at the product tile Cause: ======= On the /shop page, the subscription price displayed on a product tile is computed by `_get_sales_prices`. The cart has no plan selected yet at that point, so `request.cart.plan_id.id` is empty and was passed as `plan_id` to `_compute_price`. In `product.pricelist.item._compute_base_price`, the recurring base price is only looked up when a `plan_id` is given: if rule_base == 'list_price' and product.recurring_invoice and plan_id: ... # find the recurring rule -> base = recurring price With `plan_id` empty, that branch is skipped and the percentage rule falls back on the product's one-time `list_price` instead of the recurring price. Example: one-time price 5, recurring price 10/month, pricelist rule -10% on the monthly plan. => Tile showed 4.5/month (5 * 0.9) instead of 9/month (10 * 0.9). Solution: ========= The chosen pricing already targets a plan, so pass `pricing.plan_id.id` to `_compute_price`, matching what the product page does in `_get_additionnal_combination_info`. opw-6307398
The test setup for push notifications now only registers devices for internal users, matching how Odoo Cloud Notifications work in practice. This prevents misleading test coverage for users who should not receive these notifications and improves reliability of the mail enterprise test suite.
Original PR description
Only devices of internal users are registered in order to send them Odoo Cloud Notifications (OCN). However, the test setup registers devices for non-internal users as well. This commit ensures devices are only registered for internal users. Forward-Port-Of: odoo/enterprise#119956
A typo in the payroll accounting code was fixed so the intended customization now matches the standard payroll method name. This helps ensure payroll-related accounting behavior runs as expected and avoids issues caused by the method not being properly recognized.
Original PR description
An incorrect underscore "_" was added when overriding the method, so we're removing it Correct name as it appears here: https://github.com/odoo/odoo/blob/1b8f6802832cfa4d146193a912af1f4445d09f0a/addons/hr/models/hr_version.py#L443
French VAT reports now only include a direct debit payment order when VAT is actually owed. This prevents refund requests from being rejected by the French tax authority because an invalid payment block was attached.
Original PR description
`_prepare_edi_vals` always called `_get_formatted_payment_values()`, adding an EDI-Paiement (telereglement) block to the T-IDENTIF of the 3310CA3, regardless of whether the company owes VAT or is in…
`_prepare_edi_vals` always called `_get_formatted_payment_values()`, adding an EDI-Paiement (telereglement) block to the T-IDENTIF of the 3310CA3, regardless of whether the company owes VAT or is in a credit position. Steps to reproduce: - French company in a VAT credit position, requesting a refund. - Fill a bank account line, the account to receive the refund and send the VAT report to the DGFiP. Current behaviour: The DGFiP returns a negative acknowledgement on the CA3 interchange: "Telereglement 1 rejete: Montant telereglement absent ou invalide. Code erreur : 018", even though the declaration itself is accepted. The wizard's bank account lines are reused for two opposite purposes: the account to debit when VAT is due, and the account to credit when a refund is asked. `_get_formatted_payment_values()` builds a payment order from them unconditionally, so a telereglement for the credit amount is emitted in the refund case. A telereglement is invalid when no VAT is due, hence error 018. A return nets to either a payment or a credit, never both, so the two cases are mutually exclusive. This commit guards the call with `self.is_vat_due`, so the telereglement is only generated when the company actually owes VAT. The VAT-due flow is unchanged. opw-6275695
Uruguayan electronic invoices now avoid extra blank lines when combining addenda text with terms and conditions. This helps keep addenda content on the expected page when it fits, preventing unnecessary page breaks in generated CFE PDFs.
Original PR description
## Context When generating a CFE (Comprobante Fiscal Electrónico) that contains both a configured addenda (e.g. bank account details stored in `l10n_uy_edi_addenda_ids`) and terms & conditions from…
## Context
When generating a CFE (Comprobante Fiscal Electrónico) that contains both a configured addenda (e.g. bank account details stored in `l10n_uy_edi_addenda_ids`) and terms & conditions from the invoice's `narration` field, the resulting addenda string could end up with unnecessary blank lines between the two sections, causing the addenda to be rendered on a separate page even when the logical content fits within the 6-line threshold.
## Root Cause
`_l10n_uy_edi_get_addenda` joins both parts without stripping whitespace from either of them first, and adds two lines between addendas and terms and conditions:
addenda = addenda + "\n\n" + term_and_conditions if addenda else term_and_conditions
Two sources independently introduce extra newlines around the separator:
1. **Addenda content** — `_get_legends` returns the raw `content` field value of each addenda record. These fields commonly end with a trailing `\n`, so the addenda string already ends with a newline before the `"\n"` separator is concatenated.
2. **`html2plaintext`** — the `narration` field is stored as HTML. When converted to plain text, `html2plaintext` typically wraps paragraph content in leading/trailing newlines.
The combination of the trailing `\n` from the addenda, the explicit `"\n\n"` separator, and the leading/trailing `\n` from `html2plaintext` produces 2–3 consecutive newlines, which `splitlines()` counts as blank lines.
A realistic 4-line addenda + 1-line narration thus produces **7 lines** instead of the expected 5, crossing the 6-line threshold in `_get_report_params` and triggering `adenda=true` — which forces the addenda onto a separate page unnecessarily.
## Steps to Reproduce
1. Configure a `l10n_uy_edi.addenda` record of type `addenda` with multi-line content (4 lines)
2. Create and confirm an invoice with `narration` set to a short single-line term
3. Generate the CFE PDF via Uruware.
4. Observe that the addenda is rendered on a separate page despite the logical content being only 5 lines.
<img width="1042" height="448" alt="image" src="https://github.com/user-attachments/assets/b538211c-5f37-4648-979d-99cd75cf31c2" />
## Fix
Strip leading and trailing whitespace (including newlines) from both parts before joining them. The ternary is also replaced with an explicit `if/else` for clarity:
def _l10n_uy_edi_get_addenda(self):
addenda = self.l10n_uy_edi_document_id._get_legends("addenda", self)
if self.narration:
term_and_conditions = html2plaintext(self.narration).strip()
if addenda:
addenda = addenda.strip() + "\n" + term_and_conditions
else:
addenda = term_and_conditions
return self._l10n_uy_edi_clean_non_ascii_chars(addenda)
This guarantees exactly one `\n` separator between sections regardless of how the content fields were stored or how `html2plaintext` formatted the narration.
The threshold logic in `_get_report_params` is unchanged: addendas that genuinely exceed 6 lines (after wrapping at 140 chars) continue to be printed on a dedicated page.
Result
<img width="1117" height="456" alt="image" src="https://github.com/user-attachments/assets/3a2d942c-8c37-40f6-bc25-470c0bd25b08" />
Forward-Port-Of: odoo/enterprise#119283Email buttons for public appointment and event interactions now use the website linked to the appointment or event instead of an unrelated site visited during login. This prevents customers on multi-website setups from being sent to the wrong website when managing appointments or registrations.
Original PR description
In Multiwebsite settings, when the public user interactions needs email generation (appointment or event flow), the email links are generated with a base url that does not corresponds to the one from…
In Multiwebsite settings, when the public user interactions needs email generation (appointment or event flow), the email links are generated with a base url that does not corresponds to the one from which the request started. Case 1: - Have website A and website B - Create an appointment page website A - Log in via website B - As public user, make an appointment in Website A - Check the generated email Issue: button links in the email will redirect to the wrong website, so users will encounter an issue when managing the appointment. This occurs because when an user log in, the system parameter 'web.base_url' is updated with the current url. This parameter is then used as fallback when we need to retrieve the base url without an active record Case 2: - Have website A and website B - Create an event and assign it to website B - As public user, access the event and register to it - Check the generated email Issue: button links in the email will redirect to the wrong website, so users will encounter an issue when managing the event. This occurs because the record `event.registration` has no website_id field and the base url is taken from the company default website (website A) Backport with improvements of 15bae202d8f1b5bf70bbc63b2d89025e9237e6cf opw-4146760 opw-4336369 Forward-Port-Of: odoo/enterprise#124173 Forward-Port-Of: odoo/enterprise#122669
Colombian electronic invoice imports now keep the DIAN price amount as the actual unit price instead of dividing it by the base quantity. This prevents incorrect negative discounts on vendor bills when imported XML lines use quantities greater than one.
Original PR description
Problem: When importing XML files to generate vendor bills, the system uses UBL parser and assumes that the PriceAmount node needs to be divided by the BaseQuantity node to obtain the exact price…
Problem: When importing XML files to generate vendor bills, the system uses UBL parser and assumes that the PriceAmount node needs to be divided by the BaseQuantity node to obtain the exact price unit. However, in Colombia, the DIAN treats the PriceAmount node as the exact price unit. This was not flagged in the system so the parser incorrectly divides the PriceAmount by BaseQuantity, resulting in negative discounts to be added to match the subtotal. Solution: Extract the basis_qty logic into a helper method so other localizations can override when needed. Current behavior: When importing a Colombian XML with a product that has a BaseQuantity greater than 1, the PriceAmount gets incorrectly divided, resulting in negative discounts on the vendor bill. Expected Behavior: When importing a Colombian XML with a product that has a BaseQuantity greater than 1, the PriceAmount gets parses as the exact unit price with no negative discounts applied. task-6215466 Forward-Port-Of: odoo/enterprise#124400 Forward-Port-Of: odoo/enterprise#122313
Fixed an issue where Uruguayan e-Ticket credit notes for original tickets totaling 0.00 could be rejected by the tax authority because a required zero-value reference was omitted. The system now keeps that mandatory value in the electronic document, improving compliance and avoiding failed submissions for these edge cases.
Original PR description
Problem: When generating an e-Ticket Credit Note for an original e-Ticket with a total amount of 0.00, the XML cleanup mechanism removes reference fields whose value is 0.00. As a result, the credit note is rejected by DGI with: "CODE 31: En línea de Referencia 1 si NO IndGlobal = 1 deben existir TpoDocRef, Serie, NroCFERef, MntCFERef, TpoMonedaRef." Solution: Ensure that MntCFERef is sent even if the value is 0.00. opw-6378783 Forward-Port-Of: odoo/enterprise#124377 Forward-Port-Of: odoo/enterprise#124354
2 changes
Enhancements to existing features
This commit implements a way for user to manually set a default value for the `invoice_cash_rounding_id` field in moves, and apply by default a standard `account.cash.rounding` record based on the found `ir.default` record or the ISO-4217 standard for every available currency. It adds 5 new `account.cash.rounding` record included on the data of the `account` module. They're set with the 'biggest_tax' strategy so that the user doesn't need to add required account data on the fields. We
Original PR description
This commit implements a way for user to manually set a default value for the `invoice_cash_rounding_id` field in moves, and apply by default a standard `account.cash.rounding` record based on the…
This commit implements a way for user to manually set a default value for the `invoice_cash_rounding_id` field in moves, and apply by default a standard `account.cash.rounding` record based on the found `ir.default` record or the ISO-4217 standard for every available currency. It adds 5 new `account.cash.rounding` record included on the data of the `account` module. They're set with the 'biggest_tax' strategy so that the user doesn't need to add required account data on the fields. We use the `ir.default` object to store the "preferred" default value of the move rounding field. The rounding field will also be computed to make sure it gets filled with the default value. An override on `default_get` is needed so that the default `ir.default` behavior does not interferre with the compute and making sure the rounding field gets computed correctly. To set a preferred `ir.default` on the cash rounding field, the user must create a new record on "User-Defined Defaults" menu, select the cash rounding, fill the JSON value with the rounding record ID, and fill either the company field and/or the condition string with "currency=<currency_name>" (e.g. "currency=USD"). When searching through the `ir.default` records to apply, the code will by priority search through the record sorted by: - company and currency condition filled - currency condition filled - company filled By default, 3 `ir.default` record are created when installing `account` module as an exception to the ISO-4217 standard in Odoo for the currency of IDR, INR, and CHF. An options for digits is added on the rounding field in the `account.cash.rounding` list and form view, to make sure the additional decimal points are shown (it's set to hide anything smaller than 2 digits behind the decimal separator) In master (18.1), an additional preferred default value for the rounding field will be saved on the partner of the move, and will be made a priority over all current `ir.default` for this rounding field. task-id: 4338116
Resolved issues and error corrections
When sending an email to an invoice mail alias without valid attachment, a custom bounce mail is sent to inform the customer that it was bounced because of a missing attachment. Currently, the bounce email from will default to the bounce alias of the main company of the DB. In a multi company setup, where each company might have their own mail alias domain, this can be confusing for the receiving party that didn't send the original email to the main company's alias domain. ## P
Original PR description
When sending an email to an invoice mail alias without valid attachment, a custom bounce mail is sent to inform the customer that it was bounced because of a missing attachment. Currently, the bounce…
When sending an email to an invoice mail alias without valid attachment, a custom bounce mail is sent to inform the customer that it was bounced because of a missing attachment. Currently, the bounce email from will default to the bounce alias of the main company of the DB. In a multi company setup, where each company might have their own mail alias domain, this can be confusing for the receiving party that didn't send the original email to the main company's alias domain. ## Proposed solution: We call `_routing_create_bounce_email` with the right company in the context, since the journal company is already detected. Additionally, in the context of the accounting code calling the bounce private method, we pass an explicit value for `reply_to` ## How to reproduce it: 1) Set up DB with two companies (companyA and companyB). Install account. 2) Set up two mail alias domains (companya.com and companyb.com) 3) Send an email to the invoice alias of companyB (e.g. invoices@companyb.com). -> bounce email will have FROM as "notifications@companya.com" (the notifications alias of companyA) Example headers before: ``` Reply-To: "YourCompany" <catchall@companya.com> To: customer@example.com Date: Thu, 02 Jul 2026 11:18:49 -0000 Return-Path: bounce@companya.com From: "MAILER-DAEMON" <bounce@companya.com> ``` After fix: ``` Reply-To: info@companyb.com To: customer@example.com Date: Thu, 02 Jul 2026 11:16:19 -0000 Return-Path: bounce@companyb.com From: "MAILER-DAEMON" <notifications@companyb.com> ``` opw-6342778
1 change
Resolved issues and error corrections
## Short fix summary: Both Nilvera sync crons (`_l10n_tr_nilvera_get_submitted_document_status` and `_cron_nilvera_get_new_documents`) built their API client from the ambient `self.env.company` instead of each invoice's own `company_id`. In a multi-company setup, or whenever the cron's runtime user's default company differs from the invoice's, this silently used the wrong (or no) API key and the sync failed for those invoices. `_l10n_tr_nilvera_get_submitted_document_status` now groups invoice
Original PR description
## Short fix summary: Both Nilvera sync crons (`_l10n_tr_nilvera_get_submitted_document_status` and `_cron_nilvera_get_new_documents`) built their API client from the ambient `self.env.company` instead of each invoice's own `company_id`. In a multi-company setup, or whenever the cron's runtime user's default company differs from the invoice's, this silently used the wrong (or no) API key and the sync failed for those invoices. `_l10n_tr_nilvera_get_submitted_document_status` now groups invoices by `company_id` and opens one Nilvera client per company. `_cron_nilvera_get_new_documents` now goes through a new `_l10n_tr_nilvera_company_get_documents` helper that loops over the Turkish companies with an API key configured and switches into each one's context via `with_company()` before fetching. task-6328589 I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr