Saturday, August 22, 2026
8 changes · saas-19.4
Enhancements to existing features
This update simplifies how localization-specific partner identifiers are made available across Odoo. It reduces duplicate setup work for teams adding country-specific identifier rules, while keeping core tax identifier metadata available consistently.
Original PR description
When adding an l10n-only identifier metadata, you'll want to EXTENDS the `_get_all_additional_identifiers_metadata` if you want your new identifier to be displayed on the JSON additional identifiers. Before this commit, you also needed to EXTENDS `_get_all_identifiers_metadata` to make it available to globally, regardless of if it was in additional_identifers or not, for example for validation purposes, which is tedious and easy to avoid. Note that it make sense that the TIN_METADATA are always added in base module, since they describe the content of the `vat` field that is generic, and that we want to be available for all databases, regardless of which localization is installed. task-none
Resolved issues and error corrections
This update brings the embedded spreadsheet component up to its latest 19.4 version and fixes how input borders are displayed. Users should see more consistent spreadsheet editing visuals with no expected workflow changes.
Original PR description
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/b48dbb3d11 [REL] 19.4.9 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/171df58fa3 [FIX] inputs: explicitly define border-style [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) Co-authored-by: Florian Damhaut (flda) <flda@odoo.com> Co-authored-by: Anthony Hendrickx (anhe) <anhe@odoo.com> Co-authored-by: Alexis Lacroix (laa) <laa@odoo.com> Co-authored-by: Lucas Lefèvre (lul) <lul@odoo.com> Co-authored-by: Adrien Minne (adrm) <adrm@odoo.com> Co-authored-by: Ronak Mukeshbhai Bharadiya (rmbh) <rmbh@odoo.com> Co-authored-by: Dhrutik Patel (dhrp) <dhrp@odoo.com> Co-authored-by: Rémi Rahir (rar) <rar@odoo.com> Co-authored-by: Pierre Rousseau (pro) <pro@odoo.com> Co-authored-by: Vincent Schippefilt (vsc) <vsc@odoo.com> Co-authored-by: Marceline Thomas (matho) <matho@odoo.com>
This fixes an issue where finding a customer's most recent sales order line for timesheet-related work could fail when the search criteria included list values. The change keeps the existing caching behavior while preventing invalid searches, improving reliability for sales and project workflows.
Original PR description
- Avoid converting list values in `_get_last_sol_of_customer_domain` to an invalid domain structure when computing the last sale order line of a customer. - Fix by using `str(domain)` as the cache key instead of the domain itself, while still passing the original `domain` to `search()`. This keeps the per-domain caching behavior intact and works for any domain, regardless of whether it contains list values. task-6425335 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281897
Odoo now correctly creates replenishment suggestions when stock is moved from one internal replenishment location to another. This prevents source locations from being overlooked when a confirmed internal transfer makes their forecasted quantity negative, helping businesses avoid unexpected stock shortages.
Original PR description
Steps to reproduce: ------------------- 1. Install `stock` module. 1. Enable the "Storage Locations" from setting. 2. Create an internal location and enable `replenish_location` on it and set…
Steps to reproduce:
-------------------
1. Install `stock` module.
1. Enable the "Storage Locations" from setting.
2. Create an internal location and enable `replenish_location` on it and set warehouse(WH) as Parent.
3. Create a storable product track by quantity with no on-hand quantity.
4. Create an internal transfer from the warehouse stock location to the new internal location.
5. Confirm the transfer.
6. Open the Replenishment report.
Issue:
------
No manual reordering rule is created for the product at the source location, although
the confirmed transfer makes its forecasted quantity negative.
If `replenish_location` is disabled on the destination, the expected reordering rule is created.
Cause:
------
When both the source and destination have `replenish_location=True`, Odoo considers both locations
inside the same replenishment area. Therefore, the internal transfer is not counted as either
incoming or outgoing, and no replenishment is created for the source location.
Code Flow:
Opening the Replenishment report calls
`stock.warehouse.orderpoint.action_open_orderpoints()`, which delegates the report preparation to `_get_orderpoint_action()`:
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/stock_orderpoint.py#L324-L326
`_get_orderpoint_action()` first obtains every location that must be monitored for replenishment through `_get_orderpoint_locations()`:
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/stock_orderpoint.py#L520
`_get_orderpoint_locations()` returns all locations whose `replenish_location` field is enabled:
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/stock_orderpoint.py#L799-L800
All these locations are passed together to
`product.product._get_domain_locations_new()`:
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/product.py#L396-L464
This method treats the provided locations and their descendants as a set. Its move domains are equivalent to:
- incoming: destination is inside the set and source is outside;
- outgoing: source is inside the set and destination is outside.
Consider the following sibling locations:
WH
├── Stock
└── Replenish Location
and the transfer:
Stock -- 3 units --> Replenish Location
When `replenish_location` is disabled on the destination, the considered location set contains only `Stock`:
considered location set = {Stock}
The source is inside the set and the destination is outside:
source inside = True
destination outside = True
The transfer therefore matches the outgoing domain:
source inside AND destination outside
True AND True
= True
It is included in `moves_out`, and the preliminary forecast for `Stock` becomes:
0 on hand + 0 incoming - 3 outgoing = -3
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/stock_orderpoint.py#L552
The negative quantity is detected and a manual orderpoint is created.
When `replenish_location` is enabled on the destination, both sibling locations belong to the considered location set:
considered location set = {Stock, Replenish Location}
Both ends of the transfer are now inside:
source inside = True
destination inside = True
destination outside = False
source outside = False
The transfer matches neither aggregate domain:
incoming:
destination inside AND source outside
True AND False
= False
outgoing:
source inside AND destination outside
True AND False
= False
The transfer is considered internal to the considered location set and is therefore absent from both `moves_in` and `moves_out`:
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/stock_orderpoint.py#L530-L540
This aggregate treatment conflicts with the next step, where `_get_orderpoint_action()` computes the quantity separately for each replenishment location:
https://github.com/odoo/odoo/blob/12a66c6931d81e1cce18f676836aca0b32090d16/addons/stock/models/stock_orderpoint.py#L549-L552
When `Stock` is evaluated separately, the internal transfer has already been discarded. Its calculated outgoing quantity is consequently zero:
0 on hand + 0 incoming - 0 outgoing = 0
As the quantity is not negative, the product is not scheduled for the final `virtual_available` computation and no manual orderpoint is created.
Fix:
----
Also retrieve moves whose source and destination are both inside the aggregate replenishment-location set.
Include these internal moves in both the incoming and outgoing grouped queries. The existing per-location path filtering then assigns each side correctly:
- the source replenishment location counts the move as outgoing;
- the destination replenishment location counts it as incoming.
For the reported transfer, this produces:
Stock:
0 on hand + 0 incoming - 3 outgoing = -3
Replenish Location:
0 on hand + 3 incoming - 0 outgoing = 3
This is correct because the report creates replenishment propositions per location, even though the initial move query is performed for all replenishment locations together.
---
opw-6462608
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#282168The help text shown when hovering over the Sales Order Expiration field was rewritten to fix a grammar issue and make the wording more natural. This small correction improves clarity for sales users without changing any business process or functionality.
Original PR description
Steps to produce: --- - Install the Sales module. - Create a new Sales Order. - Hover over the `Expiration` field. Issue: --- - The help text of the Expiration field contains a grammatical error and the overall sentence is slightly awkward. Improve the help text to make it grammatically correct and more natural. opw-6481226 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283757 Forward-Port-Of: odoo/odoo#283187
This update fixes issues in Odoo's JSON logging so ignored fields and test-related log data are handled correctly. It also adds safeguards through tests and makes it easier to include extra log details without replacing the default configuration.
Original PR description
The JsonFormatter has two bugs - the ignore list is not working as expected - in 18.0-18.4 the 'test' key is broken This commit add tests to ensure those behavior works as expected While on it, also…
The JsonFormatter has two bugs - the ignore list is not working as expected - in 18.0-18.4 the 'test' key is broken This commit add tests to ensure those behavior works as expected While on it, also adds a `additional_record_keys` parameter to allow to specifically add keys to the default list, without having to override the whole list, and add additional default keys (exc_info and test) The previous `ignored_record_keys` default value was possible to remove by calling `JSONFormatter(ignore_record_keys=[])` The purpose was to be able to easily include all keys and ignore the default ingnore list, but this makes the additional blacklisting of a few keys more tedious, and the general usage and implementation more complex `JSONFormatter(ignore_record_keys=[*JSONFormatter.DEFAULT_IGNORED_RECORD_KEYS, 'other key'])` To simplify the logic, **this is not the case anymore**, so to include all keys something like this would be needed `JSONFormatter(additional_record_keys=JSONFormatter.DEFAULT_IGNORED_RECORD_KEYS)` Or an hardcoded list. Forward-Port-Of: odoo/odoo#283042 Forward-Port-Of: odoo/odoo#279049
Android users can now download files opened from the Odoo mobile file viewer, such as images shared in Discuss. The app now uses the mobile download flow directly for web links, avoiding an Android limitation that previously blocked the download.
Original PR description
Steps to reproduce: - send an image in a Discuss channel - click the image to open the file viewer - click the download button => Android shows "The Odoo Mobile Apps only supports file downloads…
Steps to reproduce: - send an image in a Discuss channel - click the image to open the file viewer - click the download button => Android shows "The Odoo Mobile Apps only supports file downloads using the HTTP protocol." downloadFile()'s GET-by-URL case fetches the URL via XHR, then saves the Blob response by clicking a hidden <a download> anchor on a blob: URL. Android's DownloadManager only accepts http(s) URLs, so it rejects that blob: URL instead of downloading anything. Patch downloadFile._download to hand the URL directly to a new mobile.methods.saveFile bridge method when available, the same way download._download already delegates to mobile.methods.downloadFile. Blob/string content downloads aren't handled here — the only such call site (spreadsheet JSON export) is debug-mode only, so this is left as a console.warn for now. Related to odoo/odoo@e83fd8c08c879f5e262d39f24edcb3f81238ea82 Code made by Claude Changes supervised by HUVW Forward-Port-Of: odoo/enterprise#128471 Forward-Port-Of: odoo/enterprise#127693
Creating Colombian child contacts now keeps them as individuals unless a VAT number is provided. This prevents incorrect company classification and supports cleaner customer and contact records.
Original PR description
Before this change: When creating a child contact under a Colombian company from another company context, the identification type defaults to NIT. The system evaluated the child contact as a company immediately, regardless of whether a VAT number was entered, preventing proper individual contact creation. To reproduce: 1. Create a new contact and set the country to Colombia. 2. Set the identification type to NIT. 3. Leave the VAT field empty. 4. Observe that the "Is a Company" checkbox becomes checked automatically. After this change: The company computation logic explicitly verifies that a valid VAT number is present before evaluating NIT contacts as companies, allowing individual child contacts to retain their correct entity status. opw-6468848 Forward-Port-Of: odoo/enterprise#128656