Wednesday, September 23, 2026
26 changes · saas-19.3
Enhancements to existing features
Accounting test setup now updates database statistics after loading test chart data, helping the system choose faster queries. This reduces very slow localization test runs from minutes to seconds and improves build stability without changing user-facing accounting behavior.
Original PR description
The default order of account.account joins an aggregate over the ancestor paths of every account. A company created in a test only exists in the test transaction, so the planner assumes it has a…
The default order of account.account joins an aggregate over the ancestor paths of every account. A company created in a test only exists in the test transaction, so the planner assumes it has a single account and reruns that aggregate for each of its accounts, including the dead rows left by earlier test classes. On runbot this turned 5s L10n class setups into minutes and got buckets killed. Analyzing the account tables after the chart load gives the planner the real counts, as test_balance_sheet_balanced already does. Order changes alone are not enough, and rewriting the query slows down production searches. The journal default account lookup only needs a code length, so it now uses the id order. | Benchmark | without | with | |--------------------------------------------|----------|----------| | Peru TestSequence, 25k dead rows (local) | 120-132s | 4.3-7.2s | | L10n build, slow account path queries | 94 | 0 | | L10n build, TestPosAR setup | 145s | 15s | | L10n build, TestCIIFR setup | 109s | 8s |
Resolved issues and error corrections
The Point of Sale now avoids treating a register as opened when the server cannot confirm it due to an unclear connection loss. Staff are notified and can retry once the connection returns, helping prevent sessions with missing opening cash details or incorrect temporary names.
Original PR description
Steps to reproduce: - Open a PoS so that the Opening Control popup is displayed. - Cut the connection to the server without the browser going "offline" (e.g. the router stays up but the internet link…
Steps to reproduce: - Open a PoS so that the Opening Control popup is displayed. - Cut the connection to the server without the browser going "offline" (e.g. the router stays up but the internet link is down). - Click on "Open Register". - Restore the connection, make some orders, close the session. Issue: The session is closed with orders but was never opened on the server: it has no opening date, keeps its temporary name "<config>/00000", and the opening cash was never recorded. `set_opening_control` was called with the `queue` flag. On a ConnectionLostError the call was silently pushed to the in-memory unsync queue, and the popup marked the session as opened locally and closed itself. That queue is only replayed on the browser `online` event, which is never fired when the device stayed connected to its local network, and it is lost when the tab is closed or reloaded. Meanwhile the server accepts orders on a session in `opening_control`, and nothing checks it at closing. Opening the register is not an operation that can be deferred: the session must be opened before any order is made. The call is no longer queued. When the connection is lost, the user is told that the register cannot be opened offline, and the popup stays open so the opening can be retried once the connection is back. opw-6580262 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#289036 Forward-Port-Of: odoo/odoo#288899
Fixed an issue where importing or creating multiple variants for the same product at once could remove the product image. This ensures product and variant images remain visible after bulk imports, reducing manual cleanup for users managing product catalogs.
Original PR description
Steps to reproduce: - Import a product CSV with one row per attribute value ("Product Values" column) and an image URL on every row, or create several variants of the same template in one `create()`…
Steps to reproduce:
- Import a product CSV with one row per attribute value ("Product Values" column) and an image URL on every row, or create several variants of the same template in one `create()` call with an image.
- Open the product: neither the template nor the variants have an image. Products without attribute values imported in the same file are fine.
The import creates all variants of a template in a single batch. The inverse of `image_1920` is applied record by record: the first variant finds no image on the template and writes its image there. Writing `image_1920` on the template invalidates the image cache of every product.product, including the sibling variants being created, whose `image_1920` is protected during the inverse and therefore reads back as empty. The next variant then takes the "clear the image from the template" branch and writes `False` on the template, undoing the first write. Nothing is stored.
Read the value to inverse for every record before writing anything, so the invalidation triggered by the template write cannot change what the following records write.
opw-6545651
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#287419Point of Sale now correctly loads product option values when products are updated after data has been cached in the browser. This prevents errors when selecting affected products and ensures product variants remain available without requiring users to clear cache or restart sessions.
Original PR description
Steps to reproduce: - Open a PoS session so the browser caches the data (IndexedDB) - Add an attribute line on a product available in PoS, using an attribute that already existed and was not modified…
Steps to reproduce: - Open a PoS session so the browser caches the data (IndexedDB) - Add an attribute line on a product available in PoS, using an attribute that already existed and was not modified since - Reopen the PoS and click the product Issue: TypeError: undefined is not an object (evaluating 'values[0].is_custom') in openConfigurator. The attribute line is loaded but none of its values are; the variants have no attribute values on the frontend. Closing the session or reloading the browser does not help since the cache is kept. Cause: On an incremental load (pos_last_server_date in context), every model only returns the records written after the last sync. The domain of product.template.attribute.value restricts attribute_id to the ids in data['product.attribute'], which in that case only holds the attributes modified since the last sync. Values created on an older attribute are therefore excluded, and the frontend drops the ids it cannot resolve. Fix: Filter attribute_id with the domain of product.attribute itself instead of the ids of the records returned in the current load. A full load returns the same records as before. opw-6568808 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#288065
When a Belgian customer's VAT number is changed, the related BCE/KBO business identifier is now updated automatically instead of keeping the old value. This keeps accounting and electronic invoicing data consistent and avoids outdated company identifiers.
Original PR description
Steps to reproduce: * Install **Accounting** module. * Create a Belgian partner. * Assign a VAT number (e.g. BE0477472701). * The BCE/KBO field in Multi ID is correctly populated. * Modify the VAT…
Steps to reproduce:
* Install **Accounting** module.
* Create a Belgian partner.
* Assign a VAT number (e.g. BE0477472701).
* The BCE/KBO field in Multi ID is correctly populated.
* Modify the VAT number.
Observed behavior:
* The BCE/KBO (BE_EN) in Multi ID keeps the old value.
Cause:
* `_deduce_additional_identifiers_from_vat` skipped any identifier key that already existed in `additional_identifiers`: new_identifiers = {k: v for k, v in deduced.items() if k not in identifiers}
* On the first VAT save, BE_EN is written correctly.
* On subsequent VAT changes, BE_EN already exists, so the condition short-circuits and the old value is preserved.
Fix:
* Replace the key-presence guard with a value-equality check so that deduced identifiers are always overwritten when their value would change: updated = {k: v for k, v in deduced.items() if identifiers.get(k) != v}
* Since BE_EN is fully derived from the VAT (it is the VAT with the country prefix stripped), it must always mirror the current VAT.
* The Peppol endpoint is already declared as depending on `additional_identifiers`, so it recomputes automatically once BE_EN is corrected — no further change needed there.
opw-6545770This fix prevents invalid delivery dates from being saved during checkout, reducing the chance of incorrect customer delivery promises. It also clears estimated delivery settings when a delivery method type changes, helping keep shipping configuration consistent.
Original PR description
1. Restrict updating commitment date when the input date is invalid. 2. If estimated delivery config fields were set disable them if delivery_type was changed --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Employees without an active contract/version who are on approved time off that counts as working time will no longer be automatically checked out by the attendance scheduler. This prevents inaccurate attendance records and avoids incorrectly ending work sessions for employees who are still considered working.
Original PR description
## Short functional explanation of the error When an employee doesn't have an active version, and they're on a leave considered working time. When we run the scheduled action `Attendance:…
## Short functional explanation of the error When an employee doesn't have an active version, and they're on a leave considered working time. When we run the scheduled action `Attendance: Automatically check-out employees`, the employee is checked out after the defined tolerance time. The employee shouldn't be checked out, as they're considered working at the time. ## Reproduction steps 1. Create an employee without contract. 2. Go to Attendances > Configuration > Settings. Check Automatic Check-out. Select based on Tolerance and set the Tolerance at 2 hours. 3. Go to Time off > Management > Allocations and click on New. As Time Type, select a type considered as Working (like Homeworking or Attendance), set your employee and an Allocation duration. Click Validate. 4. Click on Management tab > Time off. Click on New and use for Time Type and Employee fields the same values you used on the allocation. Set the date as today. 5. Go to Attendances and click on New. Select the employee you're working with. Set the Check in at today, 3 hours before your local time. Remove the check out. 6. Enable the debugger and go to Scheduled Actions. Search for `Attendance: Automatically check-out employees` and click on Run manually. 7. Go back to Attendances. ### Expected Behavior The attendance of the employee is still ongoing as they're on a leave considered as working time. ### Unexpected Behavior The attendance of the employee has ended, and its duration is equal to the tolerance time defined in the settings. ## Origin of the issue This bug doesn't occur when an employee has an active version, as we retrieve the work intervals taking into account the leaves of the employee: https://github.com/odoo/odoo/blob/50298287733ce4ed8c5372495778e69cac5e114d/addons/hr/models/hr_employee.py#L1804 However, we don't take such leaves in consideration when an employee doesn't have a version: https://github.com/odoo/odoo/blob/50298287733ce4ed8c5372495778e69cac5e114d/addons/hr/models/hr_employee.py#L1780-L1788 __ task-6453044 Forward-Port-Of: odoo/odoo#289822 Forward-Port-Of: odoo/odoo#282477
Opening an order from the Sales report is now faster, especially in databases with many sales lines. The system now finds the related order directly instead of running a heavy report lookup, reducing delays for users reviewing sales data.
Original PR description
Versions -------- - 18.0+ Steps ----- 1. On a database with many sale order lines, open Sales > Reporting > Sales and switch to the list view. 2. Click a line to open its order. Issue ----- Opening the order takes seconds. Cause ----- `action_open_order` reads `order_reference` off the `sale.report` view. The view's `id` is `MIN(sale_order_line.id)`, so reading one record filters on an aggregate. PostgreSQL therefore has to aggregate every sale order line and POS order line before keeping one row. Solution -------- The report id is itself a line primary key, so the order can be resolved without reading the view. Add a `_get_order_reference` hook returning the order from the `sale.order.line` record directly. Forward-Port-Of: odoo/odoo#289284 Forward-Port-Of: odoo/odoo#289143
This fixes cases where sales orders for made-to-order manufactured products could show an incorrect cost by averaging in manufacturing moves that should not affect the sale cost. Sales margins should now better reflect the actual outgoing delivery cost, improving margin accuracy for these sales flows.
Original PR description
**Issue** Cost of SO might be wrong with MTO+Manufacture product **Steps to reproduce** - Activate margin in settings - Create 2 products: - product A: MTO + Manufacture, 1 qty onHand, standard price…
**Issue** Cost of SO might be wrong with MTO+Manufacture product **Steps to reproduce** - Activate margin in settings - Create 2 products: - product A: MTO + Manufacture, 1 qty onHand, standard price to 10 with avco valuation - product B with a standard price of 5 - Create a BOM with the product B as comp - create and confirm a SO for 1 unit of product A - Go to the associated MO and produce all - confirm the delivery linked to the SO -> The cost on the SO is 6.25, while the standard price remains 7.5 **Cause** While confirming the MO, it linked the producing move (which has a unit value of 5), to the SO: https://github.com/odoo/odoo/blob/a6f22922a97399265c04855b5d9b18ac7ac609a0/addons/sale_mrp/models/mrp_production.py#L41-L48 While confirming the delivery, it triggers `_compute_purchase_price` since the picking state changes: https://github.com/odoo/odoo/blob/a6f22922a97399265c04855b5d9b18ac7ac609a0/addons/sale_stock_margin/models/sale_order_line.py#L10-L11 which will eventually takes all the moves links to the sale order to compute the price: https://github.com/odoo/odoo/blob/a6f22922a97399265c04855b5d9b18ac7ac609a0/addons/sale_stock_margin/models/sale_order_line.py#L21 https://github.com/odoo/odoo/blob/a6f22922a97399265c04855b5d9b18ac7ac609a0/addons/stock_account/models/stock_move.py#L701-L714 Which gives an averaging between 5 and 7.5 -> 6.25. Indeed, the unit value of the out move is the standard price: https://github.com/odoo/odoo/blob/8387c28423e8208e53939da3ef5a074c072d33a6/addons/stock_account/models/stock_move.py#L353 opw-6418948 Forward-Port-Of: odoo/odoo#283940 Forward-Port-Of: odoo/odoo#280712
This fix prevents a server error that could occur when uninstalling an app after the Website app was installed. It ensures the system initializes its internal change notifications correctly, making module removal more reliable for users and administrators.
Original PR description
Issue ----- Uninstalling a module on runbot causes a `KeyError` traceback. Steps to reproduce ----- - Install website - Install any module (eg Contacts) - Uninstall Contacts > KeyError ``` Odoo…
Issue ----- Uninstalling a module on runbot causes a `KeyError` traceback. Steps to reproduce ----- - Install website - Install any module (eg Contacts) - Uninstall Contacts > KeyError ``` Odoo Server Error Traceback (most recent call last): File "/home/odoo/src/odoo/saas-19.3/odoo/http/router.py", line 443, in serve_db return retrying(serve_func, env=request.env) File "/home/odoo/src/odoo/saas-19.3/odoo/http/retrying.py", line 115, in retrying env.registry.signal_changes() ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ File "/home/odoo/src/odoo/saas-19.3/odoo/orm/registry.py", line 1210, in signal_changes self.cache_sequences[cache_name] += 1 ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^ KeyError: 'stable' ``` Cause ----- Commit c28fe3e introduced changes to registries. If the registry is not loaded after the call to `load_modules`, a new one is created. The thing is that we don't do `setup_signaling` on this new registry like we did for the previous one[^1]. This is problematic when `website` is installed since 0c0667b, which introduced a need for a cache invalidation[^2] on the new registry (writing an `ir.config_parameter` causes a clearing of the `stable` cache). Because there was no signal setup, the registry's `cache_sequence` is empty, causing the error. ----- Ticket: opw-6566786 [^1]: https://github.com/odoo/odoo/blob/dc6a8c87ed33a82b0422423eea1ef0351f5a5fc8/odoo/orm/registry.py#L173 [^2]: https://github.com/odoo/odoo/blob/dc6a8c87ed33a82b0422423eea1ef0351f5a5fc8/addons/website/models/ir_module_module.py#L57
Shipment cancellations now send each delivery carrier only the shipment records that belong to it. This prevents duplicate cancellation attempts and reduces the risk of carrier API errors or accidental cancellations of the wrong shipments.
Original PR description
When cancelling shipments for multiple pickings with different delivery carriers, passing the entire recordset (self) instead of the individual picking to the carrier's cancel_shipment method causes each carrier to receive tracking references that do not belong to it. This results in API errors or silent wrong cancellations at the carrier level, and each shipment being cancelled N times instead of once, where N is the total number of pickings being processed. Forward-Port-Of: odoo/odoo#289809
This fixes an issue that could prevent users from signing in to Odoo through the Gmail plugin. The plugin now uses the updated configuration method required by newer Odoo versions, avoiding an authentication error.
Original PR description
In version 19.1+, the ir.config_parameter model no longer has the function get_param. Instead you use get_int, get_str, etc. This was causing a traceback in auth_access_token when a user tries to authenticate with Odoo via the Odoo gmail plugin. Forward-Port-Of: odoo/odoo#289957
Fixes two Slovak asset templates that were linked to the wrong accounting accounts. This prevents assets from being recorded under land or the wrong asset class, improving the accuracy of depreciation and financial reporting for Slovak companies.
Original PR description
In `account.asset-sk.csv` the asset-account column has slipped by one row for the last two entries, so each of those two models pairs an asset account with the accumulated depreciation account of a…
In `account.asset-sk.csv` the asset-account column has slipped by one row for the last two entries, so each of those two models pairs an asset account with the accumulated depreciation account of a different asset class.
As shipped (Slovak account names glossed in English):
sk_asset_basic_herd_and_draft_animals
asset 029000 Ostatný dlhodobý hmotný majetok "Other tangible fixed assets"
depreciation 086000 Oprávky k základnému stádu "Accumulated depreciation, basic herd"
sk_asset_other_tangible_fixes_assets
asset 031000 Pozemky "LAND"
depreciation 089000 Oprávky k ostatnému DHM "Accumulated depreciation, other tangible"
Every other row in the file pairs an account with the accumulated depreciation account that names it: 012000 with 072000, 021000 Stavby ("Buildings") with 081000 Oprávky k stavbám ("Accumulated depreciation, buildings"), and so on. Only these two break the pattern, and both break it the same way.
`account.account-sk.csv`, in the same directory, disagrees with them and is right: it links 026000 Základné stádo a ťažné zvieratá ("Basic herd and draft animals") to the herd model and 029000 to the other-tangible one, and gives 031000 Pozemky no asset model at all, land not being depreciable under Slovak law or any other.
The consequence is not cosmetic.
`account.account.asset_model_ids` creates an asset when a vendor bill line hits the account, and the asset takes its accounts from the model. So on a Slovak database a bill booked to 029000 "Other tangible fixed assets" produces an asset whose value sits on 031000 "Land" and depreciates over six years, while the account that should have carried it never does.
Corrected to the accounts that both the accumulated depreciation side and `account.account-sk.csv` point at:
sk_asset_basic_herd_and_draft_animals asset 026000 Základné stádo a ťažné zvieratá "Basic herd and draft animals"
sk_asset_other_tangible_fixes_assets asset 029000 Ostatný dlhodobý hmotný majetok "Other tangible fixed assets"
Verified by loading the SK chart on a clean database: all ten asset models now pair with their own accumulated depreciation account, and none points at 031000.
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#289542Draft accounting entries that were never posted are now deleted when cancelled, even if their date falls in a locked period. This prevents the system from creating reversal entries for amounts that were never officially booked, avoiding misleading accounting records in payroll, assets, loans, and deferred entries.
Original PR description
_unlink_or_reverse relies on _can_be_unlinked, which only looks at the hash and the lock dates. A draft entry dated in a locked period thus lands in the reversal branch: it is copied, the copy is posted after the lock date and the draft stays behind. The reversal books amounts that were never posted in the first place. Lock dates protect what has been booked. An entry that was never posted can be deleted whatever its date. Seen with hr_payroll_account when a payslip is cancelled after the accountant locked the period without posting the salary entry. Any caller of _unlink_or_reverse (assets, loans, deferred entries) behaves the same.
Live Chat now checks URL matching rules when they are saved and rejects invalid patterns. This prevents website visitors from triggering Live Chat initialization errors caused by misconfigured channel rules.
Original PR description
Currently, an error occurs when a user opens the website and Live Chat is initialized with an invalid URL regex. Steps to Reproduce: - Install `website_livechat` without demo data. - Open `Live Chat`…
Currently, an error occurs when a user opens the website and Live Chat is initialized with an invalid URL regex. Steps to Reproduce: - Install `website_livechat` without demo data. - Open `Live Chat` and, in the `YourWebsite.com` channel, click the `three-dot` menu and select `Configure Channel`. - Go to the `Rules` tab. - Add a rule, select `Welcome Bot` in `Chatbot`, and set an invalid regex such as `*` in `URL Regex` and save. - Click `Join Channel`. - Open the `website`. `re.PatternError: nothing to repeat at position 0 when serializing dict item 'result' ` - when `URL Regex is '['` `re.error: unterminated character set at position 0 when serializing dict item 'result'` When a user opens the website, Live Chat is initialized by checking operator availability and finding the matching country/URL rule [1]. The rule matching checks whether `regex_url` matches the current page URL. It uses `re.search()` [2], which compiles the regex pattern before searching. If `regex_url` contains an invalid pattern such as `*`, `re.search()` raises `nothing to repeat at position 0` because `*` must follow a preceding regex element. Similarly, `[` starts a character set (`[...]`) but has no closing `]`, causing `unterminated character set at position 0`. This commit adds a constraint to validate `regex_url` and reject invalid regex patterns when creating or updating Live Chat rules. [1]- https://github.com/odoo/odoo/blob/f962f1eccecfdbc95375f795174c8f687459b521/addons/im_livechat/controllers/main.py#L87 [2]- https://github.com/odoo/odoo/blob/f962f1eccecfdbc95375f795174c8f687459b521/addons/im_livechat/models/im_livechat_channel.py#L387 sentry-7679767396 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283545
Fixes an issue where partially processing a subcontracted purchase receipt in the Barcode app could block validation with an error. Businesses can now split backorders for subcontracted products without interrupting warehouse receiving workflows.
Original PR description
eproduction steps: - MRP subcontracting + purchase - Subcontracted product with BOM - Create PO with a qty > 1 for this product - Go into barcode - Scan > 0 and < all of the expected quantity - Back…
eproduction steps: - MRP subcontracting + purchase - Subcontracted product with BOM - Create PO with a qty > 1 for this product - Go into barcode - Scan > 0 and < all of the expected quantity - Back out of barcode - Click validate -> Error **OR** Go back into barcode, attempt to validate -> Error Added the StockMove and StockPicking classes to stock_barcode_mrp_subcontracting to extend functions `_subcontracted_produce`, `_clean_merged`, and `split_uncompleted_moves` to use a new context flag, `keep_subcontract_production`. When this flag is set, the move Barcode creates from the split keeps the MO of the move it was split from rather than splitting the MO in two, gives that link up before it is merged away so its cancellation does not cancel the MO, and the MO quantity is resynced with the merged move afterwards. Error thrown here: https://github.com/odoo/odoo/blob/f84eeb3ed1421e0cc07c906ff6444734dc01d35f/addons/mrp_subcontracting/models/stock_move.py#L248 opw-6428928 Forward-Port-Of: odoo/enterprise#131459
This fixes Colombian electronic invoices so the note field sent to DIAN only includes the invoice's terms and conditions. It prevents an internal technical control key from appearing in customer-facing tax XML documents, improving accuracy and compliance of generated documents.
Original PR description
**Steps to reproduce:** - Install the `l10n_co_dian` module and switch to the CO Company. - Disable `Test environment` and enable `DIAN Demo Mode` in the invoicing settings. - Set a `Technical…
**Steps to reproduce:** - Install the `l10n_co_dian` module and switch to the CO Company. - Disable `Test environment` and enable `DIAN Demo Mode` in the invoicing settings. - Set a `Technical control key` on the `Customer Invoices` journal. - Create and confirm an invoice with Terms and Conditions. - Send the invoice to `DIAN`. - Open the generated XML file and observe the `cbc:Note` tag. **Observation:** The `Note` tag contains the `technical control key`. **Expected behavior:** The `Note` tag should only contain the Terms and Conditions value from the invoice. (Confirm with PO [1]) **Root Cause:** At [2] and [3], the code includes the `technical key` in the `Note` tag. [1]: https://www.odoo.com/mail/message/1164671931 [2]: https://github.com/odoo/enterprise/blob/a5f4bde1aa33ea7b796d7ee3f9d713a6c8ab8348/l10n_co_dian/models/account_edi_xml_ubl_dian.py#L664 [3]: https://github.com/odoo/enterprise/blob/a5f4bde1aa33ea7b796d7ee3f9d713a6c8ab8348/l10n_co_dian/models/account_edi_xml_ubl_dian.py#L1600-L1603 opw-6513843 Forward-Port-Of: odoo/enterprise#132606 Forward-Port-Of: odoo/enterprise#131495
This fixes how Belgian payroll determines when sick leave becomes unpaid after 30 days. The update uses the correct relapse status across year boundaries, helping prevent incorrect sick leave classification and payroll results.
Original PR description
The legacy check for unpaid sick time off after 30 days was wrong. The relapse period used was always the one from the latest leave, and not adapted if we span multiple years. Instead, we can just look at the l10n_be_sickness_can_relapse field, which is correctly computed.
This fix ensures PFA variable salary is reduced proportionally in the same way as base salary when calculating Belgian payroll. It helps keep payroll amounts accurate for employees with partial periods or prorated pay, reducing the risk of overpayment or reporting errors.
Original PR description
PFA variable salary should be prorated just like the base salary.
Resetting an expense report to draft now also clears Studio-added approval decisions for posting journal entries. This prevents old approvals from being reused silently, ensuring users must obtain fresh approval after changes.
Original PR description
Currently, when resetting to draft an expense sheet, approval steps added with studio are not reset, silently granting the change Steps to reproduce: - In Studio, add an approval rule on the "Post Journal Entries" button on expense sheet - Create an expense report, submit it and approve it - Click "Post Journal Entries" and approve approve it - Open the journal entry and reset it to draft - Back to the expense sheet, reset it to draft too Issue: The "Post Journal Entries" button is still approved, showing the previous approver with the same approval date. Analysis: Approval entries are dropped on state change by a base automation that Studio builds when the rule is created. However it is only done for sale order, account move and purchase order. opw-6530689 Forward-Port-Of: odoo/enterprise#130692
Reminder emails for overdue invoices linked to child contacts are now sent to the parent contact responsible for accounting. This prevents customers from receiving empty follow-up reports and ensures the correct complete report is included.
Original PR description
Steps to reproduce: - Install accounting and contact - Make a child and parent contact - Create an overdue invoice for the child contact - In the list view, send a reminder for the overdue invoice Current behavior: The child contact gets sent a reminder email with an empty follow up report Expected behavior: Parent contact gets sent a complete email Justification: Previous versions only sent reminder emails to the parent contact. This is because the parent contact is 'responsible' for the accounting of its children. The follow up report also only contains data for the parent contact which is what is sent in the email as the main attachment
This fixes an issue where Peruvian delivery guides for itinerant issuer transfers could be rejected by SUNAT because an establishment code was included when it should be omitted. The delivery guide XML now follows SUNAT rules for this transfer reason, helping users generate and submit these documents successfully.
Original PR description
### Issue before this commit: When issuing a Delivery Guide for an "Itinerant issuer transfer CP", SUNAT rejects the document with error 3416 because the establishment code of the arrival point is…
### Issue before this commit: When issuing a Delivery Guide for an "Itinerant issuer transfer CP", SUNAT rejects the document with error 3416 because the establishment code of the arrival point is being reported. ### Steps to reproduce the issue: 1. Download Stock, l10n_pe_edi_stock and l10n_pe_reports_stock 2. Create one contact setting a valid RUC 3. Go to settings and insert Credentials for Sunat Delivery Guide API 4. Create a new warehouse 5. Go to deliveries and create a new one with: 1. Delivery Address: the contact you created 2. Transport Type: Public Transport 3. Reason for transfer: Itinerant issuer transfer CP 4. Operator (in the PE EDI tab): any 6. Validate the delivery 7. Generate the delivery guide 8. There was an error communicating with the SUNAT service. Details: 400 Client Error: Bad Request for url: https://api-seguridad.sunat.gob.pe/v1/clientessol/test/oauth2/token/ 9. If you download the generated XML, you will see that the node <cbc:AddressTypeCode> inside <cac:DeliveryAddress> is being generated with a default value of "0" but it should not be there ### Cause of the issue: The UBL template automatically forces the <cbc:AddressTypeCode> node with a default value of "0" for any delivery address associated with a RUC (identification type 6). However, SUNAT's validation rules strictly forbid this node when the transfer reason is 18. ### Reason to introduce the fix: Update the XML template to conditionally omit the <cbc:AddressTypeCode> tag inside <cac:DeliveryAddress> whenever the l10n_pe_edi_reason_for_transfer is '18'. This ensures compliance with SUNAT's validation rules and allows the successful generation of the delivery guide. opw-6493067 Forward-Port-Of: odoo/enterprise#131431
This fix prevents Kenya eTIMS invoice numbering from moving backwards after certain failed invoice submissions. It avoids duplicate invoice numbers and reduces the risk of one invoice incorrectly picking up receipt details from another.
Original PR description
Give an eTIMS invoice number back to the sequence only when the failing call is the one that took it, and only when it is still the last one handed out. When sending a customer invoice fails with anything other than a timeout, the number is given back so that it is not consumed for nothing. current issue: - send an invoice, let it time out, so it keeps number N - send other invoices, so the sequence moves past N - send the first one again and let it fail with a non-timeout error - the sequence drops by one and the next invoice sent reuses a number Both documents then sit under the same number. On its next attempt the one that was never accepted finds the other one's filing through selectInvoiceDetails and copies its receipt details. opw-6502563 Forward-Port-Of: odoo/enterprise#131790 Forward-Port-Of: odoo/enterprise#129994
Vietnamese Profit and Loss reporting is updated to match Circular 99/2025/TT-BTC, including the new required investment property gain/loss line and revised numbering. Balance Sheet and Profit and Loss comparisons remain usable while avoiding an unavailable automatic growth percentage column.
Original PR description
Circular 99/2025/TT-BTC (effective FY2026) replaces Circular 200/2014/TT-BTC. The Profit & Loss statement (Mẫu B02-DN) gains a new mandatory line, "Lãi/lỗ của hoạt động bán, thanh lý bất động sản đầu…
Circular 99/2025/TT-BTC (effective FY2026) replaces Circular 200/2014/TT-BTC. The Profit & Loss statement (Mẫu B02-DN) gains a new mandatory line, "Lãi/lỗ của hoạt động bán, thanh lý bất động sản đầu tư" (mã số 21), computed from the new 5117/6327 sub-accounts (see the paired l10n_vn commit). Financial income, financial expenses and the interest memo line shift to mã số 22/23/24 accordingly, the "Net profit from operating activities" formula is updated to include the new line, and all following line numbers/labels are renumbered to match the printed form. The interest memo line itself is renamed from "Chi phí lãi vay" to "Chi phí đi vay" (Borrowing costs), per the gazette text. Also, per revised guidance: - Revenue and cost of goods sold now exclude the new investment property sub-accounts (5117/6327), so those amounts aren't double-counted between the main lines and the new gain/loss line. - Other income is now computed from the "income_other" account type instead of a hardcoded account code, matching how the chart of accounts already classifies it. Both the Balance Sheet and Profit and Loss VN reports also declare an extra "Code" string column (for the printed-form mã số) alongside "Balance". Core's growth-comparison heuristic only turns on the auto growth-% column when there are exactly 2 total columns; with Code+Balance that becomes 4 once a comparison period is added, so the % never renders even though the underlying side-by-side comparison values are fine. account.report exposes filter_growth_comparison as a toggle independent from filter_period_comparison for exactly this case (see Trial Balance and the Deferred reports), so both reports set it to False: period comparison stays available, only the auto growth-% column is suppressed. Task-6518304 Forward-Port-Of: odoo/enterprise#132480 Forward-Port-Of: odoo/enterprise#131085
Vendor bills can no longer be posted with a missing depreciation model when they would automatically create a fixed asset. This prevents inconsistent asset records that users could not properly manage afterward.
Original PR description
**Steps to reproduce:** * Install the **Accounting** and `account_asset` modules. * Configure an account with the **Fixed Asset** type and enable **Can Create Asset**. * Create a vendor bill with a…
**Steps to reproduce:** * Install the **Accounting** and `account_asset` modules. * Configure an account with the **Fixed Asset** type and enable **Can Create Asset**. * Create a vendor bill with a line using this fixed asset account. * On the invoice line, manually remove the **Depreciation Model** so that `depreciation_model_id` is empty. * Post the vendor bill. **Observed behavior**: - An `account.asset` record is created with `model_id = False`. - The asset is in an inconsistent state and cannot be reset to draft. Cause: - [Commit](https://github.com/odoo/enterprise/commit/dced50c696052716a1bf6a3d0c0a9cedde7dbabd) refactored the widget attribute on the `depreciation_model_id` field in `account_move_views.xml` from `m2o_cell_with_extra_m2o_fields` to `m2o_cell_with_extra_fields`, and in doing so accidentally dropped the `required="display_depreciation_model"` attribute that was on the same line. - Without it, the field is no longer enforced at the UI level, so a user can leave it blank and post the move. Fix: - Restore `required="display_depreciation_model"` on the `depreciation_model_id` field in `account_move_views.xml`. - `display_depreciation_model` is True when the account has a depreciation model and the line belongs to an invoice, which is exactly the condition under which an asset would be auto-created on post — matching the original v19.2 behaviour. opw-6585290
This fixes an issue where manufacturing users could be blocked from completing production after generating a lot number and then increasing the quantity to produce. The change ensures valid production updates continue smoothly, reducing interruptions in manufacturing workflows that use lot tracking and storage rules.
Original PR description
Currently, when the user tries to produce a Manufacturing Order after generating a lot and increasing the quantity, they get a user error stating that a lot must be assigned. ## Steps to produce: -…
Currently, when the user tries to produce a Manufacturing Order after generating a lot and increasing the quantity, they get a user error stating that a lot must be assigned. ## Steps to produce: - Install Manufacturing - Go to the setting enable Lots & Serial Numbers and Storage Locations - Create a product 'Soda can' which is tracked by lots - Create a bill of material for Soda can with component as 'Metal sheet' - Create a storage location called 'Fridge' with parent location WH/Stock - Create a Putaway rule for the Soda can to be stored in the fridge when it arrives in WH/Stock. - Create and confirm a Manufacturing Order for soda can - Click 'Generate Lot' - Increase the total quantity to be produced from 1 -> 2 - Click 'Produce All' ## Observed Behaviour: ``` Invalid Operation: You need to supply a Lot/Serial Number for product: - Soda can ``` ## Root cause: When the user confirms the Manufacturing Order, `_apply_putaway_strategy` is called through: ``action_confirm -> _action_assign -> _apply_putaway_strategy`` This happens for both finished-product move lines and component-product move lines. It updates the finished product move lines' destination location to `WH/Stock/Fridge` at: https://github.com/odoo/odoo/blob/fd06c4df5889e23cfb701c12d743b5652141a111/addons/stock/models/stock_move_line.py#L288-L292 However, the `move_finished_ids` destination location remains` WH/Stock`. After the user generates a lot and increases the production quantity using the wizard, `change_prod_qty()` updates the finished move's demand and re-reserves it through `_update_finished_moves()`: https://github.com/odoo/odoo/blob/f586c1dad0b9a7653c45d1f82a9c3989a287c641/addons/mrp/wizard/change_production_qty.py#L77 This calls `_action_assign` on the finished move: https://github.com/odoo/odoo/blob/f586c1dad0b9a7653c45d1f82a9c3989a287c641/addons/mrp/wizard/change_production_qty.py#L49 Within `_action_assign` because finished moves originate from the production location, they bypass the normal reservation logic at: https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/stock/models/stock_move.py#L2086 So `_action_assign() `then tries to reuse an existing move line. However, it only searches for move lines whose `location_dest_id` matches the move's destination location (WH/Stock): https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/stock/models/stock_move.py#L2108-L2117 The existing move line has `WH/Stock/Fridge` as its destination, so it does not match. As a result, a new move line is created for the finished move: https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/stock/models/stock_move.py#L2182 The new move line gets the lot value from the Manufacturing Order: https://github.com/odoo/odoo/blob/f586c1dad0b9a7653c45d1f82a9c3989a287c641/addons/mrp/models/stock_move.py#L557 Later, when Produce All is triggered,` button_mark_done()` calls` _post_inventory()`: https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/mrp/models/mrp_production.py#L2236 Because the finished move already has a `lot_id`, the condition below is not satisfied: https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/mrp/models/mrp_production.py#L1929-L1933 Therefore, the `lot_id` is not assigned to the move line that was created earlier. When `button_mark_done()` validates the finished move lines, it finds that one move line has no lot assigned and raises an 'Invalid Operation' error. ## Solution: Allow the user to produce the Manufacturing Order after changing the quantity. Increasing the quantity is a valid operation, so the user should not be blocked from completing the production afterward. opw-6563498 Forward-Port-Of: odoo/odoo#289725 Forward-Port-Of: odoo/odoo#289171