Monday, August 17, 2026
12 changes · 18.0
Enhancements to existing features
Allow resetting sent moves to draft. Ensures a rectificative flow exists or is created. Allow to create an empty rectificative report (if no more invoices to report after being reset to draft). Task: 6273211 Backport of https://github.com/odoo/odoo/commit/801051138621d884ca53324a1befb6de47d83306 This commit also makes minor changes that where done in the 18+ forward ports but not in the 18.0 branch itself (removing 'l10n_fr_pdp_bypass_draft_check' in tests and correcting one comment). F
Original PR description
Allow resetting sent moves to draft. Ensures a rectificative flow exists or is created. Allow to create an empty rectificative report (if no more invoices to report after being reset to draft). Task: 6273211 Backport of https://github.com/odoo/odoo/commit/801051138621d884ca53324a1befb6de47d83306 This commit also makes minor changes that where done in the 18+ forward ports but not in the 18.0 branch itself (removing 'l10n_fr_pdp_bypass_draft_check' in tests and correcting one comment). Forward-Port-Of: odoo/odoo#282260
Resolved issues and error corrections
When an online bank sync finds no new transactions, the reconciliation screen now stays empty instead of showing transactions from all bank journals. This prevents confusion for users managing multiple bank accounts and keeps the experience consistent with newer Odoo versions.
Original PR description
Currently, when we fetch zero transaction for an online account through bank synchronization, we open the bank reconciliation view with an empty domain, thus showing every transactions from every journals. This is confusing for the user if they have several bank journals. This commit now shows an empty bank reconciliation widget if no transactions are fetched. Additionally, this aligns with how it works in Odoo 19. [opw-6384718](https://www.odoo.com/mail/message/1138570272) Forward-Port-Of: odoo/enterprise#127833
Miscellaneous changes
The Sale Details report (report.point_of_sale.report_saledetails, get_sale_details) repeatedly searches account.payment filtered by pos_session_id while building the payments breakdown: it runs one such search per (session x payment method) pair. Since pos_session_id had no index, every one of those searches was a sequential scan over the whole account_payment table, which degrades badly on databases with a large payment history. Adding index='btree_not_null' lets PostgreSQL resolve each lookup
Original PR description
The Sale Details report (report.point_of_sale.report_saledetails, get_sale_details) repeatedly searches account.payment filtered by pos_session_id while building the payments breakdown: it runs one…
The Sale Details report (report.point_of_sale.report_saledetails,
get_sale_details) repeatedly searches account.payment filtered by
pos_session_id while building the payments breakdown: it runs one such
search per (session x payment method) pair. Since pos_session_id had no
index, every one of those searches was a sequential scan over the whole
account_payment table, which degrades badly on databases with a large
payment history.
Adding index='btree_not_null' lets PostgreSQL resolve each lookup with an
index scan. This is the same index already added in saas-18.3 by
e5e3a3f7bf3770fe2bba11b501870d4fb7ef2e51.
Measured on a real customer database (Odoo 18.0), Sale Details report for
one PoS config over a full month:
Dataset: 29 sessions, 1,317 orders, 212,160 account_payment rows.
Per-lookup query plan (EXPLAIN SELECT id FROM account_payment
WHERE pos_session_id = X):
before: Seq Scan on account_payment (cost=0.00..27263.00)
after: Index Scan using account_payment_pos_session_id_index
(cost=0.29..11.84)
get_sale_details, called directly and timed:
before: 144.0 s (of which 1,628 account.payment searches = 139.6 s, 97%)
after: 5.5 s
=> ~26x faster
Methodology: get_sale_details was called directly on the model. The ORM
cache was invalidated (env.invalidate_all()) before the "after" run so the
improvement cannot be attributed to caching. The index creation was the
only change between the two runs (before: index dropped; after: index
created, 0.2 s). The count/time of account.payment searches was captured
by instrumenting Model.search during the "before" run.
This supersedes #276284, which got closed automatically after a bad force-push on the branch and cannot be reopened. As requested there by @pivi-odoo, the field now uses index='btree_not_null', the same diff as e5e3a3f7bf3770fe2bba11b501870d4fb7ef2e51 in saas-18.3.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError: maximum recursion depth exceeded`. **Cause:** - In a previous commit (3e32d7b9eace62dfa7334009707a93967906c726) aimed at fixing stale analytic distribution totals, the assignment loop in `_compute_discount_allocation_needed` was changed from iterating over `self` to `self.move_id.line_ids`. -
Original PR description
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError:…
**Steps to reproduce:** 1. Install Accounting 2. Import a new invoice with more than 1000 lines (xlsx file found in ticket attachments) 3. Test the imported records **Issue:** - `RecursionError: maximum recursion depth exceeded`. **Cause:** - In a previous commit (3e32d7b9eace62dfa7334009707a93967906c726) aimed at fixing stale analytic distribution totals, the assignment loop in `_compute_discount_allocation_needed` was changed from iterating over `self` to `self.move_id.line_ids`. - While this ensured all lines generated updated distribution ratios, it violated the compute logic: assigning values to records outside the current compute batch (`self`). - By executing `line.discount_allocation_dirty = True` on external sibling lines, the method forced the ORM to trigger out-of-band `write()` calls. These writes re-triggered dependency checks (`_field_will_change`), which invoked the compute method again, leading to a recursive loop. **Fix:** 1. Revert the assignment iteration back to `for line in self:`. 2. To preserve the intention of the previous commit (ensuring all lines recompute their shared distribution pool when one line changes), modify the method's `@api.depends` to be `move_id.line_ids.discount` and `move_id.line_ids.analytic_distribution`. By declaring these relational dependencies, modifying a single line now batches all sibling lines into `self` from the start. This allows the lines to synchronize properly without triggering new ORM writes, eliminating the recursion. opw-6451854 Forward-Port-Of: odoo/odoo#282050
### Issue before this commit: When creating a fixed-amount down payment on a sale order containing a fixed tax alongside percentage taxes, the invoiced down payment amount did not match the amount configured by the user. ### Steps to reproduce the issue: 1. Download Sales 2. Create a new tax with Tax Computation as Fixed, amount 8$ and a new tax group name 3. Create a sale order, set 1000$ as price, insert 15% tax and the new tax, confirm it 4. Click 'Create invoice' and create a downpay
Original PR description
### Issue before this commit: When creating a fixed-amount down payment on a sale order containing a fixed tax alongside percentage taxes, the invoiced down payment amount did not match the amount…
### Issue before this commit: When creating a fixed-amount down payment on a sale order containing a fixed tax alongside percentage taxes, the invoiced down payment amount did not match the amount configured by the user. ### Steps to reproduce the issue: 1. Download Sales 2. Create a new tax with Tax Computation as Fixed, amount 8$ and a new tax group name 3. Create a sale order, set 1000$ as price, insert 15% tax and the new tax, confirm it 4. Click 'Create invoice' and create a downpayment with fixed amount of 500$ 5. See that the amount of the downpayment are incorrect. In particular the untaxed amount is 431.78$ instead of 434.78$ and the tax amount is 68.22 instead of 65.22$ ### Cause of the issue: Fixed taxes are not proratable (their amount doesn't scale with price), so the down payment line generation explicitly excludes them when building each down payment line. However, the ratio used to prorate the down payment was computed as self.fixed_amount / order.amount_total, where order.amount_total still included the fixed tax amount. This mismatch meant the fixed tax contributed to the denominator of the ratio but was never represented in the resulting down payment amount, causing the invoiced total to fall short of the requested fixed_amount by a margin proportional to the fixed tax. https://github.com/odoo/odoo/blob/baf2a1ee9d7df408aab8f3b5268a00d1161f9232/addons/sale/wizard/sale_make_invoice_advance.py#L233-L304 ### Reason to introduce the fix: The values inside the downpayement need to match with the ones inserted by the user and of the initial sale order. opw-6410745 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Some features hand a cloud storage download URL to an external service that may fetch it later. The default five-minute lifetime is too short for those flows. ### Steps to reproduce 1. Configure a cloud storage provider (e.g. cloud_storage_google). 2. Upload a large file from the web client, so it is stored in the cloud. 3. Generate a download URL for a consumer that may fetch it after five minutes. 4. The URL expires before the consumer fetches it. ### Cause The Google and Azure
Original PR description
Some features hand a cloud storage download URL to an external service that may fetch it later. The default five-minute lifetime is too short for those flows. ### Steps to reproduce 1. Configure a cloud storage provider (e.g. cloud_storage_google). 2. Upload a large file from the web client, so it is stored in the cloud. 3. Generate a download URL for a consumer that may fetch it after five minutes. 4. The URL expires before the consumer fetches it. ### Cause The Google and Azure providers always use the default download URL lifetime, so callers cannot request a longer-lived URL. ### Fix Read an optional cloud_storage_download_url_time_to_expiry context value when generating a download URL. Keep the existing five-minute lifetime as the default for all current callers. opw-5424132 Related Enterprise PR: odoo/enterprise#105967
**Current behavior before PR:** Archiving a user automatically deletes all activities related to him, without such notification in the UI. This can lead to loss of important data (for example, employee replacement where we want to move activities to the new one). **Desired behavior after PR is merged:** Activities will be archived and can be restored if needed. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Original PR description
**Current behavior before PR:** Archiving a user automatically deletes all activities related to him, without such notification in the UI. This can lead to loss of important data (for example, employee replacement where we want to move activities to the new one). **Desired behavior after PR is merged:** Activities will be archived and can be restored if needed. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
**Steps to reproduce:** * Create a **French** parent company and a branch. * Activate **Electronic Invoicing (PDP)** for the parent company. * Switch to the branch while keeping both the **parent company** and the **branch** selected in the company switcher. * Go to **Settings → French Localization → Activate Electronic Invoicing**. * Activate **Electronic Invoicing (PDP)** for the branch. * Select the **Participate in the pilot phase** checkbox and try to save settings. **Observed beha
Original PR description
**Steps to reproduce:** * Create a **French** parent company and a branch. * Activate **Electronic Invoicing (PDP)** for the parent company. * Switch to the branch while keeping both the **parent…
**Steps to reproduce:**
* Create a **French** parent company and a branch.
* Activate **Electronic Invoicing (PDP)** for the parent company.
* Switch to the branch while keeping both the **parent company** and the **branch** selected in the company switcher.
* Go to **Settings → French Localization → Activate Electronic Invoicing**.
* Activate **Electronic Invoicing (PDP)** for the branch.
* Select the **Participate in the pilot phase** checkbox and try to save settings.
**Observed behavior:**
* A traceback occurs with the error: `psycopg2.errors.SyntaxError: syntax error at or near ")"` on `IN ()` in the SQL query inside `_force_update_l10n_fr_f10_moves`.
**Cause:**
* `_force_update_l10n_fr_f10_moves` searches for receivable/payable accounts using `company_ids IN companies.ids`.
* A branch company has no accounts assigned directly to it — accounts belong to the parent company — so the search returns an empty list.
* Passing an empty tuple to `IN %(account_ids)s` generates `IN ()`, which is invalid PostgreSQL syntax.
**Fix:**
* Replace `('company_ids', 'in', companies.ids)` with
`('company_ids', 'parent_of', companies.ids)` in the account search
inside `_force_update_l10n_fr_f10_moves`.
* This ensures that accounts owned by a parent company are correctly
found when the given companies are branches, since branch companies
inherit their parent's chart of accounts.
opw-6394650`cbc:RoundingAmount` was the sum of `raw_total_excluded` over the non-fixed taxes plus the sum of `raw_tax_amount` over all the taxes, so the base of the line was counted once per non-fixed tax, inflating the line total. `cbc:TaxableAmount` was taken from the tax details of each grouping key, which is the base of that specific tax not the net amount of the line expected by JoFotara. Both amounts are now read from the base line tax details (`raw_total_included` and `raw_total_excluded`), so
Original PR description
`cbc:RoundingAmount` was the sum of `raw_total_excluded` over the non-fixed taxes plus the sum of `raw_tax_amount` over all the taxes, so the base of the line was counted once per non-fixed tax, inflating the line total. `cbc:TaxableAmount` was taken from the tax details of each grouping key, which is the base of that specific tax not the net amount of the line expected by JoFotara. Both amounts are now read from the base line tax details (`raw_total_included` and `raw_total_excluded`), so they describe the line itself regardless of the number of taxes set on it. The document level `cbc:TaxableAmount` keeps using the aggregated tax details. Even tho the problems were hidden because in Jordan, a line wouldn't have more than 1 percent tax + 1 fixed tax, it emerged during the development of the fix in this PR: https://github.com/odoo/odoo/pull/279335
close_db matched readonly connections against the primary DSN. When db_replica_* differs, those connections were left open. 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
Original PR description
close_db matched readonly connections against the primary DSN. When db_replica_* differs, those connections were left open. 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
Error introduced while trying to refactor the code for fix in commit 9c58663fa5cea09f1a930442ee85f718ea3c306b task-None
Original PR description
Error introduced while trying to refactor the code for fix in commit 9c58663fa5cea09f1a930442ee85f718ea3c306b task-None
Calling `/shop/payment/validate` as a portal user with an empty cart confirms the empty sale order. Steps to reproduce: - Sign in as a portal user. - Add a product to the cart. - Remove the product. - Go to `/shop/payment/validate`. - The empty sale order is confirmed. opw-6430637 Forward-Port-Of: odoo/odoo#280924
Original PR description
Calling `/shop/payment/validate` as a portal user with an empty cart confirms the empty sale order. Steps to reproduce: - Sign in as a portal user. - Add a product to the cart. - Remove the product. - Go to `/shop/payment/validate`. - The empty sale order is confirmed. opw-6430637 Forward-Port-Of: odoo/odoo#280924