Friday, August 28, 2026
24 changes · 19.0
Enhancements to existing features
Companies sharing the same French SIREN in one database can now reuse a successful identity check from another company. This reduces repeated manual KYC work for groups with many branches or entities registered under the same identifier.
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
Odoo now checks user permissions more consistently when related records are changed through background context commands. Unauthorized changes are ignored in the same way as standard field updates, reducing unexpected access errors and improving data protection consistency.
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
Neutralized test databases will now clear the Belgian POS fiscal identifier instead of keeping a placeholder value. This lets businesses remove the blackbox from POS configurations and continue using the POS normally in test environments.
Original PR description
When a database is neutralized, the fiscal data module keeps a dummy l10n_be_pos_id on every pos.config. As long as this value is set, the blackbox cannot be detached from the configuration, which blocks any use of the POS on a neutralized (test) database. Empty the field instead of setting a dummy identifier, so the blackbox can simply be removed from the config and the POS used normally.
Pay later receivable entries in Point of Sale settlement are now reconciled in one grouped operation instead of many repeated operations. This keeps the accounting result unchanged while reducing processing overhead, especially for sessions with many customers using pay later.
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
Resolved issues and error corrections
Mexican electronic invoice XML files now use the customer or user language for units of measure when invoices are sent in bulk. This prevents Spanish-language CFDI documents from incorrectly showing unit names in English, improving consistency between the PDF and XML sent to customers.
Original PR description
### Issue before this commit: When a CFDI invoice was sent through a mass action (Send & Print executed by OdooBot), the unit of measure in the generated CFDI appeared in English instead of the…
### Issue before this commit: When a CFDI invoice was sent through a mass action (Send & Print executed by OdooBot), the unit of measure in the generated CFDI appeared in English instead of the configured language (e.g. Spanish. ### Steps to reproduce the issue: 1.Download Accounting, Contacts and l10n_mx 2. Switch to Spanish (MX) language 3. Set the language of "Inmoviliaria CVA" and "XENON INDUSTRIAL ARTICLES" to Spanish (MX) 4. In contacts select Archived in filters and switch OdooBot language to Spanish (MX) 5. Create and confirm two invoices in the database, one for each customer but don't send these invoices 6. Go to the list view and select these two invoices, and click on "Send and print" and select CFDI 7. Check any of the XML files generated in any of the invoices (in the CFDI tab of the invoice) 8. See that the UoM in the XML file, will be set in english rather than Spanish (MX) ### Cause of the issue: The context under which the batch action runs does not contain the lang key. As a result, translated fields (such as product_uom_id.name) were read in the source language (English) instead of the executing user's language, because nothing in the CFDI generation chain explicitly forced the correct lang into the context. ### Reason to introduce the fix: A CFDI must always report translated fields in the correct language. The fix ensures the invoice is read with the executing user's language when the context doesn't already specify one, so translated fields are consistently correct across both flows. opw-6399860
This fix prevents PDF quote generation from failing when Quote Builder documents include dynamic fields. It preserves existing PDF form settings while adding the required form resources so quotes can be printed reliably in affected Python/pypdf environments.
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
Checkout now stops carts that become zero-priced because of country-based pricing when zero-priced product sales are blocked. Customers are sent back to the cart with a clear warning instead of waiting on a payment step that may hang or time out.
Original PR description
When 'Prevent Sale of Zero Priced Product' is enabled and a country-group pricelist prices a product at 0 once the customer's country becomes known during checkout, the cart total becomes 0. The 'free order' branch then validated the order at the payment step, which could hang and time out for a cart that is not actually sellable. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285105 Forward-Port-Of: odoo/odoo#284959
Point of Sale loyalty rewards now correctly grant a free product when a cashier enters a qualifying quantity directly, such as using the numpad or barcode quantity. This prevents missed promotions for buy-quantity-get-one offers based on product tags, improving checkout accuracy and customer experience.
Original PR description
Steps to reproduce: - create a "Buy 2 Take 1" program whose rule and reward both target a product tag containing several products - in the PoS, add one of the tagged products to the order - set its…
Steps to reproduce: - create a "Buy 2 Take 1" program whose rule and reward both target a product tag containing several products - in the PoS, add one of the tagged products to the order - set its quantity to 3 with the numpad Issue: The free product is not given. Clicking the product a third time instead of typing the quantity does give it, and so does a program whose reward is a single product. Cause: A reward whose products come from a tag is multi_product, so getClaimableRewards never computes its unclaimed quantity and the auto claim of updateRewards skips it: which product to give out is unknown. The only place claiming such a reward is addLineToCurrentOrder, which resolves it with the product that was just added. Setting a quantity does not go through it, hence the difference. Fix: Resolve the reward product the same way when auto claiming: the product of the line being worked on, or, failing that, the only reward product the order contains. The reward then follows the quantity of any of the tagged products, not only of the first one added, and the cashier is still asked when the order does not settle the choice. opw-6430385 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fix stops Odoo from showing repeated error dialogs when a browser clears or blocks the local storage used for the unread message badge. The unread badge may fail to update in those rare cases, but the main app continues working normally without disrupting users.
Original PR description
Description of the issue/feature this PR addresses: The unread counter feeding the PWA app badge is written to the `odoo-mail-unread-db` IndexedDB database, through a connection opened once, at…
Description of the issue/feature this PR addresses: The unread counter feeding the PWA app badge is written to the `odoo-mail-unread-db` IndexedDB database, through a connection opened once, at module load, and cached for the life of the page by the bundled idb-keyval (3.2.0), which cannot reopen it. When the browser drops the origin's storage (eviction under disk pressure, site data cleared, a privacy extension purging it), that connection is closed for good, and as `updateAppBadge()` discards the promise returned by `idbKeyval.set()`, the rejection reaches the user as a client error dialog. Reported in production on a backend tab left open overnight (Firefox, 19.0): ``` UncaughtPromiseError > InvalidStateError Uncaught Promise > IDBDatabase.transaction: Can't start a transaction on a closed database ``` Reproduced on a demo database below, with `?debug=assets` so that the stack points at the source: lines 23 and 24 of the vendored idb-keyval are the `db.transaction()` call of `_withIDBStore`, on the connection cached at module load. <img width="1600" height="590" alt="error-dialog-debug" src="https://github.com/user-attachments/assets/e2b1aa46-501a-43d4-b4c0-20def1f529ef" /> Current behavior before PR: 1. log into the backend and leave the tab open 2. in the devtools, Application > Storage, tick *only* "IndexedDB" and click "Clear site data", as the browser itself does when it evicts the origin (the session cookie is left untouched, so the tab keeps working) 3. receive a message, or do anything else that changes the unread counter The dialog opens, and opens again on every counter update for the whole life of the tab, since the dead connection is never replaced; the counter is not saved any more either. Two variants of the same code: the write also rejects, without anything being cleared, when the origin runs out of storage quota (`QuotaExceededError`), and when the browser forbids storage for the origin, `indexedDB.open()` throws during module evaluation, so `store_service_patch.js` fails to load entirely. Desired behavior after PR is merged: The store is opened lazily, dropped whenever saving the counter fails, and the write is retried once on a new connection: a single counter update after the connection was closed saves it again. Opening the database is guarded too, for the synchronous throw. When the retry fails as well the error is ignored, the app badge being cosmetic. `mail/static/tests/web/app_badge.test.js` covers the retry, the recovery of a permanently closed connection, and the database that cannot be opened at all. The three tests fail on the current code with the uncaught `IDBDatabase.transaction` error. Introduced in 19.0 by c04c4cb715902fe95618a15e18233cd001bdb304, and identical from saas-19.1 to master, hence this PR against 19.0. The corporate CLA for ERPVibe Limited is submitted in #283477. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Products added to an active Point of Sale session after it starts now receive the correct pricelist discounts or prices based on their category. This prevents cashiers from charging the standard sale price when a category pricing rule should apply, improving price accuracy at checkout.
Original PR description
Steps to reproduce: - Create a pricelist with a rule applied on a product category and make it available in the PoS - Open a PoS session - From the backend, create a product in a new category covered…
Steps to reproduce: - Create a pricelist with a rule applied on a product category and make it available in the PoS - Open a PoS session - From the backend, create a product in a new category covered by such a rule - Back in the PoS, find that product through Search > Search more and add it to the order Issue: The product is priced at its sale price, the pricelist rule set on its category is ignored. Cause: A product that is not part of the initial payload is loaded on the fly by load_product_from_pos, which sends back the rules returned by get_pos_ui_product_pricelist_item_by_product. That domain only matches the rules set on the template or on the variant, never the ones set on a product category, and the payload carries no product.category record either. The client therefore has neither the rule nor the category: parentCategories walks categ_id, which resolves to nothing, so getCategoryRulesIds returns no rule and getPrice falls back to the sale price. This stayed unnoticed because product.category is fully loaded when the session starts, along with every category rule, so only the categories created after the session was opened are missing. Fix: Send the categories of the loaded products, since a rule set on a parent category applies to its children - along with the products, and match the rules set on those categories in get_pos_ui_product_pricelist_item_by_product. The initial loading domain of product.pricelist.item no longer filters the category rules on the loaded categories: such a rule has to be loaded whatever the products sent to the client are, since a product of that category may be loaded later on. opw-6477745 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Vendor bills created from incoming emails now avoid using the saved original email copy as the main invoice attachment. This ensures the actual invoice PDF or image is shown for preview, reducing confusion and broken document previews when processing supplier invoices by email.
Original PR description
When an incoming mail server has the "Keep Original" option enabled, a copy of every incoming mail is stored as original_email.eml. Because it is not a document of the invoice, the system unattaches it, however it may still be used as main attachment in case no other PDF or image was attached to the message. Steps to reproduce: - Configure an incoming mail server with "Keep Original" enabled, using an alias pointing to a vendor bill journal. - Send a mail to that alias containing an xml embedding a PDF. - Open that bill Issue: The invoice's main attachment points to the .eml file. This occurs because it is set before the PDF is extracted from the xml. Then, when import extracts the PDF, it is added as attachment on the invoice, but we already have a main attachment that won't be overwritten. However, once a pdf or image is added as attachment, the system will show the (broken) preview. opw-6431726
This fix prevents subcontracted receipt lines from being treated as already picked too early, which previously caused incorrect backorders when receiving both subcontracted and regular products together. It adds regression coverage so future changes do not reintroduce the issue, helping inventory receipts reflect the actual quantities processed.
Original PR description
### Steps to reproduce: - Create a subcontracted product P1 - Create a storable product P2 - Buy 5 units of both products from your subcontractor - On the receipt set both moves quantity to 2 Units -…
### Steps to reproduce: - Create a subcontracted product P1 - Create a storable product P2 - Buy 5 units of both products from your subcontractor - On the receipt set both moves quantity to 2 Units - Validate the receipt and create a backorder #### > Only the subcontracted move has been kept on the receipt and a backorder was created for 3 units of P1 and 5 of P2. ### Cause of the issue: Setting the quantity of the subcontracted move will automatically record the quantities on the subcontracted MO: https://github.com/odoo/odoo/blob/9440ff9064c77af0159a427de8f5e5721aec0de5/addons/mrp_subcontracting/models/stock_move.py#L83 https://github.com/odoo/odoo/blob/9440ff9064c77af0159a427de8f5e5721aec0de5/addons/mrp_subcontracting/models/stock_move.py#L123 https://github.com/odoo/odoo/blob/9440ff9064c77af0159a427de8f5e5721aec0de5/addons/mrp_subcontracting/models/mrp_production.py#L91 However, the `_update_finished_move` method adds and update the related subcontracted move lines marking them as *picked* to adapt the related reservation: https://github.com/odoo/odoo/blob/9440ff9064c77af0159a427de8f5e5721aec0de5/addons/mrp_subcontracting/models/mrp_production.py#L118-L164 This is problematic since picking a move line will also pick the move: https://github.com/odoo/odoo/blob/9440ff9064c77af0159a427de8f5e5721aec0de5/addons/stock/models/stock_move.py#L261-L267 And only picked moves are considered to be processed at picking validation. ### Note: The exact same issue had already been fixed in 17.0: db8b33ebb9fe23507bcba30b12741e4d688ae549 However, the fix had an issue concerning the barcode behavior as it removed the picked computation for subcontracted moves which made hybrid pickings such as the above one (with one subcontracted and one non-subcontracted move) impossible to process in the barcode app. As such, the fix and test where reverted in cf2d18c92bee55ef79db1a338e9baf12f258ee5b The present commit provides an alternative fix of the original issue keeping subcontracted moves unpicked by quantity changes without affecting the picked computation of subcontracted moves (e.g. adding a picked move line on a subcontracted move will still pick that move). Enterprise: https://github.com/odoo/enterprise/pull/123884 opw-6330584 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283729 Forward-Port-Of: odoo/odoo#275304
Odoo now removes the old single calendar entry when Outlook changes it into a recurring meeting. This prevents duplicate events from appearing after synchronization and keeps users' calendars 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 fixes an issue where archiving or deleting one user could incorrectly remove their shared contact from restricted discussion channels, even if another active user for that contact still had access. Contacts are now only removed when no remaining linked user qualifies for the channel, helping avoid accidental 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
Uruguay electronic invoicing now uses the exact exchange rate saved on the original invoice instead of recalculating it later. This prevents mismatches on credit notes or references if currency rates are updated after an invoice is posted.
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
Asset list views now remain accessible even when some assets contain invalid analytic distribution references. This prevents users from being blocked by a repeated loading error while preserving the read-only list behavior.
Original PR description
Issue - If there are any account.asset records with analytic distributions with accounts that do not exist, it causes a recursive traceback when opening the list view of the `account.asset` model. The issue stems from `jsonToData` attempting to save the distributions json via the `save` call, where one (or multiple) accounts are non existent, which in turn runs `jsonToData` after refetching via the `load` call - overwriting `record.data` with the original, still-corrupt JSON. This creates a loop with no exit condition. Solution - In the Assets list every row is readonly, so `save()` is never reached, so `root.load()` never fires, so there is no reload to re-read the corrupt JSON. Makes the list accessible, even though the JSON values for the `analytic_distribution` are invalid. opw-6500446
Manufacturing orders could incorrectly warn that components were under-consumed when a serial-tracked purchased component arrived after the finished product serial number was assigned. The fix recognizes correctly reserved components in this flow, avoiding unnecessary warnings and smoother production completion.
Original PR description
Steps to reproduce the bug: - Warehouse configured for 2-Step Manufacturing - Component "C1": - Routes: MTO + Buy - Tracking: By Unique Serial Number - Create a Finished product: - Route: Manufacture…
Steps to reproduce the bug:
- Warehouse configured for 2-Step Manufacturing
- Component "C1":
- Routes: MTO + Buy
- Tracking: By Unique Serial Number
- Create a Finished product:
- Route: Manufacture
- Tracking: By Unique Serial Number
- BoM:
- component "C1": 1 unit
- Create and confirm the Manufacturing Order
- Generate/assign the serial number for the finished product immediately, before the related purchase order is even confirmed
- Confirm the Purchase Order
- Receive the component
- Transfer the component to WH/Pre-Production
- Click Produce All
Problem:
A Consumption Warning was displayed stating that the consumed quantity differs from the expected quantity, even though the consumed quantity was exactly equal to the BoM quantity and no manual quantity change was performed. Clicking "Set Quantities & Validate" completed the MO successfully, hiding the inconsistency.
`_get_consumption_issues()` only counts a raw move's quantity as "consumed" when `move.picked` is `True`
[(odoo/addons/mrp/models/mrp_production.py#L1791)](https://github.com/odoo/odoo/blob/19.0/addons/mrp/models/mrp_production.py#L1791).
Generating the finished product's serial number calls `_set_qty_producing()`
[(odoo/addons/mrp/models/mrp_production.py#L1402)](https://github.com/odoo/odoo/blob/19.0/addons/mrp/models/mrp_production.py#L1402).
, which itself only sets `move.picked = True` when `move.quantity` is truthy
[(odoo/addons/mrp/models/mrp_production.py#L1443)](https://github.com/odoo/odoo/blob/19.0/addons/mrp/models/mrp_production.py#L1443).
At that point the MTO component had not been received yet, so `move.quantity` was still 0 and `picked` was never set. Once the component was later received and transferred to Pre-Production, `_action_assign()` correctly reserved the raw move (`quantity` became correct), but nothing ever went back to flip `picked` to `True`, since `_set_quantities()`
[(odoo/addons/mrp/models/mrp_production.py#L2932)](https://github.com/odoo/odoo/blob/19.0/addons/mrp/models/mrp_production.py#L2932).
only calls `_set_qty_producing()` again when `qty_producing` is still falsy, which was no longer the case. `_get_consumption_issues()` therefore still counted the consumed quantity as 0 against the expected BoM quantity.
Solution:
In `pre_button_mark_done()`, for auto productions (single unit being produced), after `_set_quantities()` runs, also mark as `picked` any non-manual-consumption raw move that is not yet `picked` but whose reserved `quantity` already exactly matches its own `product_uom_qty`. This is scoped to auto productions only, since on a multi-unit production a raw move's aggregate `quantity` can equal its `product_uom_qty` by coincidence (reservation for future backorder steps) even though only part of it is meant to be consumed for the current step.
opw-6421531This fixes a Windows-specific issue that prevented website and app assets such as scripts, styles, and images from loading correctly. Windows deployments should no longer see missing static content caused by failed URL handling.
Original PR description
### Problem
In `Application.get_static_file()`, `os.path.normpath(os.path.normcase(path))` rewrites every `/` as `\\` on Windows. The following `path.split('/', 3)` yields a single element rather than four, raising `ValueError` and returning `None`. This causes all `/<module>/static/...` asset requests on Windows to fail with HTTP 404.
### Solution
Append `.replace(os.sep, '/')` to the normalized path to ensure URL segment splitting works consistently across Windows and POSIX while preserving traversal jail normalization.
Fixes #285284Users with Invoicing & Banks access can now undo bank reconciliations for entries that have not been reviewed, without running into an access error. The change keeps the existing review status intact, so controls still apply to reviewed entries while day-to-day correction work is unblocked.
Original PR description
Steps to reproduce: - Install `Accounting` module - Change group of current user for "Accounting" to `Invoicing & Banks` - Accounting > Dashboard > Bank > Any reconciled bank statement line > Undo…
Steps to reproduce: - Install `Accounting` module - Change group of current user for "Accounting" to `Invoicing & Banks` - Accounting > Dashboard > Bank > Any reconciled bank statement line > Undo Reconciliation (<img width="38" height="48" alt="image" src="https://github.com/user-attachments/assets/b637e4f3-c43b-47d4-9de1-2f0e14d1efc9" />) Problem: Users with "Invoicing & Banks" access encounter an "AccessError" when undo a bank reconciliation when account move is not checked/reviewed. Cause: Upon undoing, `action_undo_reconciliation` method is called and recreates the statement move lines with `checked=True`. This value is forwarded to `account.move.write()`, where the `_is_user_able_to_review()` check requires `account.group_account_user`. As Invoicing & Banks users only have `account.group_account_basic`, therefor access check raises an AccessError. Solution: Preserve the existing `checked` state when resetting the statement line so that unchecked lines can be unreconciled while the access restriction remains for checked lines. opw-6409216 Enterprise PR: https://github.com/odoo/enterprise/pull/127895 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fix ensures that when a timesheet-related invoice is reversed and recreated through a credit note, the original timesheets are correctly connected to the new invoice. This helps maintain accurate billing records and prevents replacement invoices from appearing disconnected from the work they cover.
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 subscription invoicing from crashing when an automatic payment fails and the related payment record is rolled back. It helps recurring billing continue handling failed payments cleanly instead of interrupting the scheduled invoice process.
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#83913Users with Invoicing & Banks access can now undo bank reconciliations for entries that have not been reviewed, avoiding an unnecessary access error. Reviewed entries remain protected by the existing approval restrictions, preserving accounting controls.
Original PR description
Steps to reproduce: - Install `Accounting` module - Change group of current user for "Accounting" to `Invoicing & Banks` - Accounting > Dashboard > Bank > Any reconciled bank statement line > Undo…
Steps to reproduce: - Install `Accounting` module - Change group of current user for "Accounting" to `Invoicing & Banks` - Accounting > Dashboard > Bank > Any reconciled bank statement line > Undo Reconciliation (<img width="38" height="48" alt="image" src="https://github.com/user-attachments/assets/b637e4f3-c43b-47d4-9de1-2f0e14d1efc9" />) Problem: Users with "Invoicing & Banks" access encounter an "AccessError" when undo a bank reconciliation when account move is not checked/reviewed. Cause: Upon undoing, `action_undo_reconciliation` method is called and recreates the statement move lines with `checked=True`. This value is forwarded to `account.move.write()`, where the `_is_user_able_to_review()` check requires `account.group_account_user`. As Invoicing & Banks users only have `account.group_account_basic`, therefor access check raises an AccessError. Solution: Preserve the existing `checked` state when resetting the statement line so that unchecked lines can be unreconciled while the access restriction remains for checked lines. opw-6409216
Fixed an issue where Belgian payroll Dimona action buttons could disappear after an employee record was auto-saved before being explicitly saved. This ensures HR teams can reliably open, update, close, or cancel Dimona declarations when managing employee records.
Original PR description
Steps to reproduce: - Create an employee with a contract start date and wage. - Leave the form without clicking Save (create another employee, or navigate away). - Reopen the employee from the Kanban view. - The Open/Update/Close/Cancel Dimona buttons never appear again. Made l10n_be_needs_dimona_in and l10n_be_dimona_next_action read-only computed fields, so a stale value saved by the client can no longer override them. Task 6515877
Helpdesk forms on websites now keep their translations when created for a team. This ensures customers see the form in the website or visitor language instead of the language used by the employee who configured the team.
Original PR description
When a helpdesk team has its website form enabled, a dedicated qweb view is generated from the `ticket_submit_form` template. The arch was read in the language of the user creating or editing the…
When a helpdesk team has its website form enabled, a dedicated qweb view is generated from the `ticket_submit_form` template. The arch was read in the language of the user creating or editing the team, so a team set up by an English user language produced an English form even when the website served another language. Steps to reproduce ================== 1. Set a language other than English as the website default language. 2. While your user language is English, create a helpdesk team with the website form enabled. 3. Open the team form on the website. => The form is rendered in English instead of the website language. Root cause ========== `_ensure_submit_form_view` read the template arch without forcing a language, so it used the current user's language and stored only that value on the generated per-team view. Fix === Read the template arch in the default language of the team's website, so the generated form matches the website language regardless of the user's own language. opw-6303903 Forward-Port-Of: odoo/enterprise#121164