Friday, August 28, 2026
51 changes · saas-19.1
Security fixes and vulnerability patches
This update makes Odoo consistently enforce access permissions when related records are changed through background context commands. Unauthorized changes are now blocked in the same way as regular field updates, reducing the risk of hidden permission bypasses and improving data protection.
Original PR description
Some commands may perform a change on related records by using Commands. When passed through the context, unallowed actions are ignored instead of rising access errors. This check aligns the behaviour with normal writes of fields. A test in `project` module shows this behaviour. Backport of odoo/odoo#258845 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285132 Forward-Port-Of: odoo/odoo#284501
Self-ordering payment notifications no longer include full order details when updates are sent in real time. This reduces unnecessary data sharing while keeping order status updates working for customers and staff.
Original PR description
Remove the order data from the websocket notification. Forward-Port-Of: odoo/odoo#284631
When a production database is neutralized for testing, staging, or support, saved outgoing mail usernames and passwords are now cleared. This prevents real email relay credentials from being carried into database copies while keeping email sending disabled as before.
Original PR description
### Impacted versions 19.0 (the same applies to every branch shipping `base/data/neutralize.sql`) ### Steps to reproduce 1. On a database with an outgoing mail server configured with a username and…
### Impacted versions 19.0 (the same applies to every branch shipping `base/data/neutralize.sql`) ### Steps to reproduce 1. On a database with an outgoing mail server configured with a username and password, neutralize it (`odoo-bin neutralize`, or restore/duplicate with neutralization enabled). 2. Look at the `ir_mail_server` row. ### Current behavior The server is archived, so the database can no longer send. `smtp_user` and `smtp_pass` are left untouched, so the credentials stay in the database and travel with every dump taken from it. The password still authenticates against the real relay. ### Expected behavior Neutralization is what turns a production database into one that can be copied and handed around — a staging build, a dump downloaded for debugging. A database that is not allowed to send mail has no use for a credential to send it with, and keeping it means the credential leaves the platform with the copy. ### Fix Clear `smtp_user` and `smtp_pass` in the same statement that archives the servers. The stub relay inserted just below still blocks the fallback to the command-line SMTP settings, so behavior is unchanged otherwise. Tested by seeding a mail server with credentials, running the file, and checking the row: archived, credentials empty, stub relay present. Forward-Port-Of: odoo/odoo#284960
The Sign app now checks whether a user can access signature data without automatically using elevated permissions. This helps ensure signature information is only accessed through the intended permissions path, while still allowing authorized elevated access when explicitly needed.
Original PR description
If the signature is needed with sudo, the user can read directly sign_signature_data.
Enhancements to existing features
Settling sales orders in Point of Sale is now faster, especially for large orders. The update reduces repeated background calls and screen refreshes, so staff should experience less waiting and smoother checkout when converting sales orders.
Original PR description
In `settleSO`, two per-line bottlenecks were addressed: 1. `has_valued_move_ids` was called once per order line via a separate RPC, causing N sequential HTTP round-trips. It is now computed server-side inside `read_converted`, which is already called once for all lines. 2. `addLineToCurrentOrder` was awaited per line, yielding to the event loop each iteration and triggering a full Owl re-render for every line. Lines are now created directly, batching all mutations into a single render. `recomputeOrderData()` is called once after the loop. `updatePrograms` is moved to a `pos_sale_loyalty` patch on `settleSO` so the loyalty concern belongs to the bridge module. opw-6319922 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282571 Forward-Port-Of: odoo/odoo#271742
Resolved issues and error corrections
This fix narrows how document-related messages are found so the system only retrieves messages for the intended item type. It helps prevent unrelated mailings from being shown or processed by mistake, improving reliability in Documents.
Original PR description
Description of the issue/feature this PR addresses: Updates the `_get_mail_messages` domain filter to include the target model, preventing accidental retrieval of incorrect mailings. runbot-242254
Accounting now uses a more suitable database index when finding the latest or earliest journal entry by date. This can dramatically reduce wait times in affected accounting screens, turning a previously very slow read operation into a near-instant one in the reported case.
Original PR description
This commit adds an index on `(journal_id, date)` to speed up `_get_last_sequence_domain`. The slow part is `.search(domain, order='date [desc|asc]', limit=1)` Because of the order by date,…
This commit adds an index on `(journal_id, date)` to speed up `_get_last_sequence_domain`. The slow part is `.search(domain, order='date [desc|asc]', limit=1)` Because of the order by date, postgresql scans the index on `date`, assuming a row will be quickly matched. If this assumption is wrong though, it'll scan a large part of the index or all of it, which is slow. This is the case with account move 17102258 on odoo.com at the time of writing this. - before - 1st query https://explain.dalibo.com/plan/6b8ff62b1572ah1a - 2nd query https://explain.dalibo.com/plan/41243cgccg5798gb - after - 1st query https://explain.dalibo.com/plan/4fd2c7ed28g7b138 - 2nd query https://explain.dalibo.com/plan/2heh6c6965c88h71 - ~~1st query https://explain.dalibo.com/plan/f874a31f4b07hdeb~~ - ~~2nd query https://explain.dalibo.com/plan/e8h09725gfh4c2e9~~ full `web_read` - before ~1min 10s - after ~650ms task-6481393 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283159
The French e-invoicing PDP pilot phase option has been removed because the deadline to use it has passed. This simplifies the registration and settings screens so companies only see the current process going forward.
Original PR description
The pilot phase was there if people wanted to send before the deadline. The deadline has been reached, so we can remove the field from the view. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285268 Forward-Port-Of: odoo/odoo#284186
Companies sharing the same French SIREN in one database can now reuse a successful registration verification from another company. This reduces repeated KYC work for groups with many branches and speeds up onboarding to the French PDP e-invoicing process.
Original PR description
We have some clients that have several hundreds of companies/branches on the same db, with the same SIREN (incubateur or the like). They will need to do the kyc (that will be identical, as it's the same SIREN) for all the companies. It's especially cumbersome if it needs manual intervention So, if one company on that database, with the same SIREN, managed to register, then it means it has succeded the kyc. Meaning we can bypass the kyc for the other identifiers as well. task-6515315 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285236
Mail test checks that were waiting for interface text or field values now retry regularly instead of waiting until a long timeout. This reduces unnecessary delays in the mail test suite without changing customer-facing behavior.
Original PR description
Before this commit, a contains that does not match right away runs again only when its MutationObserver fires, and once more at the 10 seconds timeout. The problem is that the observer reports neither a text node updated in place nor an input value or checked property, so a check waiting for one of those sleeps 10 seconds and then passes: "Delete starred message decrements starred counter once" spends 10.2s of the 183s @mail suite waiting for a counter to go from "Starred3" to "Starred2". This commit turns that single timeout into a 500ms tick up to the same deadline, so that such a check costs 500ms. The tick uses the unmocked timer to stay out of the timer graph a test drives with runAllTimers, and only 32 of the suite's 4239 contains calls stay pending long enough to tick once. Forward-Port-Of: odoo/odoo#285129 Forward-Port-Of: odoo/odoo#284944
Deleting accounts is now faster in accounting and point of sale because related payment and analytic records can be checked more efficiently. This reduces delays for users or administrators cleaning up account records, especially in databases with a lot of transaction history.
Original PR description
Without these indexes, the foreign key check when deleting an account can take a long time. Forward-Port-Of: odoo/odoo#284780
Deleting accounts in the German reporting module is now faster in cases with many related accounting entries. This reduces waiting time and helps keep account maintenance smoother for users.
Original PR description
Without these indexes, the foreign key check when deleting an account can take a long time. Forward-Port-Of: odoo/enterprise#129403
New UAE-specific overtime work entry types make it easier to calculate overtime pay accurately. This helps payroll teams use clearer overtime categories for weekday, night, and day-off work when configuring salary rules.
Original PR description
Add UAE overtime work entry types (OVTWD, OVTWDN, OVTOD) to simplify overtime salary rule calculations using `worked_days['<code>'].amount`. Task: 6469393 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282898
Large point-of-sale sessions with many invoiced bank payments now close much faster by processing accounting work in batches. This reduces the risk of session closing timing out for businesses with high transaction volumes.
Original PR description
Steps to reproduce ------------------ 1. On a bank payment method, enable "Identify Customer". 2. Create a lot of orders paid with this method and invoice them (the customer reporting the issue had…
Steps to reproduce ------------------ 1. On a bank payment method, enable "Identify Customer". 2. Create a lot of orders paid with this method and invoice them (the customer reporting the issue had 2081 orders). 3. Close the session. The close takes several minutes, and on a remote database the request is killed by the worker time limit before it completes. Cause ----- `_reconcile_account_move_lines` reconciles each group of lines with its own `reconcile()` call, one per payment. Every call creates its partials and triggers the recompute cascade of the ORM, which searches `account.move.line` over the whole set of payments of the session, so the cost of a single call grows with the number of payments. `_create_invoice_receivable_lines` has the same shape: the values are grouped per payment, so every group holds a single value and the lines are created one `create()` call at a time. opw-6242303 already batched the creation and the posting of the split bank payments, and the reconciliation of their receivable lines, but only for the orders that are not invoiced. Invoiced orders go through `split_inv_payment_receivable_lines`, which was left untouched. Fix --- Gather every reconciliation of the session into a single `_reconcile_plan` call. The entries of the plan are processed independently and in order, so the result is the same as reconciling them one by one, but the recompute cascade runs once instead of once per payment. Create all the invoice receivable lines in a single `create()` call and dispatch the records back to their payment method or payment afterwards. Benchmark --------- Closing a session of 2081 invoiced orders on a copy of the customer database: - before: 558s - after: 54s opw-6458271 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284783 Forward-Port-Of: odoo/odoo#281495
Pay later receivables in POS settlement are now reconciled in one grouped operation instead of repeated partner-by-partner processing. This keeps the accounting result unchanged while reducing system workload and improving performance for businesses with many pay later transactions.
Original PR description
The pay later receivable lines are reconciled with one `reconcile()` call per partner, and each call filters the whole set of lines again. Reconcile them in a single `_reconcile_plan` call grouped by partner instead: the result is the same, but the recompute cascade of the ORM runs once instead of once per partner. opw-6458271 Related: https://github.com/odoo/odoo/pull/281495 Forward-Port-Of: odoo/enterprise#129406 Forward-Port-Of: odoo/enterprise#127415
UAE payroll now includes updated overtime pay rules aligned with a newer Odoo version. This helps ensure overtime amounts are calculated more directly and consistently for working days, nights, and days off.
Original PR description
Backport overtime salary rules (OVTWD, OVTWDN, OVTOD) from 19.4 to 19.0, updating the computation logic to directly use `worked_days['<CODE>'].amount`. Task:6469393 Forward-Port-Of: odoo/enterprise#127911
Tax returns will no longer be incorrectly marked as paid when users reply to or send normal chatter messages. Payment finalization now only occurs when sending messages from the tax payment instructions flow, preventing accidental status changes.
Original PR description
Before this fix: Replying to or sending a message from the chatter of a tax return could incorrectly change its state to Paid. This happened because action_send_mail() automatically called _action_finalize_payment() for account.return records. After this fix: Payment finalization only happens when the composer is opened from the tax payment instructions flow. Normal chatter messages and replies will no longer change the tax return state to Paid. task-6469536 Forward-Port-Of: odoo/enterprise#129161
PDF Quotes created with Quote Builder could fail when the selected document included dynamic fields. This fix preserves the needed PDF form settings so quotes can be generated reliably for affected sales documents.
Original PR description
Issue: --- Due to this issue, generating PDF Quote using Quote Builder with dynamic fields leads to a traceback. This was partially fixed by: e16edc7b0d9f56cb7769068ecf7d64ff8b0f6359 Steps: --- 1-…
Issue: --- Due to this issue, generating PDF Quote using Quote Builder with dynamic fields leads to a traceback. This was partially fixed by: e16edc7b0d9f56cb7769068ecf7d64ff8b0f6359 Steps: --- 1- Using a python 3.13 env, install requirements.txt. (You could instead uninstall pypdf2 and install pypdf==5.4.0) 2- Enable Quote Builder. 3- Create a SO and in quote builder tab, select a document. This document should have dynamic fields. e.g. you could use`Office Furnitures Header` document. 4- Print -> PDF Quote. Cause: --- In previous fix, we fixed the traceback when no dynamic field is set. However, if you have a dynamic field, then inside `PdfWriter._update_field_annotation()`, the font is get from `DR` dict inside acroform: https://github.com/py-pdf/pypdf/blob/f20954f2241640feb484800e191373f8fbdfa44b/pypdf/_writer.py#L917-L929 Even if we are not setting a font, we need to have an empty `DR` dict inside acroform, in order to avoid calling `get` on a none object, which is leading to the traceback. Also in previous fix we were losing `NeedAppearances` inside `AcroForm` by creating new dict which wasn't right. opw-6392001 Forward-Port-Of: odoo/odoo#278881
Fixes an issue where event registrations were not recreated when a cancelled sales order was reset to quotation and confirmed again. This ensures customers who reconfirm event-related orders get the correct registrations created automatically, including portal confirmations or cases where the registration form is closed.
Original PR description
**Steps to reproduce:** - Create a SO and add the Event Registration - Standard product and specify an event - Confirm the SO then select "Create/Update registrations" - Cancel the SO, then "Set to…
**Steps to reproduce:** - Create a SO and add the Event Registration - Standard product and specify an event - Confirm the SO then select "Create/Update registrations" - Cancel the SO, then "Set to Quotation" - Select "Preview" and confirm the Sale Order again - There will not be any new registration created when there should be one. **Behavior:** Usually when a sale order is confirmed the `action_sale_order_event_registration` form will be opened which when filled correctly creates registrations. However in certain cases: confirming from the customer portal, or simply closing the form when it is opened, will not trigger `action_make_registration` which creates registrations if it is not already the case `action_confirm()` should be creating the registrations correctly on its own anyway by calling ´init_registrations()´ : https://github.com/odoo/odoo/blob/beed378cde592bc96c1e79a976ac775264b843ed/addons/event_sale/models/sale_order_line.py#L49-L66 This function tries to create each missing registrations by looking at the amount in the so_line and deducting the already created registrations, however since some of them can be cancelled, this computation is wrong. And leads to registration not being created when they should. opw-6444127 Forward-Port-Of: odoo/odoo#280426
Fixes an issue where archiving or deleting one user could remove a shared partner from restricted discussion channels, even when another active user for the same partner still qualified for access. Partners are now only unsubscribed when no remaining associated user has the required group access, preventing unnecessary loss of channel membership.
Original PR description
Before this commit, archiving or deleting a user removed its partner from every group restricted channel, even when another user of that partner was still active and in the group the channel requires. This happens because the members to unsubscribe are searched on partner_id alone, so the search cannot tell whether the partner keeps another user. This commit fixes the issue by unsubscribing a partner only when none of its remaining users has the group the channel requires. Forward-Port-Of: odoo/odoo#283933 Forward-Port-Of: odoo/odoo#283807
Fixed a formatting issue in the inventory report PDF where location rows did not align with newly added columns. This restores missing gridlines and borders, making printed inventory counts clearer and more professional.
Original PR description
When new columns were added to the stock inventory report, the location grouping row was not updated. This results in mismatched column counts, causing missing gridlines and broken borders in the PDF output Fixed by ensuring the location row's column count matches the header <img width="603" height="200" alt="image" src="https://github.com/user-attachments/assets/86872bee-f315-4bdd-b3f2-a525e3bb5fe0" /> ### Steps to reproduce: - Ensure warehouses are activated in the settings - Go to Barcode -> Count Inventory - Add a Product - Select the gear Icon then "Print Inventory" - You will notice that the location row has missing gridlines opw-6307728 Forward-Port-Of: odoo/odoo#275918
Automatic planning in Project Forecast now handles cases where several roles are involved more reliably. This prevents scheduling issues and helps teams get more accurate forecast plans without manual correction.
Original PR description
task-6484707 Forward-Port-Of: odoo/enterprise#128541
This fixes a Windows-specific issue where static file paths could be split incorrectly after path normalization. It helps ensure Odoo can reliably locate and serve static resources on Windows environments.
Original PR description
In commit 31aad6c, path normalization was added which also resulted in `/` being converted into `\` on Windows. There the `path.split('/')` did not work.
This commit changes the `'/'` to `os.sep` to fix the issue.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#285216This fix prevents duplicate calendar entries when an Outlook event that was already synced is changed into a recurring meeting in Outlook. The system now removes the old single event because it is represented by the new recurring series, keeping calendars cleaner and more accurate.
Original PR description
When a single event already synced with Outlook is turned into a recurring event directly in Outlook, Microsoft reuses the event in place: the seriesMaster keeps the iCalUId of the former single event, while each occurrence gets a fresh id/iCalUId. On resync, a seriesMaster is only matched against calendar.recurrence, never against calendar.event. As no recurrence exists yet, it is handled as a brand new recurrence whose occurrences are created from scratch. The original single event, which shares the master's iCalUId and the first occurrence's timeslot, is then left untouched. Drop that pre-existing single event when building a new recurrence from an inbound seriesMaster, since it is now represented by the recurrence itself. opw-5129848 Forward-Port-Of: odoo/odoo#285106 Forward-Port-Of: odoo/odoo#271787
This update corrects how user groups are handled in the Sign app. It helps ensure users receive the right access behavior when working with signing features, reducing confusion or incorrect permissions.
Original PR description
opw-6518598
Fixed an issue where the Dimona button or badge could disappear after autosaving an employee record, preventing users from sending the required declaration. The payroll fields now keep their correct server-calculated status, and a test was added to prevent the issue from returning.
Original PR description
Steps to reproduce: - Create an employee, fill in name, CP, contract start date, wage. - Leave the form without saving manually (e.g. open another record). - Select the employee again: the Dimona button/badge is gone and stays gone regardless of further edits. l10n_be_needs_dimona_in and l10n_be_dimona_next_action are server-computed but readonly=False let autosave submit a stale value, permanently blocking the recompute. Drop the stray readonly=False on both and add a regression test. Task 6515877
This fix ensures Uruguayan electronic invoicing uses the exchange rate saved on the invoice, rather than recalculating it from current currency rates. This prevents credit notes and invoice references from showing mismatched rates when exchange rates are updated after posting.
Original PR description
18.0 introduced an `invoice_currency_rate` field, storing the currency rate used for each invoice. Currently `_l10n_uy_edi_get_used_rate` uses the `currency.convert()` method using the date and company of the invoice. This is vulnerable to inaccuracy if the currency rates are changed after the invoice posting. For example, if an invoiceis posted and the currency rates are updated, the invoice will use the old rate, while `_l10n_uy_edi_get_used_rate` will return the new one. In the case of the customer in the related ticket, this caused their credit note reference currency rate to mismatch with the one on the invoice. This PR changes the `currency.convert()` call to fetching and inverting the `invoice_currency_rate` field. This ensures the returned value will match the one used for the invoice. opw-6456867 Forward-Port-Of: odoo/enterprise#128190
Appraisals now keep the template chosen by the user when they are confirmed or reset, instead of switching back to the first generic template. This makes appraisal records easier to find with template filters and prevents confusion from unexpected template changes.
Original PR description
Issue: When a user selects a generic appraisal template other than the first one, confirming or resetting the appraisal silently replaces that selection with the first generic template. Filtering…
Issue: When a user selects a generic appraisal template other than the first one, confirming or resetting the appraisal silently replaces that selection with the first generic template. Filtering appraisals by the originally selected template then fails to return the appraisal. Steps to reproduce: * Configure multiple appraisal templates without department restrictions. * Create an appraisal and select a template other than the first one. * Confirm the appraisal. * Filter appraisals by the selected template. Cause: `_compute_appraisal_template()` only preserved templates directly linked to the appraisal's department. Generic templates have no department relation, so a valid selected template was discarded whenever the computation was triggered by a state dependent department recomputation. The generic fallback then stored the first template instead. https://github.com/odoo/enterprise/blob/5c57ccbb13269af28de0a6c7f35454be52424f43/hr_appraisal/models/hr_appraisal.py#L195-L209 Solution: We need to distinguish the generic default loaded on an unsaved form from a compatible generic template already stored on an appraisal. Preserve the latter across recomputations while retaining department template priority during creation and rejecting templates that no longer match the appraisal's department or company. opw-6449041 Forward-Port-Of: odoo/enterprise#127566
Fixes the Mexican chart of accounts so accumulated depreciation and amortization accounts are classified correctly. This ensures asset depreciation calculations create journal entries with proper values, helping Mexican companies keep accurate asset balances and financial records.
Original PR description
Issue: When creating an asset using an "154.01.01 Vehicles", the depreciation values of the journal entries created are zero Steps to reproduce: 1. Install l10n_mx and enter a Mexican company 2.…
Issue: When creating an asset using an "154.01.01 Vehicles", the depreciation values of the journal entries created are zero Steps to reproduce: 1. Install l10n_mx and enter a Mexican company 2. Create an asset and setting the fixed asset account as 154.01.01 Vehicles 3. Click on "Compute Depreciation" and in the "Depreciation Board" page, all the journal entries created will not have any depreciation values Cause: In the l10n_mx chart of accounts, all accumulated depreciation accounts are set as "expense_depreciation" whereas they should be asset accounts because accumulated depreciation accounts are used to credit asset accounts to decrease asset values. If an account of type "expense_depreciation" is used, then when the depreciation account is credited and the expense account is debited, all financial events will occur within expense accounts, which is why no depreciation values were recorded as the asset balances stay the same. Additionally, the "asset_depreciation_account_id" field on "account.account" has a domain restricting selection to "account_type" of "asset_fixed" or "asset_non_current" Solution: Change the "account_type" of l10n_mx accumulated depreciation accounts from "expense_depreciation" to "asset_fixed" and l10n_mx accumulated amortization accounts from "expense_depreciation" to "asset_non_current" Updrade PR: [odoo/upgrade/pull#10936](https://github.com/odoo/upgrade/pull/10936) opw-6359618 Forward-Port-Of: odoo/odoo#278185
Users can now click read-only links in the HTML editor to open the link popover and inspect the destination. This fixes a frustrating issue where non-editable links appeared unresponsive, improving confidence when reviewing website or article content.
Original PR description
Problem: Clicking a non-editable link does nothing, making it impossible to open or inspect the link. Solution: Allow the link popover to open in read-only mode for non-editable links. Steps to reproduce: - Run `/article`. - Click on the inserted article link. - Observe that nothing happens. opw-6442026 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283473 Forward-Port-Of: odoo/odoo#280224
Customers can no longer set optional products on sales orders to negative quantities through the portal. This keeps customer-facing order changes consistent and prevents invalid quantities that should only be handled by sales staff when needed.
Original PR description
Since the fusion of `sale.order.option` model into `sale.order.line` model, the optional products (editable from portal) lines are not deleted when reaching a quantity of 0 or below. This could allow some customers to set negative quantities, which makes no sense as it's only something that should be set by the salesman if necessary. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284493
Fixes a billing issue where timesheets could lose their invoice link after an invoice was reversed and replaced through a credit note. This ensures replacement invoices correctly reference the related timesheets, improving billing traceability and reducing manual follow-up.
Original PR description
### Description of the issue/feature this PR addresses: Fixes an issue where timesheets lose their invoice reference when reversing and re-creating an invoice via a credit note. ### Current behavior…
### Description of the issue/feature this PR addresses: Fixes an issue where timesheets lose their invoice reference when reversing and re-creating an invoice via a credit note. ### Current behavior before PR: When reversing an invoice tied to timesheets and creating a replacement via a credit note, the timesheets linked to the original invoice have their timesheet_invoice_id cleared. Because the modify_moves function builds the replacement invoice directly via copy_data()/create(), it bypasses the normal sale order invoicing flow. As a result, the unbilled timesheets are left permanently unlinked from the newly created invoice, leaving the new invoice with no reference to the timesheets linked to the original sale order. ### Desired behavior after PR is merged: When a replacement invoice is created, each timesheet is properly relinked to the corresponding line on the new invoice. This linkage matches on the sale order line (so_line) rather than line position, ensuring accuracy since line order and count are not guaranteed to be preserved between the original and modified invoices. opw-6449995 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283104
This fix prevents manually adjusted prices on optional quotation lines from being overwritten when customers change quantities in the portal preview. Sales teams can rely on negotiated or custom prices being preserved while still allowing standard pricelist behavior where no manual price was set.
Original PR description
When a user manually sets a price on an optional line (overriding the pricelist), and then changes the quantity in the portal preview, the manual price is lost and gets reset to the pricelist price.…
When a user manually sets a price on an optional line (overriding the pricelist), and then changes the quantity in the portal preview, the manual price is lost and gets reset to the pricelist price. Steps to reproduce: --- - Install Sales module and enable Pricelists. - Create a product with qty-based pricelist rules: - min qty: 1 → price: 100 - min qty: 10 → price: 80 - Create a quotation with an optional section containing this product. - Manually change the product price to 150 (overriding pricelist). - Mark the section as optional and preview the quotation. - Change the quantity to 10 in portal preview. Issue: --- - The manually set price (150) is incorrectly reset to the pricelist price (80). Root cause: --- - After [commit], if there is no config parameter set and if there is an active pricelist, we simply call `_reset_price_unit()` without checking whether the price was manually set or not. - Additionally, `_reset_price_unit()` calls `update()` which writes `price_unit` and `technical_price_unit` one by one as separate `write()` calls. The `write()` method has a guard([1]) that strips a lone `technical_price_unit` write unless `sale_write_from_compute` is set in context. Without this flag, `technical_price_unit` is silently discarded, causing it to drift from `price_unit`. On the next qty change, this mismatch is detected as a manual price, permanently blocking further pricelist updates. Solution: --- - Check whether the price was manually set before calling `_reset_price_unit()`, and pass `sale_write_from_compute=True` in context so both `price_unit` and `technical_price_unit` are written correctly. [commit]: https://github.com/odoo/odoo/commit/93b6bdd6a4909bc0b45b90ab6a2d0734a218292d [1]https://github.com/odoo/odoo/blob/fffd987cc98d1ea0cd04e24dda2ed8b64a219cdc/addons/sale/models/sale_order_line.py#L1391-L1399 opw-6426634 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281107
This fix prevents invoice printing from failing when an Argentine company partner has a VAT number that is valid in another country but not as an Argentine CUIT. It helps users complete invoice printing reliably instead of encountering an error message.
Original PR description
When printing an invoice, a traceback will occur if the company's partner has an invalid CUIT. Steps to reproduce the error: - Install ``l10n_ar_edi`` module with demo data - Switch to ``(AR)…
When printing an invoice, a traceback will occur if the company's partner has an invalid CUIT. Steps to reproduce the error: - Install ``l10n_ar_edi`` module with demo data - Switch to ``(AR) Exento`` Company - Go to Invoicing > Configuration > Journals > Open ``Ventas Preimpreso`` journal > ARCA POS System: ``Electronic Invoice - Web Service`` > Save - Create a new invoice with ``ADHOC SA`` partner > Confirm the invoice - Open the ``(AR) Exento`` partner and set the VAT to ``BE0477472701`` - Open the Invoice > print Traceback: ```py ValueError: invalid literal for int() with base 10: 'BE0477472701' ``` After this [commit], companies outside the EU can use European VAT numbers. Consequently, an Argentine partner can have a CUIT number such as ``BE0477472701``, which is valid as a Belgian VAT number but not as a CUIT. When printing the invoice, the ``l10n_ar_vat`` field is computed from the partner's VAT and its value is passed to ``int()``, causing a traceback at the following line: https://github.com/odoo/enterprise/blob/d55486866d09f8aa87c2003dab722cfa323068b4/l10n_ar_edi/models/account_move.py#L138 [commit]: https://github.com/odoo/odoo/commit/a2afe3292e1cd0a4f339dc47707e469653d13ea0 Enterprise PR: https://github.com/odoo/enterprise/pull/127820 sentry-7666042143 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282454
Invoice printing in Argentina localization now avoids crashing when a company partner has an invalid CUIT tax identifier. This helps users continue their invoicing workflow and receive a proper validation outcome instead of an unexpected error.
Original PR description
When printing an invoice, a traceback will occur if the company's partner has an invalid CUIT. Steps to reproduce the error: - Install ``l10n_ar_edi`` module with demo data - Switch to ``(AR)…
When printing an invoice, a traceback will occur if the company's partner has an invalid CUIT. Steps to reproduce the error: - Install ``l10n_ar_edi`` module with demo data - Switch to ``(AR) Exento`` Company - Go to Invoicing > Configuration > Journals > Open ``Ventas Preimpreso`` journal > ARCA POS System: ``Electronic Invoice - Web Service`` > Save - Create a new invoice with ``ADHOC SA`` partner > Confirm the invoice - Open the ``(AR) Exento`` partner and set the VAT to ``BE0477472701`` - Open the Invoice > print Traceback: ```py ValueError: invalid literal for int() with base 10: 'BE0477472701' ``` The issue occurs because when the partner's identification type is CUIT, At [1] ``_run_check_identification()`` method does not include partners whose identification type has ``is_vat=True``. As a result, CUIT is not validated by ``_run_check_identification()`` method in ``l10n_ar`` module at [2]. So, partner's ``l10n_ar_vat`` field can be computed as ``BE0477472701``. Passing this value to ``int()`` raises the traceback during invoice printing at below line. https://github.com/odoo/enterprise/blob/d55486866d09f8aa87c2003dab722cfa323068b4/l10n_ar_edi/models/account_move.py#L138 [1]:https://github.com/odoo/odoo/blob/c2a39085ba0fbcf8a0e6a55228191e764499caea/addons/l10n_latam_base/models/res_partner.py#L24-L30 [2]:https://github.com/odoo/odoo/blob/c2a39085ba0fbcf8a0e6a55228191e764499caea/addons/l10n_ar/models/res_partner.py#L55-L65 Community PR: https://github.com/odoo/odoo/pull/282454 sentry-7666042143 Forward-Port-Of: odoo/enterprise#127820
This fixes cases where the same user group has more than one internal identifier, but permission checks only recognized one of them. Users and administrators should now get consistent access results regardless of which valid identifier is used.
Original PR description
A group can be identified by multiple xmlids. We add support to provide a list of "refs" to the `SetDefintions` object.
Reproductible issue:
```
demo = self.env["res.users"].browse(5)
demo.has_group("accountant.group_account_user") # False
demo.has_group("account.group_account_user") # True
assert self.env.ref("accountant.group_account_user") == self.env.ref("account.group_account_user")
```
task-6471260
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#284866Belgian accounting users without Analytic Accounting access can now import SODA XML files without seeing an access error. This keeps payroll-related accounting imports working even when Analytic Accounting is not enabled for the user or company.
Original PR description
**Description of the issue/feature this PR addresses:** When importing a SODA XML file, users without the Analytic Accounting group encounter an access rights error even if Analytic Accounting is not…
**Description of the issue/feature this PR addresses:** When importing a SODA XML file, users without the Analytic Accounting group encounter an access rights error even if Analytic Accounting is not enabled. This occurs because the import wizard reads the `analytic_account_id` field on the `soda.analytic.mapping` model to build an internal dictionary of departments. Because this field is restricted to the Analytic Accounting group, the evaluation of this field crashes the import for users even when the Analytic Accounting feature is disabled. This commit resolves the issue by using `.sudo()` on the analytic mapping recordset to bypass the field-level group restriction. **Steps to reproduce:** - Log in as Mitchell Admin, change company to “My Belgian Company” - Settings > Users & Companies > Users > Mitchell Admin > Access Rights > Extra Rights > ensure “Analytic Accounting” is unchecked - Also ensure Mitchell Admin is not part of the “Analytic Accounting” group - Accounting Dashboard > remove “Favorites” from filter > drag & drop a SODA XML file to “Miscellaneous Operations” > save > observe Access Error **Current behavior before PR:** - Users who don't belong to the Analytic Accounting group encounter an access error when attempting to import SODA XML files, even when the Analytic Accounting feature isn't enabled **Desired behavior after PR is merged:** - Those users no longer receive an access error opw-6376039
Changing a project's visibility no longer fails when the project folder contains shortcuts to other documents. This ensures project access settings can be updated reliably while still preventing direct permission changes on shortcut-only documents.
Original PR description
Changing a project's visibility fails when its documents folder contains a shortcut. The visibility change is never applied and the following error is raised: "You can not update the access of a…
Changing a project's visibility fails when its documents folder contains a shortcut. The visibility change is never applied and the following error is raised: "You can not update the access of a shortcut, update its target instead." ### Reproduction steps - Create a project and add a document to its folder. - Create another document outside the project's folder. - Create a shortcut to that document in the project's folder. - Change the project's visibility. ### Cause Changing a project's visibility updates the access rights of its folder and documents together. The shortcut access check is meant to reject operations performed only on shortcuts. However, reading `shortcut_document_id` on a recordset returns the shortcut targets found across that recordset. Therefore, the presence of a single shortcut makes the check reject the whole operation. This prevents regular documents and the project folder from having their access updated. ### Fix Only reject access updates when all records involved are shortcuts. This preserves the protection against changing shortcut access directly while allowing project access updates to include shortcuts alongside regular documents and folders. opw-6472637 Forward-Port-Of: odoo/enterprise#129448 Forward-Port-Of: odoo/enterprise#128756
This fix prevents subscription billing from crashing when an automatic payment fails and its temporary transaction record is rolled back. It helps recurring invoice processing continue reliably and avoids unnecessary disruption for customers using saved payment methods.
Original PR description
Step to reproduce: - create a faulty token that won't work and link it to a subscription - launch the recurring invoice cron - the following traceback occurs ``` last_tx_sudo = (self.transaction_ids…
Step to reproduce:
- create a faulty token that won't work and link it to a subscription
- launch the recurring invoice cron
- the following traceback occurs
```
last_tx_sudo = (self.transaction_ids - existing_transactions).sudo()
```
When the payment fails, the system rollback and we store the last_tx_sudo value in a dedicated variable. After rollback, the record does not exists anymore. Therefore, accessing the value fails.
```
File "/home/odoo/src/enterprise/saas-18.2/sale_subscription/models/sale_order.py", line 1703, in _handle_automatic_invoices
if not last_tx_sudo or last_tx_sudo.renewal_state in ['pending', 'authorized']:
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 1439, in __get__
self.compute_value(record)
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 1603, in compute_value
records._compute_field_value(self)
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/models.py", line 4575, in _compute_field_value
determine(field.compute, self)
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 69, in determine
return needle(*args)
^^^^^^^^^^^^^
File "/home/odoo/src/enterprise/saas-18.2/sale_subscription/models/payment_transaction.py", line 25, in _compute_renewal_state
if tx.state in ['draft', 'pending']:
^^^^^^^^
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 1406, in __get__
raise MissingError("\n".join([
odoo.exceptions.MissingError: Record does not exist or has been deleted.
```
Moreover, since https://github.com/odoo/enterprise/pull/45236/files#diff-c36fd7952cc2bef40716419a668de41963d49e1aa4177d9319d503fc260da588R1678-R1682
```
if not last_tx_sudo or not last_tx_sudo.renewal_state not in ['pending', 'authorized']:
```
has become
```
if not last_tx_sudo or last_tx_sudo.renewal_state in ['pending', 'authorized']:
```
But it feels strange to unlink the invoice when the payment succeed.
This PR fixes it.
Forward-Port-Of: odoo/enterprise#83913Closed or renewed subscriptions no longer stay listed in Orders to Invoice just because prepaid recurring lines were still marked for invoicing. This prevents misleading billing work while still allowing already delivered postpaid items to be invoiced after closure.
Original PR description
When a closed subscription could still show up in the Orders to Invoice because its recurring lines kept their "to invoice" status. This was misleading since no further period should be billed. When a subscription is churned (or renewed), prepaid lines are now flagged as nothing to invoice. Postpaid lines are left as is so that already delivered products can still be billed after closing. task-6227787 Forward-Port-Of: odoo/enterprise#117797
The accounting report for invoiced but not delivered sales now excludes delivery fee lines. This prevents non-deliverable shipping charges from appearing as outstanding delivery items, making the report more accurate for finance review.
Original PR description
Issue: --- Delivery lines are included in `invoiced not delivered` report, which is wrong as delivery lines are not deliverable. Steps: 1- Create a SO with a good product and add a delivery line. Set the product line as delivered and create an invoice. 2- Open accounting, and from review tab, open `Invoiced not Delivered`. As you see, delivery lines are included in the report. Fix: --- On stable we could fix it inside `_get_accrual_domain` by checking if `delivery` is installed. On master we need to implement a solution to be able to differentiate the lines that won't be delivered. opw-6360894 Forward-Port-Of: odoo/enterprise#123517
Financial reports now hide the unallocated earnings or losses line when it has a zero balance across all report columns. This reduces clutter and makes trial balance reports easier to read by removing rows that do not add useful information.
Original PR description
… zero The unallocated earnings/losses line was displayed even when its balance was zero in every column group, cluttering the report with uninformative rows. We therefore filter out lines whose balance is zero across all column groups. Forward-Port-Of: odoo/enterprise#129129
This fix prevents invoices with similar numbers but different suffixes from affecting each other's gap warnings. Businesses will see more accurate alerts for missing invoice numbers, reducing confusion during accounting reviews and compliance checks.
Original PR description
### Issue: When moves share the same `sequence_number` in a journal but have different suffixes (e.g. `INV/2026/00010` and `INV/2026/00010A`), the `made_sequence_gap` flag was incorrectly set ###…
### Issue: When moves share the same `sequence_number` in a journal but have different suffixes (e.g. `INV/2026/00010` and `INV/2026/00010A`), the `made_sequence_gap` flag was incorrectly set ### Cause: `_update_sequence_made_gap`, introduced in commit https://github.com/odoo/odoo/commit/17893089e8b21c0ecab5e61ed8e2c33f3731b3ac selects the previous and next moves ordered by `sequence_number` without filtering by suffix This causes two issues: - Moves from different suffix sequences are used as neighbors, leading to incorrect gap detection - Duplicate `sequence_number` values across suffixes are not accounted for, so only one move is considered per number ### Steps to reproduce: - Install `account` - Post 12 invoices to get a sequence up to `INV/2026/00012` - Reset `INV/2026/00012` to draft, rename it to `INV/2026/00010A` - Reset `INV/2026/00010A` to draft, rename it to `INV/2026/00009A` and confirm Before the fix: `INV/2026/00011` is red Expected: `INV/2026/00011` should not be red because `INV/2026/00010` exists - Delete `INV/2026/00009` Before the fix: `INV/2026/00010` is red Expected: `INV/2026/00010` should be red (gap in no-suffix sequence) - Reset `INV/2026/00009A` to draft and confirm it again Before the fix: `INV/2026/00010` is not red Expected: `INV/2026/00010` should still be red (different suffix) ### Notes: Suffix changes are treated as distinct sequences following the same gap rules as any other sequence This was agreed with R&D — the gap flag is meant to signal inconsistencies within a sequence, not across suffixes opw-6454823 Forward-Port-Of: odoo/odoo#282503
This change makes an automated CRM forecast check wait until a deal is fully marked as won before moving on. It reduces random test failures in the release process, helping teams get more dependable validation without changing CRM behavior for users.
Original PR description
The crm_forecast tour is red randomly on runbot on the Won banner step. We click the won button and go back directly, so the kanban can be loaded before the lead is won. Now we wait for the ribbon first. runbot-242139 Forward-Port-Of: odoo/odoo#284721
This fix prevents an unexpected error when the system handles report actions. It improves reliability for users by allowing the process to use the correct record context instead of ending in a traceback.
Original PR description
opw-6360013 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#274977
The registration wizard for France's PDP service no longer shows an unnecessary “Production” label when the system is already in production mode. This avoids confusing wording for users during setup without changing the underlying registration process.
Original PR description
It makes no sense to mention (Production) on pdp registration wizard when you are in prod mode Forward-Port-Of: odoo/odoo#280501 Forward-Port-Of: odoo/odoo#280360
Invalid VAT warning messages now preserve the exact VAT number entered by the user, such as Swiss VAT numbers beginning with CHE. This avoids confusing truncated messages and helps users identify and correct the value that failed validation.
Original PR description
Before this change: When entering or importing a VAT number (e.g., CHE-115.391.649), an invalid VAT warning displays a string missing its country_id (e.g., E-115.391.649). This confuses users and masks the actual input string that triggered the validation failure. To reproduce: 1. Open any contact record and set the Country to Switzerland. 2. Enter an invalid or manually formatted Swiss VAT number like `CHE-115.391.649`. 3. Save or trigger the VAT validation check. 4. Observe the warning banner showing `E-115.391.649` instead of `CHE-115.391.649`. After this change: The validation warning logic preserves the original user input when constructing the alert message, ensuring error notifications accurately display VAT number. Issue introduced by: * https://github.com/odoo/odoo/commit/ac95d2d6d80a368dfb190d0ac21da2af479a8488 * https://github.com/odoo/odoo/commit/a2afe3292e1cd0a4f339dc47707e469653d13ea0 opw-6474217 Forward-Port-Of: odoo/odoo#284305
Manufacturing orders in warehouses using a three-step process are now counted correctly in stock forecasts. This helps replenishment teams see incoming finished goods accurately and avoid unnecessary purchasing or production decisions.
Original PR description
### Steps to reproduce: - In the settings enable Multi-Steps Routes - Put your warehouse in manufacture in 3 steps - Create a storable product P - Create and confirm an MO for 1 unit of P - Go to…
### Steps to reproduce: - In the settings enable Multi-Steps Routes - Put your warehouse in manufacture in 3 steps - Create a storable product P - Create and confirm an MO for 1 unit of P - Go to Inventory > Operations > Procurement > Replenishment - Create a new one for P in WH/stock #### > The forecasted quantity in stock is still 0 but should be at 1 ### Cause of the issue: This is the exact use case already fixed in 85dd3369ed17b98b2ce485be04f140cf4cfa8aa3, which stamped the finished move with a `location_final_id` pointing at WH/Stock so that the move contributes to the forecast there even though its `location_dest_id` is the intermediate WH/Post-Production. That fix was reverted in practice by 42275f83dc5350822a625e19d65148e8b41ab1d4, which replaced the value with `mo.location_dest_id`: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_move.py#L466-L467 Its reasoning was that in a single-warehouse setup `location_dest_id` equals the warehouse stock location, so the behaviour would be unchanged. That holds in 1 and 2 steps, where the extra step is on the component side and only moves `default_location_src_id` to the pre-production location. It breaks in 3 steps, the only mode that also moves `default_location_dest_id`, to the post-production location: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L246-L247 and `_compute_locations` propagates it to the MO: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/mrp_production.py#L334-L341 WH/Post-Production is a sibling of WH/Stock under the warehouse view location, not a child of it. Since `location_final_id` takes precedence over `location_dest_id` for the non-done part of the move chain: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L331-L334 the finished move stopped being counted in the WH/Stock forecast. Why the test did not catch it: `test_3_steps_manufacturing_forecast` stayed green through the whole regression, because it scoped `virtual_available` with a `location_id` context key. `_get_domain_locations` only reads `location` and `warehouse_id`; `location_id` is silently ignored: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L284-L287 The call therefore fell through to the branch scoping the forecast to every warehouse view location: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L303-L309 and the warehouse view location is the common parent of both WH/Stock and WH/Post-Production. The assertion held regardless of where `location_final_id` pointed, so the test was a false positive from the start: it also passes with 85dd3369ed17b98b2ce485be04f140cf4cfa8aa3 fully reverted. Using the `location` key makes it fail without the fix and pass with it. ### Fix: Neither fix proposition was right on its own; each one was correct only in its own scenario. The`mo.warehouse_id.lot_stock_id` resolves the warehouse from the components, so it points at the wrong warehouse as soon as the finished product is produced for another one. `mo.location_dest_id` is the post-production location as soon as the warehouse manufactures in 3 steps, so it drops the quantity from the forecast of the manufacturing warehouse itself. What separates the two is not the warehouse but whether the destination is a transit step. In 3 steps the finished product only reaches the stock through the post-production push rule: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L57 so the final location is the stock of the warehouse owning that destination. Any other destination is already final and is kept as is, which leaves cross-warehouse MOs and destinations set to a sub-location of the stock untouched. The rule's destination is read rather than `warehouse.lot_stock_id` because a push move takes its destination from the rule and not from the operation type: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/stock_rule.py#L256-L260 so the forecast stays correct when the store step is reconfigured to land somewhere else than the warehouse stock. The rule is looked up on `pbm_route_id` by its `picking_type_id` instead of through `warehouse.sam_rule_id`, because that field is no longer set. It used to be an entry of `_generate_global_route_rules_values`, and it is that entry which made the generic warehouse machinery create the rule and store it back on the warehouse: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/stock_warehouse.py#L403-L410 11e69870db1c49d9a6af79ffd263e4e162b34b6b removed it when the post-production step stopped being a pull rule on the Manufacture route and became a push rule generated from `get_rules_dict`. Only the field declaration was left behind, and nothing writes it any more: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L21-L22 so reading it would silently give an empty recordset. The lookup is not delegated to `_get_push_rule` to avoid a search per finished move. opw-4882390 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283952 Forward-Port-Of: odoo/odoo#283234
Appointment invitation emails now use the required permissions when creating public calendar links. This prevents failed email generation for attendees and keeps appointment invitations working smoothly.
Original PR description
Since calendar attendee access tokens are restricted to system users, appointment mail templates must sudo token reads when generating public calendar links. This follows the same pattern as the calendar mail templates and avoids an AccessError when rendering attendee invitation emails. ref: https://github.com/odoo/enterprise/commit/88a3cca752a5f726cd0260b485fc93f65a268cf8 Task-4711415 Forward-Port-Of: odoo/enterprise#129511
Unreconciling one bank statement line from an invoice or bill now removes only that specific reconciliation instead of clearing all related reconciliations. This prevents invoices and bills with multiple bank statement matches from being incorrectly reopened or marked unpaid.
Original PR description
**STEP TO REPRODUCE** 1. Create a bill or an invoice. 2. Create multiples bank statement. 3. Reconciles those bank statements to the invoice/bill. 4. Unreconciles one of those bank statement on the invoice/bill. 5. Notice the invoice/bill is completely unreconciled. Expected behavior: only the unreconciled line should be unreconciled. **CAUSE** When unreconciling a partial linked to a bank statement, we call `delete_reconciled_line()` on both `partial.credit_move_id` and `partial.debit_move_id`. One on those is the the payment_term line of the invoice/bill the bank statement line is reconciled with. This payment_term line is also linked to all partial reconcilliation line on the invoice/bill, so calling `delete_renconciled_line()` delete all the reconciled line of the invoice/bill. **FIX** We should call `delete_renconciled_line()` only on the bank statement move line, not on the payment term line. opw-6465096 Forward-Port-Of: odoo/enterprise#128126
This fixes an automated check for the field service sales flow by adjusting the order of signing steps and handling expected page navigation. It helps keep quality checks stable so future updates are less likely to be delayed by false failures.
Original PR description
Solution: Reorder the signing steps and add `expectUnloadPage` when needed runbot-939241 Forward-Port-Of: odoo/enterprise#127442