Tuesday, September 8, 2026
4 changes · saas-19.2
Enhancements to existing features
Dutch SBR and ICP report submissions now go through Odoo's IAP proxy instead of connecting directly to Digipoort. This reduces duplicated technical handling and allows companies to submit using either their own certificate or Odoo's shared 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#117617Opening Point of Sale registers for newly created companies is now much faster in databases with very large accounting histories. The change avoids an inefficient accounting data check, reducing delays from seconds to milliseconds in large multi-company environments.
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 sections with many lines is now much faster and less likely to time out. The change batches the recalculation work that previously happened line by line, reducing waiting time for users working with large sales documents.
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
Installing the Colombian DIAN integration is now faster on large databases. The change avoids a lengthy one-time recalculation during setup while keeping normal invoice processing unchanged afterward.
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#130381 Forward-Port-Of: odoo/enterprise#129507