Monday, September 28, 2026
14 changes · saas-19.3
Resolved issues and error corrections
The website editor now handles product record identifiers correctly when opening AI-related actions. This prevents users from seeing a false “record deleted” error while editing product pages, improving reliability for website content updates.
Original PR description
resId was read as a string from editableEl.dataset["oeId"] (DOM dataset attributes are always strings) and passed downstream as-is, eventually breaking browse() calls that expect an int. To solve it, we'll cast resId to Number in getRecordInfo() so it's a proper id wherever it's consumed. Updated two tests that asserted resId/res_id as a string to expect a Number instead, matching the corrected behavior. Steps to reproduce: 1.Go to /shop. 2.Select any product. 3.Open Editor. 4.Click on the title of the product. 5.Click on the Ai popup. 6.You'll get an error saying "Record doesn't exist or has been deleted". opw-6312784
Odoo now prevents invalid custom relationship settings from making the server unavailable. Users can keep working and correct the Studio field configuration instead of encountering repeated 500 errors.
Original PR description
### Current behavior: When a Studio custom One2many field has an invalid relation field (inverse) name, accessing `registry.field_inverses` raises KeyError. On 19.0 this also crashes error-page…
### Current behavior: When a Studio custom One2many field has an invalid relation field (inverse) name, accessing `registry.field_inverses` raises KeyError. On 19.0 this also crashes error-page rendering, leaving the server unusable after closing Studio. On 18.0, it doesn't crash the server. ### Expected behavior: An invalid inverse name must not crash the registry or error handler; the server should stay responsive so the user can fix the field. ### Steps to reproduce: 1. Install sales, project with demo data 2. Create a sale order, use Studio to add a new "Lines" field (e.g. test line) 3. In the sale order, add a product that has Create on Order field (e.g. plumbing services from the demo data) > Confirm 4. Click on the smart button to go to project/task 5. Switch to Studio again, add a related field in the task, point that to Sales Order > test line 6. Activate debug mode, click on More for test line (task) 7. In properties, turn off Read only. Important here: add an invalid key for Relation Field (e.g. 'hehe') 8. Error popup shows key error. Try to close and exit out of Studio mode > Server crashes with 500 Internal Server Error ### Cause of the issue: - When a custom One2many field has an invalid `inverse_name` (set via Studio developer mode), `One2many.setup_inverses` accessed `registry[comodel]._fields[inverse_name]` without checking existence - Building the cached `registry.field_inverses` property then raises an uncaught KeyError and bricks the server with 500 Internal Server Error ### Fix: - When a custom One2many field has an invalid inverse_name (set via Studio developer mode One2many.setup_inverses accessed registry[comodel]._fields[inverse_name] without checking existence - Building the cached registry.field_inverse property then raises an uncaught KeyError and bricks the server with 500 Internal Server Error opw-6479178 Forward-Port-Of: odoo/odoo#287589
This fix prevents Odoo from creating repeated chatter messages when automated actions update records in a different language, such as when Odoobot uses French. It keeps tracking comparisons consistent across languages, reducing confusing noise in activity history.
Original PR description
Currently, the `_track_prepare` function doesn't add a fallback lang context, however the `_track_finalize` function does. This was in the refactor to saas-19.3 / mail mixin. When writing to a…
Currently, the `_track_prepare` function doesn't add a fallback lang context, however the `_track_finalize` function does. This was in the refactor to saas-19.3 / mail mixin. When writing to a tracked translatable field, this can result in a tracking message being added to the chatter when the active user is Odoobot, e.g. running a **scheduled action**. If Odoobot's language is non-english, then the fallback language of `_track_finalize` will be different than the no-context language of `_track_prepare` (which defaults to english), and in the chatter we see messages created repeatedly like: english translation -> odoo bot language translation. This fix makes the `_track_prepare` method also use the same fallback language as the `_track_finalize` method, so that the values are compared correctly in the same language and no accidental chatter records are made. ``` Steps to Reproduce on Newdb: 1. Make sure 'Automation Rules: check and execute' scheduled action is active. 2. Install l10n_fr, contacts. 3. Set the language on the res.partner record for Odoobot to French. 4. Create a new Automation Rule on Contact, Based on date field 'Created On', with 0 Hours delay. Give its name a French translation different from its English translation. 5. Make a new contact. 6. Run the scheduled action 'Automation Rules: check and execute'. 7. Observe in the Automation Rule in the chatter: English translation → French Translation (Nom de la règle d'automatisation) ``` opw-6563805, opw-6567533 <img width="1964" height="1290" alt="2026-09-23_13-33" src="https://github.com/user-attachments/assets/8f9e3af0-2f99-4ccd-aa1a-0d586636cff7" /> <img width="1105" height="669" alt="2026-09-23_13-33_1" src="https://github.com/user-attachments/assets/e4b24de5-0147-4842-953f-d687d3178943" />
This fixes a stock workflow issue where users splitting dropship lines could be shown an irrelevant "Pick From" popup, causing their entered lot number and expiration date to be ignored. The change keeps the process focused on creating the intended lot details, improving accuracy for tracked products with expiration dates.
Original PR description
Steps to reproduce 1. In Inventory Settings, enable Lots & Serial Numbers and Expiration Dates. 2. Create a storable product tracked by lot with Expiration Date enabled. 3. Create and confirm a…
Steps to reproduce 1. In Inventory Settings, enable Lots & Serial Numbers and Expiration Dates. 2. Create a storable product tracked by lot with Expiration Date enabled. 3. Create and confirm a dropship of it, then open the move's Detailed Operations. 4. The pre-filled line works: typing a batch and an expiration date creates exactly that lot at validation. 5. To split the quantity, click "Add a line": it opens the "Pick From" popup, and choosing "Create" there creates the lot and attaches it to the line. 6. On that line, edit "Lot/Serial Number" and "Expiration Date", then Validate. -> Expected: the values entered on the line are used. -> Actual: they are ignored; the lot created through "Pick From" is delivered with its own expiration date, the name and date typed on the line have no effect. Issue --- On a create-lots-only non-incoming operation like `Dropship`, `show_quant` is derived from `picking_code` alone, so the "Pick From" quant picker is shown even though the operation only creates lots. Adding a line goes through "Pick From", which creates the lot and attaches its `lot_id` to the move line; from then on the line's `lot_name` and `expiration_date` stay editable but do nothing, since a set `lot_id` is used as-is at validation and the `expiration_date` is recomputed from the lot, so anything typed there is silently dropped. Such an operation has no existing stock to pick from, so `show_quant` is now gated on the lot settings too: "Pick From" is hidden and the line's `lot_name` and `expiration_date` become the only inputs, created into the lot at validation like receipts already do. https://github.com/odoo/odoo/blob/68dcb950df83d70ff2aea0e05c96cc9b57c1a8a9/addons/stock/models/stock_move.py#L646-L647 opw-6530563 Forward-Port-Of: odoo/odoo#290402 Forward-Port-Of: odoo/odoo#287474
Fixes an error that occurred when users clicked graph bars or pivot cells in Ticket Analysis grouped by employee, manager, or department. The report now opens the related helpdesk tickets correctly, helping teams investigate workload and time tracking data without interruption.
Original PR description
Currently, an error occurs on clicking on the graph or the pivot cell if the data is grouped by Employee/Manager/Department. ### **Steps to reproduce** 1) Install helpdesk_timesheet with demo data 2)…
Currently, an error occurs on clicking on the graph or the pivot cell if the data is grouped by Employee/Manager/Department.
### **Steps to reproduce**
1) Install helpdesk_timesheet with demo data
2) Go to Timesheet > Reporting > Ticket Analysis
3) Set group by to Employee
4) Click on any graph bar or pivot cell
### **Error:**
`ValueError: Invalid field helpdesk.ticket.employee_id in condition ('employee_id', '=', 1)`
Root Cause:
The `helpdesk.ticket.report.analysis` model includes specific fields such as `employee_id`, `department_id`, and `employee_parent_id` (see [1]) that are defined for reporting purposes but do not exist on the `helpdesk.ticket` model. When a user clicks a data point to view related tickets, the reporting view passes the current domain directly to the ticket list view. Because `helpdesk.ticket` lacks these fields, the ORM fails to validate the domain, resulting in a server error.
[1]- https://github.com/odoo/enterprise/blob/8d16b647431985dd7c216ae39eea6ca050e04b46/helpdesk_timesheet/report/helpdesk_ticket_report_analysis.py#L15-L17
### **Fix:**
This commit introduces a mixin to intercept the openView call. The mixin maps reporting-specific fields to valid relational paths on the ticket model `(for example, employee_id is transformed into user_id.employee_id)`. This ensures that the domain generated from the report model is compatible with the target ticket model.
**opw-5931273**
Forward-Port-Of: odoo/enterprise#113088
Forward-Port-Of: odoo/enterprise#108503Unreconciling 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
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
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
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
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