Tuesday, September 8, 2026
38 changes · saas-19.3
Enhancements to existing features
Dutch SBR and ICP report submissions now go through Odoo's IAP proxy instead of connecting directly to Digipoort. This simplifies the submission process and gives users the choice to submit with either their own certificate or Odoo's group certificate.
Original PR description
*: l10n_nl_reports_sbr{,_icp,_status_info}
---
Description of the issue this commit addresses:
Dutch SBR and ICP reports still send directly to Digipoort with duplicated SOAP/signature code and no shared group-certificate flow.
---
Desired behavior after this commit is merged:
This commit routes both reports through IAP proxy signing/sending, and lets users submit with either a personal certificate or Odoo's group one.
---
IAP PR: https://github.com/odoo/iap-apps/pull/1593
task-3439634
Forward-Port-Of: odoo/enterprise#117617Installing the Colombian DIAN e-invoicing module is now faster on large databases. The change avoids unnecessary recalculation of existing accounting records during setup, reducing the risk of installation timeouts while keeping normal future calculations unchanged.
Original PR description
- Pre-create the stored computed columns `l10n_co_edi_type`, `l10n_co_dian_state`, and `l10n_co_edi_cufe_cude_ref` in `_auto_init()`. - This prevents Odoo from computing and writing these fields for all existing `account.move` records when installing `l10n_co_dian`. - This is particularly important for large databases with a high volume of Colombian accounting moves, where the initial computation can take too long and cause the module installation to hit the time limit. - The compute methods are kept unchanged, so the fields continue to be computed normally for subsequent record creation or dependency changes. **opw-6451331** Forward-Port-Of: odoo/enterprise#130671 Forward-Port-Of: odoo/enterprise#129507
Opening point-of-sale registers for newly created companies is now much faster when the database contains many accounting entries. The system now checks smaller company information first, avoiding slow scans through massive accounting records and reducing delays from seconds to milliseconds in large installations.
Original PR description
When you create a new company in a database that has a lot of existing account move lines and you attempt to open a PoS register from the list view, `_compute_company_has_template` checks…
When you create a new company in a database that has a lot of existing
account move lines and you attempt to open a PoS register from the list
view, `_compute_company_has_template` checks `_existing_accounting` for
the new company and wil run a sequential scan on the entire account_move_line
table followed by a nested loop as the query planner assumes AMLs company_ids
will be roughly evenly distributed.
This is not the case in a new company that has no/very few AMLs.
This is because the query ran is:
`SELECT COUNT(*) FROM
(SELECT FROM "account_move_line"
WHERE (
"account_move_line"."company_id" IN
(SELECT "res_company"."id" FROM
"res_company" WHERE
("res_company"."parent_path" LIKE '3/%')
))
LIMIT 1)`
and the values of company_id being searched for aren't known until the
subquery runs.
Running a query more like
`SELECT COUNT(*) FROM
account_move_line
WHERE company_id IN (%s)`
is much faster
Since res_company will always be a smaller table, we can do the inexpensive
search first and then pass in the values so Postgres can do a cheaper
search and return faster.
Benchmark time of `_existing_accounting`:
| Company 1 AML count | Company 2 AML count | Pre-fix | Post-fix | Multiplier |
|---|---|---|---|---|
| 10,000,000 | 0 | 700 Milliseconds | 500 Microseconds | 1,400x |
| 20,000,000 | 0 | 1.35 Seconds | 1 Millisecond | 1,350x |
| 20,000,000 | 20,000,000 | 2 Milliseconds |1.3 Milliseconds | 1.5x |
| 50,000,000 | 0 | 3.25 Seconds | 1 Millisecond | 3,250x |
| 100,000,000 | 0 | 5.3 Seconds | 1.3 Milliseconds | 4,075x |
Query Plan Before:
```
"Aggregate (cost=0.08..0.09 rows=1 width=8) (actual time=5573.001..5573.002 rows=1.00 loops=1)"
" Buffers: shared read=571435"
" -> Limit (cost=0.00..0.08 rows=1 width=0) (actual time=5572.995..5572.997 rows=0.00 loops=1)"
" Buffers: shared read=571435"
" -> Nested Loop (cost=0.00..https://github.com/odoo/odoo/commit/1571439c0c70c1f1dc3229421e696b97ce1678f8.33 rows=20000086 width=0) (actual time=5572.988..5572.989 rows=0.00 loops=1)"
" Join Filter: (account_move_line.company_id = res_company.id)"
" Buffers: shared read=571435"
" -> Seq Scan on account_move_line (cost=0.00..971432.72 rows=40000172 width=4) (actual time=0.432..1913.934 rows=40000000.00 loops=1)"
" Buffers: shared read=571431"
" -> Materialize (cost=0.00..4.03 rows=1 width=4) (actual time=0.000..0.000 rows=0.00 loops=40000000)"
" Storage: Memory Maximum Storage: 17kB"
" Buffers: shared read=4"
" -> Seq Scan on res_company (cost=0.00..4.03 rows=1 width=4) (actual time=0.785..0.785 rows=0.00 loops=1)"
" Filter: ((parent_path)::text ~~ '3/%'::text)"
" Rows Removed by Filter: 2"
" Buffers: shared read=4"
"Planning:"
" Buffers: shared hit=574 read=77"
"Planning Time: 15.323 ms"
"Execution Time: 5573.060 ms"
```
Query Plan After:
```
"Aggregate (cost=4.46..4.47 rows=1 width=8) (actual time=1.972..1.973 rows=1.00 loops=1)"
" Buffers: shared read=3"
" -> Limit (cost=0.44..4.46 rows=1 width=0) (actual time=1.967..1.968 rows=0.00 loops=1)"
" Buffers: shared read=3"
" -> Index Only Scan using account_move_line__company_id_index on account_move_line (cost=0.44..4.46 rows=1 width=0) (actual time=1.965..1.966 rows=0.00 loops=1)"
" Index Cond: (company_id = 3)"
" Heap Fetches: 0"
" Index Searches: 1"
" Buffers: shared read=3"
"Planning:"
" Buffers: shared hit=3"
"Planning Time: 0.219 ms"
"Execution Time: 1.998 ms"
```
opw-6513885
Forward-Port-Of: odoo/odoo#285096Duplicating large sections with many sales order lines is now much faster and less likely to time out. The change reduces repeated server calls by processing duplicated lines together, improving responsiveness for users working with large quotes or orders.
Original PR description
Issue: Sections containing a large number of subsections or lines can take too long to duplicate and may eventually time out. The slowdown stems from the onchange issues in `_duplicateRecords()` in…
Issue: Sections containing a large number of subsections or lines can take too long to duplicate and may eventually time out. The slowdown stems from the onchange issues in `_duplicateRecords()` in sale.order.line. Each sale.order.line is first created as an empty datapoint, which triggers an onchange RPC. The copied values are then applied, triggering a second onchange RPC for every duplicated line. Fix: Prepare the copied values before creating the datapoints and send them through a batched onchange. This retrieves the required onchange values for all duplicated lines in a single RPC. Benchmarks: sale.order.line onchanges: Note: "Timing Before" is calculated by the difference between the first and last sale.order.line onchange completion times. | Lines | RPCs Before | RPCs After | Timing Before | Timing After | Speedup | | ----: | ----------: | ---------: | ------------: | -----------: | ------: | | 10 | 20 | 1 | 0.31s | 0.04s | 5.52x | | 50 | 50 | 1 | 1.62s | 0.14s | 11.67x | | 100 | 200 | 1 | 3.41s | 0.25s | 13.64x | | 500 | 1000 | 1 | 10.98s | 0.96s | 11.43x | | 1000 | 2000 | 1 | 31.07s | 2.74s | 11.34x | Related: opw-6395475 Forward-Port-Of: odoo/odoo#280418
Resolved issues and error corrections
This fixes Spanish TicketBAI reporting for point-of-sale orders paid with gift cards, ensuring refund-related lines use the correct negative sign. It prevents mismatches between reported line totals and invoice totals, helping businesses keep tax XML records consistent.
Original PR description
The issue fixed in commit[1] is again reproducible after commit [2] the pos order line for gift card is now not considered as a refund Step to reproduce: - Install pos_loyalty and…
The issue fixed in commit[1] is again reproducible after commit [2] the pos order line for gift card is now not considered as a refund Step to reproduce: - Install pos_loyalty and l10n_es_edi_tbai_pos with demo data - Create a gift card (add a tax to the discount product, any 0%) - start pos, add a product and use the gift card - fulfill the order - go to backend and open that order - In the TicketBAI XML, the values for the giftcard product will be positive, causing an inconsistency between the product line total and the invoice total [1] https://github.com/odoo/odoo/commit/0bbc5ebdcc7e014d87130b2ff9cee98e3aa7479a [2] https://github.com/odoo/odoo/commit/b17c9713e7a3305c240298fa54fcf1bc87a9bb8c FIX - we used to determine `sign` based on each order line's `is_refund` property - this property is quite sensitive as it depends on factors like line's qty, price, is reward or not. - so its better to depend on order's refund property for sign reversal https://github.com/odoo/odoo/blob/4fef2c5b69fac10594fac81b149d542ee4621b13/addons/point_of_sale/models/pos_order.py#L1768 opw-6226003 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286448 Forward-Port-Of: odoo/odoo#278042
This fix avoids a race condition during e-invoicing proxy registration by checking for an existing local user before contacting the external IAP service. It prevents companies from ending up with outdated credentials that break later proxy communications and require manual re-registration.
Original PR description
In the current flow, _register_proxy_user first calls IAP create_user, then inserts the returned credentials in account_edi_proxy_client.user. At the same time, IAP create_user_2 may replace an existing user with a new one with different credentials before local persistence settles. So Tx A calls IAP and gets credentials for remote user U1. Tx B calls IAP and gets credentials for remote user U2 and unlinks U1. Tx A persists U1 locally. Tx B fails local insert due to a unique constraint. Client DB keeps U1 credentials, but IAP now expects U2. Subsequent proxy calls from the client fail. The DB is left with stale, unusable credentials and cannot recover without re-registration. IAP: https://github.com/odoo/iap-apps/pull/1816 task-6520501 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285648
Fixes an issue where dropshipped kit products using FIFO or average costing could show a zero cost on sales orders. This ensures sales margins reflect the purchase and component costs more accurately, improving profitability reporting.
Original PR description
Currently, when a user dropships a kit product with FIFO/AVCO category costing, the computed cost of the kit on the Sales Order becomes 0. ## Steps to replicate: - Install Sales, Purchase, and…
Currently, when a user dropships a kit product with FIFO/AVCO category costing, the computed cost of the kit on the Sales Order becomes 0. ## Steps to replicate: - Install Sales, Purchase, and Manufacturing modules. - Enable Margins and Dropshipping in settings. - Go to Product Categories > Goods and set costing method to FIFO - Create two products MOBO and CPU: - Cost: $300 - Category: Goods - Add a vendor in Purchase section with unit price same as cost - Enable Dropshipping route - Create a kit product Computer Kit with the same configuration as above (except cost) and add MOBO and CPU as components on its BoM. - Open the Computer Kit and click Compute Price from BoM. - Create and confirm a Sales Order with the Computer Kit. - Confirm the related Purchase Order and validate the dropship picking. - Return to the Sales Order > make the `Cost` field visible on SO lines . ## Observed Behavior: The product cost appears as 0 on the Sales Order, even though a price is set on the related Purchase Order. ## Root cause: When the dropshipping picking is confirmed, the method `_compute_purchase_price` is triggered to compute the cost on the Sales Order line. It calls `_get_price_unit_delivery` at [1], which then calls `_get_price_unit_dropshipped` at [2] since the products are dropshipped. Because dropshipping moves do not carry stock values, it calls `_get_value` at [3] to determine an appropriate value. This method uses `_get_value_data` at [4], which retrieves the value from the quotation via `_get_value_from_quotation` at [5]. Here, a cost ratio is applied at [6] to distribute the cost based on the BoM cost share (i.e., the percentage split of cost across kit components). Since no cost share is defined on the BoM, the ratio is 0, causing the final computed cost ratio to also be 0 at [7] and the cost value being returned as zero as shown in at [6]. **Why not in lower versions?** This issue did not occur in versions 18.4 and earlier due to the presence of the stock valuation layer and the defined logic for kit products to calculate cost, as shown in [8]. [1]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/sale_stock_margin/models/sale_order_line.py#L18-L21 [2]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/stock_account/models/stock_move.py#L672 [3]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/stock_account/models/stock_move.py#L681-L684 [4]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/stock_account/models/stock_move.py#L336 [5]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/stock_account/models/stock_move.py#L392-L397 [6]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/purchase_stock/models/stock_move.py#L225-L241 [7]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/purchase_mrp/models/stock_move.py#L12-L25 [8]: https://github.com/odoo/odoo/blob/7f1cd04259202bcafc94965d6420df360f9152c1/addons/mrp_account/models/product.py#L65-L89 ## Solution: It should not be assumed that users will always define a cost share on the Bill of Materials. In many cases, they may expect the kit price to be derived directly from the costs of its component products. To support this, we can override `_get_price_unit_dropshipped` to properly handle kit products, ensuring the cost is computed based on the component product costs instead. opw-6113398 Forward-Port-Of: odoo/odoo#282616 Forward-Port-Of: odoo/odoo#261705
When an email cannot be delivered, Odoo now displays the configured outgoing mail server name instead of showing 'None'. This helps users and support teams identify which email server needs attention when delivery problems occur.
Original PR description
Steps to reproduce: 1. Run a local SMTP server that responds with a server error. The server file is provided in the task [refuse_smtp.py](https://github.com/user-attachments/files/27166855/refuse_smtp.py) 2. Create an outgoing server for this SMTP server 3. Send an email Issue: The delivery failure reason displays Mail delivery failed via SMTP server 'None' instead of the configured server name. Cause: ir.mail_server.send_email() builds the failure message from the smtp_server argument, but in the common path the mail is sent via mail_server_id. In that case, the actual SMTP server is resolved in connect(), while smtp_server remains unset, so the error message shows None. Solution: Store the resolved server label on the SMTP connection when opening it, and reuse that value when formatting send failures. opw-6139168 Forward-Port-Of: odoo/odoo#280750 Forward-Port-Of: odoo/odoo#261776
Fixes incorrect totals in the French association balance sheet so assets and liabilities are calculated accurately. This prevents missing asset amounts and double-counted liability entries, giving users reliable financial statements.
Original PR description
## [FIX] l10n_fr_reports: fix missing lines in association balance sheet ### Issue: The Active part of the Balance Sheet for associations shows an incorrect `TOTAL ACTIVE` — some accounts are not…
## [FIX] l10n_fr_reports: fix missing lines in association balance sheet ### Issue: The Active part of the Balance Sheet for associations shows an incorrect `TOTAL ACTIVE` — some accounts are not propagated to their parent aggregations ### Cause: `ACTIF_IMMOBILISE` was missing `BIEN_PAR_DONATION` in its formula `ACTIF_CIRCULANT` was missing both `DISPONIBILITES` and `INSTRU_FINAN` in its formula ### Steps to reproduce: - Install `l10n_fr_reports` and `accountant` - Create a new FR asso company and switch to it - In Accounting Settings, select: France - Associations accounting plan - Create a Journal Entry: Account: 240000, Debit: 100 (BIEN_PAR_DONATION) Account: 512001, Debit: 100 (DISPONIBILITES) Account: 520000, Debit: 100 (INSTRU_FINAN) Account: 509000, Credit: 300 - Open the Balance Sheet and select Balance Sheet for associations Before the fix, `TOTAL ACTIVE` is 0 instead of 300 -------------- ## [FIX] l10n_fr_reports: add force_date_scope to asso cross_report ### Issue: The Passive part of the Balance Sheet for associations shows an incorrect `TOTAL PASSIVE` — the same move line is counted twice, once in `Retained earnings` and once in `Profit or loss for the year` ### Cause: In 19.1, `cross_report` aggregations were refactored: https://github.com/odoo/enterprise/commit/e699a14a1922d6b8a5bc6b88ad9c02a411e4b073 By default, a `cross_report` no longer forces its `date_scope` to the terms it calls — `force_date_scope` must now be explicitly passed in the subformula The association balance sheet was added in 19.1 without this parameter, so `RESULT_LEXERCICE` (`from_fiscalyear`) and `REPORT_NOUVEAU` (`to_beginning_of_fiscalyear`) both used the current report's `date_scope` instead of their own This caused both expressions to match the same entries and double the `TOTAL PASSIVE` ### Steps to reproduce: - Install `l10n_fr_reports` and `accountant` - Create a new FR asso company and switch to it - In Accounting Settings, select: France - Associations accounting plan - Create a Journal Entry: Account: 512001, Debit: 100 Account: 701100, Credit: 100 - Open the Balance Sheet and select Balance Sheet for associations Before the fix, `TOTAL PASSIVE` is 200 instead of 100 opw-6520639 Forward-Port-Of: odoo/enterprise#130147
This fixes mobile manufacturing order creation so generated work orders keep their linked operation. As a result, instruction and quality-check steps are created correctly, helping shop floor users avoid missing required checks on small screens.
Original PR description
Steps to reproduce --- 1. Enable Work Orders. 2. On a product's BoM, add an operation carrying an Instructions step. 3. On a small screen, create a manufacturing order for that product. 4. Open the…
Steps to reproduce --- 1. Enable Work Orders. 2. On a product's BoM, add an operation carrying an Instructions step. 3. On a small screen, create a manufacturing order for that product. 4. Open the generated work order: its quality-check step is missing because the work order was created without an operation. Issue --- On a small screen the MO form renders `workorder_ids` with the work order kanban sub-view, so the onchange field spec only carries that kanban's fields; with `operation_id` absent from it, the generated work order is saved without an operation and its `quality_point_ids` stays empty, so no `quality.check` is created. This exact bug was already fixed by https://github.com/odoo-dev/odoo/commit/e3f6cef5cf94391b5c018c5eb44ce1ddee99290b, which restored the invisible `operation_id` on the kanban, and then reintroduced by https://github.com/odoo-dev/odoo/commit/6318895101435a0a9d4bdff0b8dbfbb17179378e, a kanban rework that dropped the field again; this simply restores it. opw-6488529
This fixes how Mail tracks in-progress database updates by sending only the active items instead of a much larger empty-filled snapshot. The change can greatly reduce memory use during long-running activity, helping prevent errors and improve reliability.
Original PR description
The store version snapshot encoded in progress transactions (xip) as a bitmap spanning the whole [xmin, xmax) range, so its size grows with how far apart those bounds are rather than with how many transactions are actually in progress. A single long-lived transaction can push that range into the hundreds of thousands, leading to memory errors, most of it being zeroes. Sending it as a list of strings is much smaller (95-98% smaller tested on odoo). 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
This fixes an internal test issue where a shell command could accidentally use the same database as the running test process and get stuck. The change helps keep automated testing reliable, reducing delays for developers and release validation.
Original PR description
TestCommand.test_shell spawns a subprocess running `odoo-bin shell` with stdin/stdout piped through a pty. When PGDATABASE is set in the environment, that subprocess inherits it and resolves the same db_name as the parent test process, then calls Registry(dbname) to open a shell console against it. Steps to reproduce: - export PGDATABASE=db_name - ./odoo-bin -i test_core --test-tags .test_shell
Early payment discount entries now preserve the original invoice line cost allocations for all discount calculation methods. This ensures discounts are reported against the correct analytic accounts, improving accounting accuracy without changing existing included-discount behavior.
Original PR description
_get_invoice_counterpart_amls_for_early_payment_discount_per_payment_term_line only split the early payment discount amount per invoice line (and thus preserved each line's own analytic distribution)…
_get_invoice_counterpart_amls_for_early_payment_discount_per_payment_term_line only split the early payment discount amount per invoice line (and thus preserved each line's own analytic distribution) when the payment term's early_pay_discount_computation was "included". For "mixed" and "excluded", it instead built a single counterpart line, discarding the analytic distribution of the invoice lines. Compute the per-invoice-line base amounts (and the corresponding price_unit for "mixed") for all three computations, keeping only the tax calculation restricted to "included". This way "mixed" and "excluded" discount entries now keep the analytic distribution of the invoice line they come from, while behavior for "included" is unchanged. Steps: - Create 3 payment terms with EPD, each one with a different `early_pay_discount_computation` setting - For each payment terms, create one invoice with two lines, only one having an analytic distribution - Confirm and pay the 3 invoices (early payment, discount applied) Issue: For 'mixed' and 'excluded', the discount line is the sum of the discount from the 2 invoice lines, and the analytic distribution is lost. opw-6380278 Forward-Port-Of: odoo/odoo#286816 Forward-Port-Of: odoo/odoo#282541
This update corrects how Romanian electronic invoicing attachments are read, avoiding errors caused by treating file data as text. It helps ensure invoice XML attachments are processed reliably without changing the user workflow.
Original PR description
A previous commit (https://github.com/odoo/odoo/commit/41fe2ebdb9cc37341362d7af829c087a5f72f9f1) added an `encode` call to a line fetching the raw attachment. This assumes the attachment value is a `str`. However, the raw attachment values are actually `bytes` objects. This PR removes the `encode` call and adjusts the tests to reflect the change. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Belgian customers or suppliers that changed Peppol registration identifiers are checked again before sending documents. This helps avoid failed electronic invoice delivery when a partner is reachable under the newer identifier, with related test adjustments to prevent unrelated request errors.
Original PR description
Some partners were registered on Peppol with EAS 9925 (Belgian VAT) but have since moved to 0208. They became unreachable via Peppol because we never check if they exist on the network with EAS 0208, which makes their status `not_valid`. This fix forces the re-checking of the status with EAS 0208 for partners having EAS 9925 and a `not_valid` status. task-6296017 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#277072 Forward-Port-Of: odoo/odoo#270742
Timesheets now handle short work activity after an away-from-keyboard period correctly. This prevents small work events from being wrongly added to inactive time, improving the accuracy of recorded working hours.
Original PR description
Before this Commit, if an AFK event was followed by small events, those small events would be packed into the AFK event. This caused the recorded time worked to be inaccurate. After this Commit, when an AFK event is followed by small events, they are either packed together to form a bigger non‑AFK event or ignored if they can't be packed. task-[6486145](https://www.odoo.com/odoo/project/4105/tasks/6486145)
Fixed an accounting issue where reconciling matching foreign-currency journal items could leave a company-currency balance uncorrected. The system now creates the needed exchange difference entry for reconcilable accounts, helping keep accounting balances accurate.
Original PR description
Steps to reproduce: 1. Create two misc entries with the same foreign currency amount but different company currency amounts, on an account with "Allow Reconciliation" enabled (one debit, one credit). 2. Go to Journal Items, select both lines and click Reconcile. Issue: No exchange difference entry is generated, so the residual amount in company currency is left unbalanced. Fix: Only skip the exchange difference when the account is not reconcilable, which keeps the deferral behaviour untouched while restoring the exchange difference entry in the standard case. Causing PR: https://github.com/odoo/enterprise/pull/108362 task-6535655
This fix keeps point of sale restaurant orders marked as needing synchronization when another device refreshes the same table. It prevents items added on one device from disappearing or failing to reach the database and other devices.
Original PR description
Steps to reproduce: - Device A opens table 10 and adds a product - Device B opens table 10, then goes back to the floor plan - Device A goes back to the floor plan => The lines added on A are lost, they are never synced to the database, nor to the other device When B triggers a synchronisation, A reads the open orders from the server. The local lines of A are kept, but `setup`, which is also called when a record is updated, resets the dirty flag of the order. Going back to the floor plan calls `syncAllOrders`, which filters the order out because it is not dirty anymore, so the lines are never sent. The dirty flag is now kept when the record is updated with server data, since that data does not contain the changes made locally and must be synced in the next synchronization call task-id: 6486258 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286279 Forward-Port-Of: odoo/odoo#284701
Restored or duplicated databases using the neutralize option are now neutralized before background jobs can detect and run on them. This prevents automated tasks from briefly running with live data or settings during the restore or copy process, reducing operational risk for copied environments.
Original PR description
Before this commit: If you restore a backup or duplicate a database via /web/database/manager with "Neutralize" enabled, the cron workers had a small window between the creation of the Registry and the neutralization of the database where they could try to execute crons. Since neutralize_database does not require a full registry to run (it only does raw SQL operations), we can run it before the Registry creation (which has the effect, among other things, of making the cron workers aware of the new database). opw-6517819 Forward-Port-Of: odoo/odoo#286870 Forward-Port-Of: odoo/odoo#286532
The Sign app's automated tests were updated to work consistently with different supported PDF library versions. This reduces false test failures during development and release validation without changing the user-facing signing experience.
Original PR description
Prior to this commit, `PageObject` was not re-exported by `odoo.tools.pdf`, forcing tests (such as `test_origin_offset_translation` in `sign`) to patch internal module paths like `PyPDF2._page.PageObject`. This resulted in test failures depending on the installed PDF library version: - `pypdf` (>= 3.0.0): `PyPDF2` submodules no longer exist. - `PyPDF2` 1.x: `PageObject` resides in `PyPDF2.pdf` rather than `PyPDF2._page`. To resolve this: - `PageObject` has been re-exported through `odoo.tools.pdf`. - Update `test_origin_offset_translation` to import `PageObject` via `odoo.tools.pdf` and use `patch.object` with standard attribute names (`cropbox`, `add_transformation`). runbot-946795 Forward-Port-Of: odoo/enterprise#130093 Forward-Port-Of: odoo/enterprise#130017
Argentinian electronic export invoices now use the correct recipient identification code in their QR data. This lets customers validate these invoices as legal documents on the ARCA verification page and avoids incorrect identification type display.
Original PR description
Before this change we were sending id type code 0 and this generate two problems * ARCA verification page it was wrongly taking "CI Policia Federal" as the identification type of the receptor * We were not able to validate the expo invoice, we get always an error With this change the expo invoice can be checked as a real legal document in the ARCA page https://servicioscf.afip.gob.ar/publico/comprobantes/cae.aspx Forward-Port-Of: odoo/enterprise#126704
The timesheet timer now excludes entries that are not linked to a project when loading timer data. This keeps the timer focused on relevant timesheet records and avoids confusing or incorrect items appearing for users.
Original PR description
exclude AAL without a project when loading systray timer data task: 6538364 Forward-Port-Of: odoo/enterprise#130515
This fix makes Odoo’s PDF tooling expose a common page object consistently across supported PDF library versions. It reduces version-related test failures and helps keep PDF-related maintenance more stable without changing business workflows.
Original PR description
Prior to this commit, `PageObject` was not re-exported by `odoo.tools.pdf`, forcing tests to patch internal module paths like `PyPDF2._page.PageObject`. This resulted in test failures depending on the installed PDF library version: - `pypdf` (>= 3.0.0): `PyPDF2` submodules no longer exist. - `PyPDF2` 1.x: `PageObject` resides in `PyPDF2.pdf` rather than `PyPDF2._page`. To resolve this, this commit re-exports `PageObject` through `odoo.tools.pdf` across all backend wrappers. runbot-946795 Forward-Port-Of: odoo/odoo#286126 Forward-Port-Of: odoo/odoo#285978
The website builder's automated checks were adjusted so they continue to pass with newer Chrome behavior around background image sizing. This helps keep release validation stable without changing what users see or do in the website editor.
Original PR description
In Chrome 152, single-value `background-size` properties may be serialized or expanded to include implicit dimensions (e.g., appending `auto` like `100px auto`), causing strict exact-string test assertions to fail. This commit updates `website` builder test expectations to use regex prefix matching or substring inclusion so tests remain reliable across different Chrome versions. runbot-946570 Forward-Port-Of: odoo/odoo#286094 Forward-Port-Of: odoo/odoo#285591
Inventory users can now relocate stock that is stored in a package already reserved for an order without hitting an access error. This prevents an operational blockage when moving packaged goods between locations while reservations exist.
Original PR description
**Issue** If a move's package is already reserved, relocating that move raises an AccessError. **Steps to reproduce** - Activate the "Packages" feature in Inventory settings. - Activate track…
**Issue** If a move's package is already reserved, relocating that move raises an AccessError. **Steps to reproduce** - Activate the "Packages" feature in Inventory settings. - Activate track localization in settings - Create a new storable product. - Using an inventory adjustment, add some quantity of that product in Stock, in a new package X. - Create a sales order for that product and confirm it, so the quantity in Stock gets reserved. - Go to Inventory > Reporting > Locations. - Select the quant and try to relocate it, e.g. to WH/Input. -> Raises an AccessError: "Failed to write field stock.package.picking_ids" **Cause** Relocating a quant creates a move: https://github.com/odoo/odoo/blob/b9eb72eb1d3be841396cd5dcce827ec88ed9ee31/addons/stock/models/stock_quant.py#L1531 which creates a new move line without a `picking_id`: https://github.com/odoo/odoo/blob/b9eb72eb1d3be841396cd5dcce827ec88ed9ee31/addons/stock/models/stock_quant.py#L1276-L1287 Since both move lines (the new one and the one linked to the SO delivery) share the same `result_package_id`, in `_compute_picking_ids`, both move lines are grouped: https://github.com/odoo/odoo/blob/b9eb72eb1d3be841396cd5dcce827ec88ed9ee31/addons/stock/models/stock_package.py#L174-L176 Thus, two "pickings" end up associated with the package: the SO's, and `None`. While setting those pickings on the package, it tries to access them: https://github.com/odoo/odoo/blob/b9eb72eb1d3be841396cd5dcce827ec88ed9ee31/addons/stock/models/stock_package.py#L182 https://github.com/odoo/odoo/blob/b9eb72eb1d3be841396cd5dcce827ec88ed9ee31/odoo/orm/fields_relational.py#L1497-L1503 And since `self` isn't just `None`, this check won't be skipped: https://github.com/odoo/odoo/blob/b9eb72eb1d3be841396cd5dcce827ec88ed9ee31/odoo/orm/models.py#L4152 This eventually raises an AccessError since `None` gets filtered out by `filtered_domain`: https://github.com/odoo/odoo/blob/b9eb72eb1d3be841396cd5dcce827ec88ed9ee31/odoo/orm/models.py#L4154-L4156 https://github.com/odoo/odoo/blob/b9eb72eb1d3be841396cd5dcce827ec88ed9ee31/odoo/orm/fields_relational.py#L1504-L1505 opw-6427070 Forward-Port-Of: odoo/odoo#283937 Forward-Port-Of: odoo/odoo#282209
Point of Sale now correctly includes product variant extra prices when a discounted pricelist is based on another pricelist. This prevents undercharging at checkout and helps keep product pricing consistent across pricelist changes.
Original PR description
## Steps to reproduce: - Create a product, with never variant, the variant has an extra price of 100 - Make Pricelist 1, just leave it as default - Make Pricelist 2, make it a discount, based on Pricelist 1, for all products - Go to the PoS, click on the created product - Change the pricelist to Pricelist 2 -> the price does not take the extra price into account ## Why the fix: When we have a pricelist based on another pricelist, we recursively calculate the price on the base pricelist. Before this commit, in the recursive call, we gave 0 as the extra price. We now give the extra price in the recursive function call. opw-6500086 Forward-Port-Of: odoo/odoo#286608 Forward-Port-Of: odoo/odoo#285371
Fixed an issue in Discuss where the emoji picker could crash after users selected emojis during a search and then cleared the search field. This keeps chat interactions stable and prevents an unexpected interruption while composing messages.
Original PR description
Steps to reproduce: - open Discuss, open any chat, open the emoji picker (no 'Frequently used' emojis) - search a term and select emojis without closing the picker (shift+click on desktop, plain…
Steps to reproduce:
- open Discuss, open any chat, open the emoji picker (no 'Frequently used'
emojis)
- search a term and select emojis without closing the picker (shift+click on
desktop, plain click on mobile)
- clear the search with backspace
=> traceback: 'Cannot read properties of null (reading
`getBoundingClientRect`)' in adaptNavbar().
This happens because when we clear the search input it calls
`highlightActiveCategory()`, which sets `categoryId` to the topmost category of
the grid, which is now the 'Frequently used' category (sortId 0), added to the
picker since the emojis we just picked updated the recent state. To update the
navbar, `currentNavbarPanel` then looks for the panel holding it in
`emojiNavbarRepr`, but that representation is only built in `adaptNavbar()`,
which runs on mount and from the `ResizeObserver` only, so it was built without
the 'Frequently used' category and no panel contains it. It returns undefined,
the navbar renders empty, its size change wakes the `ResizeObserver`, and
`adaptNavbar()` crashes on querySelector('.o-Emoji').getBoundingClientRect()`.
This commit solves the issue by rendering the `recentEmojis` from a snapshot
taken when the picker is opened, so they are not added to the picker while the
while `emojiNavbarRepr` does not contain their category id.
partial backported PR: https://github.com/odoo/odoo/pull/281104
Task-[6204249](https://www.odoo.com/odoo/project/1519/tasks/6204249)
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#286986
Forward-Port-Of: odoo/odoo#284372This fix ensures Saudi Arabia tax tags and VAT reporting grids are updated automatically when databases upgrade to Odoo 19. It avoids incomplete tax setup after migration and removes the need for manual localization reloads for affected customers.
Original PR description
**Issue:** During migration to Odoo 19, the SA tax tag migration logic is present in: "migrations/2.1/pre-migrate.py" was not executed during the migration of databases because this: * SA compact tax…
**Issue:** During migration to Odoo 19, the SA tax tag migration logic is present in: "migrations/2.1/pre-migrate.py" was not executed during the migration of databases because this: * SA compact tax tags were not renamed * VAT tax grids still use old tags * Localization reload was partially updating taxes * manual intervention was required Added logs/debugging in: * 2.1/pre-migrate.py * 2.1/end-migrate.py * 2.2/end-migrate.py - Verified in both local and customer databases that only the 2.2 migration path was executed during upgrade, while the 2.1 migration scripts were skipped because the Odoo 19 manifest upgrade path already targeted the 2.2 migration version. - 19:https://github.com/odoo/odoo/blob/67510fd36f7af31f83ef110602f9922ed5a43024/addons/l10n_sa/__manifest__.py#L6 **Solution:** - Bumped the l10n_sa module version to 2.3 to apply these changes to databases that are already in production. - Moved `migrations/2.1/pre-migrate.py` to `migrations/2.3/pre-migrate.py` to ensure the SA tax tag migration logic is executed during the `2.3` upgrade flow. - Moved `migrations/2.2/end-migrate.py` to `migrations/2.3/end-migrate.py` to align with the `2.3` version bump and refresh the SA tax mappings correctly during migration. * SA tax tags migrate correctly * VAT tax grids update automatically * invoices use new tags correctly * No manual localization reload required OPW - [6117408](https://www.odoo.com/odoo/project/70/tasks/6117408), [6224788](https://www.odoo.com/odoo/project/70/tasks/6224788) 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#264571
A typo was corrected in the Italian electronic invoicing withholding tax reason. This helps ensure the displayed tax reason is accurate and avoids confusion in Italian localization documents.
Original PR description
Correction of a typo in italian withholding tax reason. opw-6514615 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284992
Nilvera refund documents that arrive as regular invoices are now identified correctly during import. When the matching original invoice is clear, the refund is linked to it, improving accounting accuracy and reducing manual corrections.
Original PR description
# Description of the issue/feature this PR addresses: Nilvera can send refund documents as Invoice with refund-specific InvoiceTypeCode values. # Current behavior before PR: The default UBL import logic only treats an Invoice as a refund when its amount is negative. As a result, Nilvera refund documents sent as Invoice with positive amounts are imported as invoices. Also, imported refunds are not linked to their original invoice. # Desired behavior after PR is merged: Nilvera refund documents using refund-specific InvoiceTypeCode values are imported as refunds. When a unique match is found, the imported refund is linked to its original invoice through reversed_entry_id. task-id-5948275 I confirm I have signed the CLA and read the PR guidelines at [www.odoo.com/submit-pr](http://www.odoo.com/submit-pr) Forward-Port-Of: odoo/odoo#285755 Forward-Port-Of: odoo/odoo#259672
Users can now change analytic details in the bank reconciliation widget even when the related accounting period is locked. This prevents unnecessary lock date errors when only analytic distribution is being updated, making reconciliation edits smoother without changing posted financial amounts.
Original PR description
In the case of the bank rec widget, when modifying a line, we actually unlink it and create a new one. In that case, modifying the analytic would trigger the lock date error. But user should be allowed to modify the analytic all the time. In the edit of the bank rec widget, we add a context key that will be added if analytic distribution is the only key modified. task-6397993 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#277260
Users can now update analytic information in the bank reconciliation widget without being stopped by locked accounting periods. This prevents unnecessary errors when only analytic details are changed, while preserving normal lock-date protections for other edits.
Original PR description
In the case of the bank rec widget, when modifying a line, we actually unlink it and create a new one. In that case, modifying the analytic would trigger the lock date error. But user should be allowed to modify the analytic all the time. In the edit of the bank rec widget, we add a context key that will be added if analytic distribution is the only key modified. task-6397993 Forward-Port-Of: odoo/enterprise#124852
The Send to eTransport action is now shown when Romanian stock transfers are ready as well as when they are completed. This lets users start the required eTransport process at the right operational stage instead of waiting until after completion.
Original PR description
Currently, the Send to eTransport button on `stock.picking` is only visible when picking is done. This PR fixes this behaviour and makes it visible when picking is ready or done both. task-5930984 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285429 Forward-Port-Of: odoo/odoo#257321
Inventory users without Accounting permissions can now view and create Indian E-Waybills without encountering access errors. This helps warehouse teams complete shipping and compliance workflows without needing unnecessary accounting access.
Original PR description
Before this commit Inventory users without Accounting permissions could get an Access Error when viewing or creating an E-Waybill because they could not access the required document types. After this commit Inventory users can now view and create E-Waybills without an Access Error. task-6515093 Forward-Port-Of: odoo/odoo#285023
This fix ensures the editor's color picker correctly recognizes the solid color tab regardless of the user's language. It prevents translated labels from causing incorrect color selection behavior, improving reliability for multilingual users.
Original PR description
### Purpose of this PR: - The color picker tabs are registered with a translated name (`_t(Solid)`), and the tab button renders that name as its only content. ColorUIPlugin read the active button's `innerHTML` and compared it to the literal string Solid to know whether the solid tab was the one in use. - Rely on the `solid-tab` class instead, which is built from the untranslated tab id. task-6441654 Forward-Port-Of: odoo/odoo#279941
Guest checkout customers who enter a valid EU VAT number now have it checked immediately when their address is created. This ensures eligible cross-border EU orders receive the correct 0% intra-community VAT treatment instead of being charged domestic VAT.
Original PR description
# Description **Steps to Reproduce** 1. Install Belgium Localization, then go to **Settings → Accounting/Invoicing**. 2. Enable **Verify VAT Numbers** (`vat_check_vies`). 3. Go to **Accounting →…
# Description **Steps to Reproduce** 1. Install Belgium Localization, then go to **Settings → Accounting/Invoicing**. 2. Enable **Verify VAT Numbers** (`vat_check_vies`). 3. Go to **Accounting → Configuration → Fiscal Positions** and confirm or create an **Intra-Community** fiscal position with: - Detect Automatically (`auto_apply`): enabled - VAT Required (`vat_required`): enabled - Country Group: EU, no specific country configured 4. Open an incognito/private browser window and make sure the session is unauthenticated. 5. Go to the website's `/shop` page and add any product to the cart. 6. Proceed to checkout until reaching the Address step (`/shop/address`). 7. Enter a delivery address in an EU country different from the company's country (e.g. company in Belgium, delivery address in the Netherlands). 8. In the VAT Number field, enter a real, valid, VIES-registered VAT number corresponding to the delivery country (e.g. a valid NL VAT number for a Netherlands address). 9. Click **Save Address / Continue** and proceed to the Payment step (`/shop/payment`). 10. Check the tax applied to the delivery line and the resulting order total. **Issue** The delivery product and the overall order are taxed at the standard/domestic VAT rate instead of the expected 0% intra-community rate — even though the customer provided a valid, VIES-registered EU VAT number matching the delivery country. **Root Cause** In `base_vat`, `res.partner.create()` unconditionally removes `vies_valid` from the ORM's pending computation queue via `env.remove_to_compute()`, relying on a subsequent `write()` to trigger the actual VIES check. This holds for the standard backend flow, where creation is followed by a `write()` — but the website guest checkout flow differs: - `website_sale` creates the guest partner through `_create_new_address()`. - The partner is created via a single `create()` call, with no follow-up `write()`. - `_compute_vies_valid()` is therefore never triggered. - `vies_valid` remains permanently unset (`NULL`), despite a VAT number being provided. Downstream, `account.fiscal.position._get_vat_required_valid()` reads this unset value as falsy, so the Intra-Community fiscal position's `vat_required` condition fails and is rejected in favor of another applicable position (e.g. EU B2C or Domestic). **Solution** After partner creation, explicitly trigger `_compute_vies_valid()` when the partner has a VAT number and the operation is not part of a file import (`import_file` context) — performing the VIES check immediately instead of relying on a `write()` that guest checkout never issues. **Result** Guest customers providing a valid EU VAT number now get `vies_valid` computed immediately at creation. Fiscal position detection correctly identifies the Intra-Community position, and the expected 0% VAT treatment is applied to the delivery and order. OPW: 6522992 Forward-Port-Of: odoo/odoo#286201
This update ensures spreadsheet range selections are clipped to the boundaries of the current sheet. It helps avoid invalid selections and makes spreadsheet behavior more reliable for users working with ranges.
Original PR description
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
Users will no longer see an unnecessary warning when creating a new CRM stage. The warning now appears only when editing an existing stage where changing its status could trigger extra opportunity recalculations.
Original PR description
Changing whether a CRM stage is won may trigger the recomputation of its opportunities. An onchange warning was added to inform users about this potentially expensive operation. However, the warning was also displayed when creating a stage because the onchange was triggered while initializing the form. Fix: Only display the warning when editing an existing stage, as it's useless to show this warning when creating a new stage. Task-6424174 Forward-Port-Of: odoo/odoo#284679