Monday, September 28, 2026
22 changes · saas-19.3
Enhancements to existing features
Adds test coverage to ensure appraisal campaigns created by managers without Appraisals access only include the employees they selected. This helps prevent accidentally scheduling appraisals for all reporting employees when only a subset was intended.
Original PR description
The campaign wizard created appraisals for every employee reporting to the user instead of the ones actually selected, whenever the user had no Appraisals access rights. Steps to reproduce: 1. Log in…
The campaign wizard created appraisals for every employee reporting to the user instead of the ones actually selected, whenever the user had no Appraisals access rights. Steps to reproduce: 1. Log in as a user with no Appraisals access rights (e.g. Marc Demo) who has several employees reporting to them 2. Go to Appraisals and click "Launch Campaign" 3. Select one employee in the Employees field 4. Pick an Appraisal Template and a date, then click "Schedule" `employee_ids` is a many2many to `hr.employee`, and `convert_to_record` drops the records a user cannot access, so for a non-HR officer the field read back empty. Both `_compute_warning` and `action_generate_appraisals` then fell through to their `self.employee_ids or ...search(...)` branch, whose domain is every employee below the user in the hierarchy. This was fixed in PR #127391 (task 6452765) by setting `bypass_search_access=True` on the field, but that shipped without a test. This one passes with the fix and fails without it. opw-6448677
This update adds the missing IGIC receivable and payable accounts to the Canary Islands chart of accounts. It helps Odoo post tax adjustments to the right accounts when closing tax periods or preparing tax reports, reducing manual accounting corrections.
Original PR description
… CoA The Canary Islands chart of accounts was missing dedicated receivable/payable accounts for IGIC. Without these, Odoo cannot automatically post tax adjustments to the correct accounts when closing tax periods or generating the tax report. Add accounts 470700 (Hacienda Pública, deudor por IGIC, asset_receivable) and 475700 (Hacienda Pública, acreedora por IGIC, liability_payable) to the common Canary Islands account template, and set them as tax_receivable_account_id and tax_payable_account_id on all IGIC tax groups. Change to the canaries association their correspondent account codes overwriting the names. task-6225928 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#270139
Spanish localization reports now include return type support for the Mod 420 tax return. This helps ensure the report can be categorized and processed consistently with other tax returns.
Original PR description
Add the `es_mod420_tax_return_type` record to `account_return_data.xml` to support the Mod 420 tax return, including its association with the `l10n_es.mod_420` report. This change is done so that 420 can have return types, which bedore it didn't. task-6225928 Forward-Port-Of: odoo/enterprise#120613
Resolved issues and error corrections
Unreconciling one partial match on an invoice now removes only the selected match instead of clearing other related payments or credit notes. This prevents accidental changes to invoice payment status and keeps accounting records consistent.
Original PR description
Repro steps: 1) Create an invoice I 2) Partially reconcile I with a credit note CN 3) Partially reconcile I with a payment P 4) Unreconcile one of the 2 partials Problem: If you unreconcile one partial, both partials are removed (an exception is when account_accountant is installed, not just account, in that case, unreconciling P works fine, but unreconciling CN, also unreconcilies P still) Root cause: account.move.line.remove_move_reconcile used to unreconcile on move level instead of line level because of this PR https://github.com/odoo/odoo/pull/249536 Solution: The logic in remove_move_reconcile is kept simple, and it only unreconciles on account.move.line level. A new method is also introduced account.move._remove_reconciliation_between_moves to unreconcile on the move level, and this one is used with the account payment field to solve the aforementioned issue. task-6574942
Payments in Mexican electronic invoices now keep the same official currency exchange rate as the related invoice when the recalculated rate is within the accepted rounding range. This prevents mismatched invoice and payment XML rates, reducing rejected CFDI documents and reconciliation confusion for USD transactions.
Original PR description
Issue: Currency rate for payment is recomputed up to 6 digit. However, it may differ from the official one stored in db up to 4 digits. Which creates differencies between invoice and payment rate…
Issue: Currency rate for payment is recomputed up to 6 digit. However, it may differ from the official one stored in db up to 4 digits. Which creates differencies between invoice and payment rate made at the same date. Steps to reproduce: - In MX company, - Enable USD, - Set currency rate for today to 1 USD = 17.4455 MXN - Create a PPD invoice (due date > 40 days) in USD - Add a line with - qty: 1, - unit_price: 3.488 - tax: 16% (default tax) - Send it to CFDI - Create payment - On the invoice Form click on "Update Payment" Current behavior: - In the CFDI sheet, Payment and Invoice XML files will have different currency rates Expected behavior: - In the CFDI sheet, Payment and Invoice XML files will have the same currency rate Cause: PACs require having the payment `amount` to be equal to `currency_amount * currency_rate`. For huge amout it may happen that using the 6 digits rounding of currency rate to compute the amount won't fall exactly on the two digit precision for the amount and payment would be refused. Therefore, for all payment, we recompute a 6 digits precision `currency_rate` from `amount` and `currency_amount` then using it to compute the final amount. However, Banxico (Mexican central Bank) publish rates with a 4 digit precision. Recomputing the currency rate up to 6 digits may slightly change it from the 4 digit precision official currency rate. opw-6411530 Forward-Port-Of: odoo/enterprise#132659 Forward-Port-Of: odoo/enterprise#129700
This fix prevents self-ordering kiosks from printing duplicate preparation tickets when customers use a different language from the kiosk default. Orders now produce a single preparation ticket in the kiosk language, reducing staff confusion and avoiding extra confirmation steps for customers.
Original PR description
When having a Kiosk config with a preparation printer and kiosk have multiple language we have the following issue: - Change language (use a different than default Kiosk lang) - Create order, add…
When having a Kiosk config with a preparation printer and kiosk have multiple language we have the following issue: - Change language (use a different than default Kiosk lang) - Create order, add lines - Pay the order - Confirmation page → Print preparation ticket in customer language - Click close → Trigger a page reload (reset to Kiosk default lang) and print a second preparation ticket in default Kiosk lang - Then we have to click again "Close" on the confirmation page a second time → Two preparation tickets printed & we have to close the confirmation page 2 times After the fix: A ticket is rendered in the language loaded with the page, so the confirmation page only prints it when the kiosk is already in its own language Otherwise it stores the order access token, restores the language and loads the default root: the next page prints the ticket, once, in the kiosk language task-id: https://www.odoo.com/odoo/project/1737/tasks/6559206 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This change removes an unnecessary default value from an internal test method parameter. It follows coding best practices and helps avoid confusing behavior or future runtime errors in Odoo's core test infrastructure.
Original PR description
[RUF077](https://docs.astral.sh/ruff/rules/method-receiver-default/#method-receiver-default-ruf077) specifies that method receiver parameters, such as `self` and `cls`, should not have default values. The reasoning is because these parameters are usually bound by the method binding protocol, so a default value on a receiver parameter is almost certainly a mistake and can lead to confusing behavior or runtime errors. This method seems to not reference self, and this should not cause errors, but it is best practice to not do this. runbot-[947144](https://runbot.odoo.com/odoo/error/947144) Forward-Port-Of: odoo/odoo#290616
This fix ensures demo data for Belgian payroll-related modules is created using the correct Belgian company context. It prevents an error when users load the demo data through the interface, making setup and testing smoother.
Original PR description
Before the fix, loading the module's demo data through the UI triggered an error. task-6581013 Forward-Port-Of: odoo/enterprise#132843 Forward-Port-Of: odoo/enterprise#132358
Updates the U.S. accounting template so physical asset accounts use “Depreciation” instead of “Amortization,” aligning terminology with standard accounting practice. It also adds a dedicated amortization account for intangible assets, improving clarity in financial reporting.
Original PR description
Amortization is used when it is an intangible asset (loan/ goodwill/ IP). Depreciation would be used for a physical tangible asset (vehicles/ PPE). This commit renames some accounts wrongly using the "Amortization" wording when "Depreciation" should've been used. We also add a new account that can be used for Amortization. task-6578876
The expense app now correctly warns users when they submit an expense that appears to duplicate an existing one. This helps prevent accidental duplicate reimbursements and improves expense review accuracy.
Original PR description
Steps to reproduce: - install hr_expense_extract - create an expense and submit it - duplicate that expense, and submit it - no warning dialog showed up when one should.
Fixes an issue where adding users to payroll groups from the Groups screen could fail and prevent the change from being saved. Group membership additions and removals are now consistently audit-logged, improving reliability for Australian payroll administration.
Original PR description
#### Description of the issue/feature this PR addresses:
Adding a user to a group from the Groups form crashes on an Australian Payroll-API database, and removals from that form are never audit-logged.
#### Current behavior before PR:
The audit-logging mixin reads the changed users out of the raw write command with vals.get("user_ids")[0][2], which assumes a 3-element command tuple. The Groups form sends the 2-element (4, id) LINK command, so the write raises an IndexError; UNLINK commands are silently not logged for the same reason.
#### Desired behavior after PR is merged:
Group membership changes made from the Groups form are audit-logged for both additions and removals, regardless of the command used to write user_ids. The mixin reads the members before and after the write instead of interpreting the command. No field, model or method-signature change, so it is safe in stable.
opw-6397011
Forward-Port-Of: odoo/enterprise#132821
Forward-Port-Of: odoo/enterprise#125124This fixes the employee HR responsible selector so it only shows users with the appropriate HR or Time Off Officer permissions. It prevents incorrect or overly broad choices, helping businesses assign time off responsibilities to the right authorized people.
Original PR description
Issue: ---------------------------------------- The domain of the field `hr_responsible_id` isn't computed. Also it should contain `hr_holidays.group_hr_holidays_user`. Cause:…
Issue: ---------------------------------------- The domain of the field `hr_responsible_id` isn't computed. Also it should contain `hr_holidays.group_hr_holidays_user`. Cause: ---------------------------------------- The domain of the field is returned by `_get_hr_responsible_domain()` as a string which is not supported. https://github.com/odoo/odoo/blob/2ea452d03aa0cbfe360c539fb3b29a7b89aa7297/odoo/orm/fields_relational.py#L112-L123 `validated()` returns `None` so the domain is left empty. Solution: ---------------------------------------- Return a list instead of a string. Also, override the field in `hr_holidays` and and the condition on `hr_holidays.group_hr_holidays_user`. Note: ---------------------------------------- This [commit](https://github.com/odoo/odoo/commit/ff50687bad2939db882e75ae20f28e6155fa2191) did the same thing in saas-19.4 but added a useless field in `hr.employee` because it did not catch the error of the string being wrong. opw-6545508 Forward-Port-Of: odoo/odoo#289243
Buy X Get Y loyalty programs no longer show or allow editing of the Minimum Purchase section. This prevents users from setting purchase thresholds that should not apply to this promotion type, reducing confusion and configuration mistakes.
Original PR description
I think the point was to hide the entire `Minimum Purchase` section when the program type is `buy_x_get_y`, but only the label is hidden. This commit will also hide the div under the label, so no minimum purchase can be set on `buy_x_get_y` programs --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#288281
The Swiss balance sheet report formulas were adjusted to produce more accurate financial results. This helps Swiss companies rely on the report for clearer and more compliant financial reporting.
Original PR description
Change some formulas in the Swiss balance sheet task-6379692 Forward-Port-Of: odoo/enterprise#123895
Spreadsheet list formulas now keep single linked numeric values as real numbers instead of converting them to text. This prevents calculation errors in locales such as French, where decimal separators differ, while leaving multi-value behavior unchanged.
Original PR description
Steps to reproduce:
- insert a sale.order list into a spreadsheet
- change the locale to FR_fr
- add the formula in A1: =ODOO.LIST(1, 1, "invoice_ids.amount_total"
- in A2: =A1+1 => error
The value for the "amount_total" is stringified
to something like "4.5" because of the `[].join(",")` but "4.5" is not interpreted as a number in the FR locale where the decimal separator is "," and it should be "4,5"
With this commit, when there's a single record in the x2many, we fallback on the "normal" case and return the real value.
When there are multiple values, we volontarily keep the current behavior. We can't "just format it" because we join with "," and it wouldn't work if "," is also the decimal separator. This is a known and accepted limitation, especially in stable
Task: 6307092
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#283777
Forward-Port-Of: odoo/odoo#270279Fixed a visual issue in Sales quotations where the optional products table appeared lighter than its surrounding section when using dark mode. This improves the consistency and readability of the quotation product selection experience.
Original PR description
**Steps to reproduce:** - Set preferences to dark mode - Create a quotation and add a product with optional products (e.g customizable desk) -> Background of optional products is lighter than the…
**Steps to reproduce:** - Set preferences to dark mode - Create a quotation and add a product with optional products (e.g customizable desk) -> Background of optional products is lighter than the surrounding div **Behavior:** The `div` showcasing optional products is set to be a bit darker to add contrast in the modal. In light mode, table's background is transparent, which allows it to adapt to a darker container. However, this is not the case in dark mode, which causes a mismatch between table and div backgrounds. By default `$table-bg` follows `$body-bg`: https://github.com/odoo/odoo/blob/3442814d57cd9420d7dc6d022d15cd5d1b3def74/addons/web/static/lib/bootstrap/scss/_variables.scss#L738-L740 And is later set to transparent here: https://github.com/odoo/odoo/blob/3442814d57cd9420d7dc6d022d15cd5d1b3def74/addons/web/static/src/scss/bootstrap_overridden_frontend.scss#L74-L75 However when darkmode is enabled, `$body-bg` is re-assigned, which forces `$table-bg` back to the default dark color, bypassing the transparent override. https://github.com/odoo/odoo/blob/3442814d57cd9420d7dc6d022d15cd5d1b3def74/addons/web/static/lib/bootstrap/scss/_root.scss#L132-L139 --- This commit ensures that `table-bg` is explicitly set to transparent for tables displaying optional products. opw-6569713 Forward-Port-Of: odoo/odoo#288127
This update removes unnecessary logic from the accounting reports download flow. It helps keep the report download code clearer and reduces the chance of confusion in future maintenance, with no expected change for users.
Original PR description
`!something` is never nullish, so `?? true` is dead code. Probably the intention was `if (!(data.no_closing_after_download ?? true))`, but reviewer has the final say and I can change it. Forward-Port-Of: odoo/enterprise#131774
Kenyan eTIMS reporting now excludes taxes that do not have a KRA tax code from the reported tax-inclusive total. This prevents levies paid to other authorities, such as tourism levies, from being incorrectly included in tax authority submissions.
Original PR description
Issue: Non KRA taxes are sent in eTIMS tax total Steps to reproduce: - With l10n_ke_edi_oscu - Create a 2% Tax named CTL with no KRA Tax Code - Create an invoice - Add a line with 16% and CTL taxes - Confirm - Send to eTIMS Current behavior: - eTIMS JSON (unaccessible) is created with 'totAmt' (total_tax_included) including non-KRA Taxes Expected Behavior! - eTIMS JSON 'totAmt' doesn't incldue non-KRA Taxes Note: eTIMS is used to report to Tax Autority, however some "taxes" (e.g. Tourism levy) are paid to other authorities (e.g. Tourism Fund) and shouldn't be included in eTIMS. opw-6456478 Forward-Port-Of: odoo/enterprise#131577
This fix prevents spreadsheet actions from converting multi-record relationship fields into plain text. It helps preserve accurate data handling in document spreadsheets and reduces the risk of incorrect filtering or display behavior.
Original PR description
See community PR task-6307092 Forward-Port-Of: odoo/enterprise#128699 Forward-Port-Of: odoo/enterprise#120701
KPI report summaries now ignore archived returns, so hidden or inactive returns no longer appear in business indicators. This keeps reporting clearer and avoids misleading history without forcing users to mark returns as completed.
Original PR description
[FIX] account_reports: KPI provider: properly ignore inactive returns Returns can be archived (setting the 'active' field to False) from the UI, so that the user can hide them when not needed,…
[FIX] account_reports: KPI provider: properly ignore inactive returns Returns can be archived (setting the 'active' field to False) from the UI, so that the user can hide them when not needed, without risking them being regenerated by the cron, and without havin to mark them as completed, which could give misleading information when browsing the history. The problem was here the KPI provider did not properly ignore such returns. ======================================== [FIX] account_reports: make KPI provider test with existing data in db The KPI provider query completely ignores multi-company. Because of that, the test wasn't properly sandboxed, and could fail if any non-test company of the database contained a return made from any untested return type, as it would add one element to the result of get_account_reports_kpi_summary. We now make sure to archive all existing returns, whatever their company, before running the test. Forward-Port-Of: odoo/enterprise#131438
The Bulgarian SAF-T reporting feature will no longer be installed automatically unless the advanced accounting setup is explicitly present. This avoids making a specialized accounting report available in environments that may not have the required accounting capabilities.
Original PR description
As the Bulgarian SAF-T Report is destined for advanced accounting, it should only be available and auto-installed when the 'accountant' module is as well. As the 'accountant' module is not part of the dependencies yet (it is from 20.0 on), we cannot be sure that it is already installed. It is then better to stop the auto installation altogether. Forward-Port-Of: odoo/enterprise#132761
Italian invoices using the Import/Export fiscal position will now apply only the appropriate 0% tax instead of adding an extra N7 tax by default. This prevents incorrect tax combinations on invoice lines and helps businesses produce cleaner, compliant invoices.
Original PR description
Upon Import/Export, the 0% EX N7 tax is added by default on each tax excluded line, as it incorrectly shares the same default Import/Export fiscal position with the standard 0% EX tax. This causes both to be applied simultaneously to a single invoice line. 1. Install Accounting and `l10n_it` 2. Switch to IT company 3. Go to Invoices and create a new one 4. Select the Import/Export fiscal position 5. Add a line with a new product (so it's clean of custom product taxes) 6. Both `0% EX` and `0% EX N7` taxes are applied, instead of just `0% EX` Ticket [link](https://www.odoo.com/odoo/project.task/6518926) opw-6518926 Forward-Port-Of: odoo/odoo#285586