Monday, September 28, 2026
7 changes · saas-19.1
Resolved issues and error corrections
Odoo now blocks invalid custom One2many relationship settings from being saved and safely ignores existing invalid settings. This keeps the server responsive so users can correct Studio configuration mistakes without triggering 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
Fixed an issue that could prevent managers from creating or viewing planning shifts when an employee's approved time off included a public holiday. The update ensures Planning correctly handles overlapping time off and public holiday records, keeping scheduling workflows uninterrupted.
Original PR description
… leaves # **Steps to Reproduce** 1. Create a fresh Odoo 19 database with Time Off, Employees, Payroll, and Planning installed. 2. Create an employee with a Flexible Working Schedule. 3. Go to **Time…
… leaves
# **Steps to Reproduce**
1. Create a fresh Odoo 19 database with Time Off, Employees, Payroll, and Planning installed.
2. Create an employee with a Flexible Working Schedule.
3. Go to **Time Off → Configuration → Time Off Types** and create:
* Name: Test Hourly Time Off
* Request Unit: Hours
4. Go to **Time Off → Configuration → Public Holidays** and create:
* Name: Test Public Holiday
* Date: 14 August 2026
* Time Type: Test Hourly Time Off
5. Create an employee Time Off using Test Hourly Time Off, covering the public holiday:
* 27 July 2026 → 30 August 2026
* Approve the Time Off.
6. Go to Planning → Schedule by Role.
7. Create a new planning shift for this employee.
# **Issue**
During the Planning Gantt computation, *`_get_flexible_resource_valid_work_intervals()`* calls *`_format_leave()`*. When the employee's Time Off includes the Public Holiday, multiple `resource.calendar.leaves` records are merged into the same interval. *`_format_leave()`* expects a single record, resulting in an **Expected singleton** error in `hr_holidays/models/resource.py`.
# **Solution**
```python
leave_record = leave[2].filtered('holiday_id')
```
This ensures that only the employee's actual `hr.leave` record is selected when a public holiday is included in the same interval. As a result, `date_from` and `date_to` are accessed on the correct single record, preventing the `Expected singleton` error while keeping the standard public holiday processing unchanged.
[Runbot v19.0 video](https://drive.google.com/file/d/1qg2VqtoxhBV3dIjxW05oQXaxKU-S9cv6/view?usp=sharing)
opw- 6478752
Forward-Port-Of: odoo/odoo#284204Italian invoices using the Import/Export fiscal position now apply the correct single 0% tax instead of adding two 0% taxes at the same time. This prevents incorrect tax setup on invoice lines and helps keep Italian accounting and e-invoicing data accurate.
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
Users can now download all files from a chatter message as a ZIP even when some attachments are stored in cloud storage. This prevents failed downloads and keeps the bulk file download action working consistently across local and cloud-hosted attachments.
Original PR description
Clicking "Download Files" on a chatter message fails when one of its attachments is stored in the cloud. Cloud attachments normally return a stream containing a signed URL. For individual downloads, Odoo redirects the browser to that URL, letting the cloud provider serve the file directly. The ZIP controller instead needs the file's bytes to build the archive on the server. It calls `Stream.read()`, which cannot read URL streams and raises `ValueError: Cannot read an URL`. Enable a `cloud_storage_force_download` context flag on the attachments when the cloud controller delegates ZIP creation to the mail controller. With this flag, the cloud attachment model fetches the file through the provider's signed URL and returns a data stream that the existing ZIP controller can read. opw-6570065 Forward-Port-Of: odoo/odoo#289819
Kenyan eTIMS submissions now exclude taxes that do not have a KRA tax code, such as levies paid to other authorities. This prevents over-reporting tax totals to the Kenya Revenue Authority and keeps invoice reporting aligned with the correct tax destination.
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 fixes formulas used in the Swiss balance sheet report so the report calculates figures correctly. It helps Swiss companies rely on more accurate financial statements for review and compliance purposes.
Original PR description
Change some formulas in the Swiss balance sheet task-6379692 Forward-Port-Of: odoo/enterprise#123895
Dropship operations that only create lots no longer show the misleading "Pick From" option. This ensures lot names and expiration dates entered by users are applied correctly, avoiding silent mistakes in delivered product traceability.
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#290071 Forward-Port-Of: odoo/odoo#287474