Thursday, June 25, 2026
20 changes · saas-19.3
Enhancements to existing features
This update automatically sends emails to companies when their Stripe connected accounts are flagged for potential restrictions due to KYC compliance. Stripe handles verification, and these emails alert businesses to address any required documentation updates, preventing account limitations. This ensures timely compliance and minimizes disruption to business operations.
Original PR description
When a company tries to create a connected account, some official documentation need to be submitted to Stripe. Stripe takes care of the KYC steps and might restrict some account which don't meet the requirements. Odoo receives the details about the error and the date of the restriction. This task aims at sending automatic emails to the said companies to let them know that they need to fix the identified issues. task: 5441662 Forward-Port-Of: odoo/enterprise#107918
Resolved issues and error corrections
This change fixes a problem where importing certain valid UBL invoices could fail if a line had no quantity and no value. Those empty lines are now safely skipped, so the invoice import completes without showing an error message.
Original PR description
### Issue: Importing a UBL invoice containing a line with `LineExtensionAmount=0`, `InvoicedQuantity=0` and a non-zero `PriceAmount` failed with a `ZeroDivisionError`, reported in the chatter as an…
### Issue: Importing a UBL invoice containing a line with `LineExtensionAmount=0`, `InvoicedQuantity=0` and a non-zero `PriceAmount` failed with a `ZeroDivisionError`, reported in the chatter as an import error Such lines are valid UBL but carry no meaningful value, so they are silently skipped after the fix ### Cause: After this commit: https://github.com/odoo/odoo/commit/a7f77f3cfc42764328e7da73a60df8d4cafc968f The `line_extension_amount` was able to go in new parts of the code with a 0.0 value When `line_extension_amount` is set and `invoiced_quantity` is 0, `quantity` is computed as `subtotal * price_quantity / (...)` which resolves to 0 since `subtotal` is also 0 `price_unit = subtotal / quantity` then divides by zero ### Steps to reproduce: - Install `l10n_be` - Import a UBL invoice with a line where `LineExtensionAmount=0`, `InvoicedQuantity=0` and `PriceAmount` is non-zero (You can use the xml on the ticket) Before the fix, the import failed with an error in the chatter opw-6234453 Forward-Port-Of: odoo/odoo#271001
This update corrects how manufacturing work orders are scheduled when several tasks share the same upstream blocker. It now keeps already planned work orders in place, preventing later planning steps from rearranging them in a way that could violate dependencies.
Original PR description
Bug introduced in: https://github.com/odoo/odoo/commit/cfc5c998035b4268c36f5097782888e18e21b4fe Steps to reproduce the bug: - Create a product with a BoM with operation dependencies enabled - Add 4…
Bug introduced in: https://github.com/odoo/odoo/commit/cfc5c998035b4268c36f5097782888e18e21b4fe
Steps to reproduce the bug:
- Create a product with a BoM with operation dependencies enabled
- Add 4 operations on the same workcenter:
- opA: no blocker
- opB: blocked by opA
- opC: blocked by opA
- opD: blocked by opC
- Confirm a manufacturing order from this BoM
- Click Plan
Problem:
opA was scheduled after opB, violating the dependency.
`_plan_workorders` starts planning from the "leaf" workorders (those with no dependents). Given the structure above, the initial set is [opB, opD]. Processing opB first correctly plans opA then opB. But processing opD triggers a recursive chain opD→opC→opA which calls `action_unplan(opA)` and replans it from scratch. By then, opB already occupies the workcenter slot that opA originally held, so opA ends up scheduled after opB.
Solution:
Add `and not wo.is_planned` to the filter on `blocked_by_workorder_ids` in the recursive call inside `_plan_workorders`. Workorders that are already planned are skipped instead of being unplanned and replanned, preserving the correct order.
opw-6299179
Forward-Port-Of: odoo/odoo#271565This update resolves several problems affecting Cashmatic payment certification in Point of Sale. It prevents payment tokens from expiring during long checkout sessions, improves error handling when cash cannot be returned, and avoids delays when the device is unreachable.
Original PR description
First issue: While paying if a user takes more than 15min the token is revoked. Second issue: When cancelling a payment where a user has already inserted money and there is an issue with giving back the money, no popup was shown on the PoS. Third issue: Fetch took too long when the device was not reacheable. 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#268296
The executive summary now counts the start and end dates as part of the reporting period, instead of leaving out one day. This corrects date-based figures such as Average Debtor Days so report results match the actual calendar span.
Original PR description
`_report_custom_engine_executive_summary_ndays` returned `date_to - date_from`, which is the gap between the two dates, not the count of days they span. For example April 2026-04-01 to 2026-04-30 will returned 29 instead of 30, making Average Debtor Days incorrect. Add +1 so the day count is inclusive of both endpoints, matching the rest of the report's date handling. opw-6215362 Forward-Port-Of: odoo/enterprise#119346 Forward-Port-Of: odoo/enterprise#118953
This change prevents the web editor from failing when it processes extremely large documents. It improves reliability and response time by avoiding a browser limit that could otherwise make the page unresponsive.
Original PR description
For complex content, descendants(root) can return more than 100K elements. Using the spread operator expands all descendants into individual function arguments, which may exceed the JavaScript's argument limit and trigger a "Maximum call stack size exceeded" error. Replace with push() each node to the targetNodes. ||Before|After| |-|-|-| |getTargetNodes|Page Unresponsive|585 ms| Related ticket: opw-6303814 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#271250
When users quickly save a new calendar event, the title they typed is now preserved instead of sometimes being replaced by the default “no title” label. This prevents confusing event entries and ensures the saved calendar information matches what the user entered.
Original PR description
When creating an event using the quick create form from the calendar view if the user saves the record while the title is still being edited (using alt+c) the record will be saved with the default title: "(no title)" The code currently relies on the record data being up to date by the time onRecordSave is reached. However in the case of a text field, it is only saved when blurred. While there is a mechanism to blur the field when saving using a hotkey, it is completely asynchronous from the save logic of the form. To ensure all fields have comitted their data at save time, the framework has a mechanism to "request changes" which notifies all fields to update the record with their latest value and waits for them to do so. We can simply reuse this mechanism to ensure the data is up to date at recordSave time already, as we don't expect fields to have any changes after it. task-6321702 Forward-Port-Of: odoo/odoo#271850 Forward-Port-Of: odoo/odoo#271473
This change prevents overtime periods from overlapping when an employee’s attendance spans more than one day. It avoids an error that could interrupt work entry generation and ensures overtime is calculated correctly, especially around day boundaries.
Original PR description
**Problem:** When an attendance has overtimes across multiple days, those overtimes can overlap when calculating their intervals. There will always be rounding errors since only the durations are…
**Problem:** When an attendance has overtimes across multiple days, those overtimes can overlap when calculating their intervals. There will always be rounding errors since only the durations are saved to 3 decimals, but this is normally fine since the durations are accumulated when calculating the next interval. However, on a day boundary in the employee timezone, the end of the interval is forced to the end of day, which incidentally removes the rounding error. This causes the overlap when calculating the next interval since its start will be based on the rounded duration, not the actual end of day. **Steps to Reproduce:** - Configure an overtime rule where >8 hours is considered overtime, and a second rule applies to non-working days - Set Overtime Rule on employee "Anita Oliver" - Set employee work entry source to "Attendances" - Create an attendance that exceeds 8 hours in a day and crosses into a non-working day and creates enough of a rounding error (see unit test) -> Traceback error: `ValueError: Expected singleton: hr.attendance.overtime.line(1, 2)` **Solution:** Add an additional check to ensure the overtime cannot start on the previous day. opw-6067969 Forward-Port-Of: odoo/enterprise#119672 Forward-Port-Of: odoo/enterprise#118570
This update fixes an issue in the Time Off calendar view where users could not always scroll all the way to the bottom of the page. It improves the reliability of the page so employees can navigate the calendar more easily and reach all content as expected.
Original PR description
This PR expected to solve scrolling issue in Calender View Time Off module. In the Time Off module's Calender View, users aren't able to scroll down all the way to the bottom page. This behavior is intermittent so it's not deterministic. Root cause: This bug occurred in the earlist version and it might be related to an updated of Framework JS. task: 6328706
This update makes module installation more reliable when some referenced records were deleted earlier. It also fixes a case where new chart-of-accounts data could be incorrectly ignored during first-time setup, helping ensure accounting settings are applied as expected.
Original PR description
Installing a new module should be safe even when the module contains new data for records that have been deleted. It is not the responsibility of the localization to make sure of that. The fix in `l10n_sa_edi` had 2 issues: * calling `self.env.ref` instead of `self.ref` * Checking for the existence of records even in the case of installing the CoA for the first time on a company, which obviously doesn't contain anything. This results in always ignoring the data. Forward-Port-Of: odoo/odoo#271818
This update prevents the Czech accounting report setup from trying to take over records that already exist when reloading the chart of accounts. It helps avoid conflicts during setup and keeps existing accounting data safer and more consistent.
Original PR description
It is the burden of the CoA framework to check for that. See community commit for more information. Forward-Port-Of: odoo/enterprise#121643
The system now only detaches attachments when that is appropriate for outgoing invoices. Incoming XML files received through EDI stay linked to bills so they can still be included in bulk exports, while the special Italian Tax Integration XML flow continues to work correctly.
Original PR description
The feature introduced in odoo/enterprise#78429 allows users to detach attachments from moves, primarily to facilitate the regeneration and re-sending of outgoing XMLs (e.g., sales invoices) without needing to delete the original attachment. However, detaching should not apply to incoming XML attachments on bills that originate from EDI import, as these attachments are the received source document and are never regenerated by the system. Detaching them inadvertently prevents their inclusion in bulk XML exports. An exception exists for Italy: businesses need to send Tax Integration XMLs back to the SdI. In this specific case, detaching the Tax Integration XML is appropriate and ensures the bulk export finds the latest, correct attachment. Ticket [link](https://www.odoo.com/odoo/project.task/5062132) opw-5062132 Forward-Port-Of: odoo/odoo#270896 Forward-Port-Of: odoo/odoo#239701
This update fixes an issue where adding an image to a vendor bill could block users from opening bill options or printing the original bill. It ensures image attachments are handled correctly so everyday accounting actions continue to work without access errors.
Original PR description
**Steps to reproduce:** - Install the `accountant` module and log in as admin. - Create and confirm a vendor bill. - Send an image in the chatter of the vendor bill. - Open a new tab and log in as a…
**Steps to reproduce:** - Install the `accountant` module and log in as admin. - Create and confirm a vendor bill. - Send an image in the chatter of the vendor bill. - Open a new tab and log in as a demo user. - Open the same vendor bill. - Click the gear icon. **Observation:** An access error is raised, and the gear icon is not accessible. **Root Cause:** At [1], the method `_should_attach_to_record` incorrectly excludes image attachments, causing them to be treated as `extra_files_data` at [2]. As a result, in `_fix_attachments_on_record_from_files_data` at [3], these attachments are assigned `res_model=False` and `res_id=0`. When the code tries to access these attachments at [4], it leads to an access error. Additionally, when we try to print `Original Bills`, we get the same access error at [5], and later the code calls the `browse` function on `self.env[attachment.res_model]`, but for `extra_files_data` we set the `res_model=False`, which results in a `KeyError`(see [6]). **Fix:** This commit prevents the error and ensures that users can access the gear icon when an image is attached in the chatter and print the `Original Bills`. [1]: https://github.com/odoo/odoo/blob/95864190a71b68eb10ae59ae8f38ac35b0cb6a97/addons/account/models/account_document_import_mixin.py#L416-L429 [2]: https://github.com/odoo/odoo/blob/95864190a71b68eb10ae59ae8f38ac35b0cb6a97/addons/account/models/account_move.py#L6668-L6672 [3]: https://github.com/odoo/odoo/blob/95864190a71b68eb10ae59ae8f38ac35b0cb6a97/addons/account/models/account_document_import_mixin.py#L409-L414 [4]: https://github.com/odoo/odoo/blob/7e874e7db30e05a02d6eeb26d9d67ed6176b9704/addons/account/models/account_move.py#L6944-L6949 [5]: https://github.com/odoo/odoo/blob/7e874e7db30e05a02d6eeb26d9d67ed6176b9704/addons/account/models/ir_actions_report.py#L30-L34 [6]: https://github.com/odoo/odoo/pull/261463#issuecomment-4602692978 opw-6119155 Forward-Port-Of: odoo/odoo#261463
The embedded Mercado Pago payment form now follows the website’s language instead of always appearing in English. This creates a more consistent checkout experience for customers and can help reduce confusion during payment.
Original PR description
The Mercado Pago Bricks SDK was always initialized with the `en-US` locale, so the embedded (inline) payment form rendered in English for every customer regardless of their website language. Resolve the Bricks locale from the website language instead. The locale is keyed by country, since each supported country maps to a single locale (e.g. Brazil is always pt-BR), so the language's country part is enough to resolve it. For the shared es_419 language, which carries no country, fall back on the company's country, and default to en-US for unsupported languages. task-6281783 Forward-Port-Of: odoo/odoo#269406
This update corrects a problem where new accounting tags weren't being properly applied during the Danish localization module's setup. Moving the tag mapping process to occur after the database is loaded ensures all tags are present, preventing errors and data inconsistencies. This resolves a foreign key violation that previously caused issues with account cleanup.
Original PR description
The tag remapping was running in pre-migrate, before the module's data files are loaded. This caused the tag swap to be silently skipped for any new tag that didn't exist yet, and the subsequent…
The tag remapping was running in pre-migrate, before the module's data files are loaded. This caused the tag swap to be silently skipped for any new tag that didn't exist yet, and the subsequent cleanup to fail with a FK violation on account_account_account_tag.
```py
File "/home/odoo/src/odoo/saas-19.2/odoo/sql_db.py", line 417, in execute
self._obj.execute(query, params)
psycopg2.errors.ForeignKeyViolation: update or delete on table "account_account_tag" violates foreign key constraint "account_account_account_tag_account_account_tag_id_fkey" on table "account_account_account_tag"
DETAIL: Key (id)=(356) is still referenced from table "account_account_account_tag".
```
Moving to post-migrate ensures all new account tags are present in the database before the remapping and cleanup run.
upg-[4341331]
[4341331]: https://upgrade.odoo.com/odoo/upgrade.request/4341331?debug=1
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#269809This update resolves an issue where Purchase Orders remained flagged as 'Late Receipts' even after a backorder was cancelled. The fix now correctly excludes both 'done' and 'cancel' pickings when determining if a purchase order is overdue, ensuring accurate reporting and a cleaner user experience. This prevents unnecessary alerts and improves order management.
Original PR description
Steps to reproduce: ------------------- - Create a Purchase Order with an expected Arrival date in the past - Confirm the Purchase Order - Validate the receipt partially and create a backorder -…
Steps to reproduce: ------------------- - Create a Purchase Order with an expected Arrival date in the past - Confirm the Purchase Order - Validate the receipt partially and create a backorder - Cancel the generated backorder - Open the Purchase Orders list and check the 'Late Receipts' Issue: ------ The Purchase Order still appears in the 'Late Receipts' filter even though there is no remaining receipt to process. Cause: ------ The 'Late Receipts' filter relies on the computed search field `is_late`: https://github.com/odoo/odoo/blob/324df67c099ab18c6fe7c8f77212cf809debf383/addons/purchase/views/purchase_views.xml#L439 The search domain for this field is generated by `purchase.order._search_is_late()`: https://github.com/odoo/odoo/blob/324df67c099ab18c6fe7c8f77212cf809debf383/addons/purchase/models/purchase_order.py#L176 In `purchase_stock`, `_get_domain_is_late()` extends the base domain to identify Purchase Orders that still have receipts pending: https://github.com/odoo/odoo/blob/324df67c099ab18c6fe7c8f77212cf809debf383/addons/purchase_stock/models/purchase_order.py#L264-L267 After a partial receipt: - the original receipt is in state `done`, - a backorder is created and linked to the Purchase Order, - the backorder is later cancelled and moves to state `cancel`, - the Purchase Order line still has `qty_received < product_qty`. The existing domain excludes only `done` pickings when determining whether a receipt is still pending. As a result, a cancelled backorder is still treated as an unfinished receipt, causing the Purchase Order to remain visible in the 'Late Receipts' filter. Fix: ---- Exclude both `done` and `cancel` pickings when determining whether a Purchase Order has pending receipts. A cancelled backorder indicates that the remaining quantity will not be received through that transfer. Therefore, once all related pickings are either completed or cancelled, the Purchase Order should no longer be considered late. --- opw-6266046 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#268488
This update resolves a technical issue impacting the processing of Mexican tax invoices (CFDI). The system was struggling to handle complex cancellation scenarios due to a limitation in the database index. Switching to a different index type allows for smoother and more efficient handling of these invoices, particularly those with multiple related documents.
Original PR description
The field `l10n_mx_edi_cfdi_origin` can contain a large number of associated UUIDs, especially in complex cancellation scenarios. The default B-tree index fails when this field exceeds 2704 bytes, which occurs after approximately 20 UUIDs. By switching to a trigram index, we avoid the entry size limit of PostgreSQL's B-tree nodes. This ensures that invoices with many related documents can be processed while maintaining efficient search performance for partial matches on this field. **Video before the fix:** https://youtu.be/24u0HbxwIH8 **Video after the fix:** https://youtu.be/sUelv1HZMvI Forward-Port-Of: odoo/enterprise#118868
The Time Off Balance report was incorrectly calculating remaining days when overlapping allocations existed. This fix ensures the report accurately reflects the remaining time off by correctly deducting leave days from allocations based on their overlap periods. This prevents overestimation of available time off.
Original PR description
The Time Off Balance report shows incorrect remaining days when overlapping allocations exist and a leave only overlaps the later one. ### **Steps to reproduce:** 1) Install time off app. 2) Create a…
The Time Off Balance report shows incorrect remaining days when overlapping allocations exist and a leave only overlaps the later one. ### **Steps to reproduce:** 1) Install time off app. 2) Create a simple time off type. - Create Allocation A (10 days, 01-01-2024 to 31-12-2025) - Create Allocation B (10 days, 01-01-2025 to 31-12-2026) 3) Create a leave of 1 day on 01-01-2026 4) Open the Balance report ### **Observed Behavior:** The report shows 20 remaining days. ### **Expected Behavior:** The report should show 19 remaining days (20 allocated - 1 taken). ### **Cause:** In the taken_per_allocation CTE at [1], each leave is joined to every allocation it overlaps. The [fifo_balances] CTE then uses the formula: ``` GREATEST(alloc_days - GREATEST(taken - prior_cumulative_alloc, 0), 0) ``` This subtracts the prior allocation capacity (A = 10 days) from the taken count (B = 1 day). Since 1 - 10 = -9, GREATEST(-9, 0) = 0, so zero days are deducted from B. The formula wrongly assumes that prior allocations can absorb leaves that do not overlap with them. [1]- https://github.com/odoo/odoo/blob/f0fa79fa21d2005dd5cd132c18b2be45424166a6/addons/hr_holidays/report/hr_leave_employee_type_report.py#L126-L142 [fifo_balances]: https://github.com/odoo/odoo/blob/f0fa79fa21d2005dd5cd132c18b2be45424166a6/addons/hr_holidays/report/hr_leave_employee_type_report.py#L145-L164 ### **Fix:** Ensure that leaves are only deducted from allocations they actually overlap by calculating the balance using the delta of cumulative leaves within an overlap group. This prevents earlier allocations from absorbing leaves that occur outside their validity period. **opw-6150161** Forward-Port-Of: odoo/odoo#271596 Forward-Port-Of: odoo/odoo#263029
This update fixes an issue where changing the quantity of a Purchase Order Line (POL) in Multi-Step Routes incorrectly updated the associated receipt quantity. The fix ensures that quantity adjustments are accurately reflected, preventing discrepancies between the sale order and the purchase order receipt.
Original PR description
### Steps to reproduce: - In the settings enable: Multi-Steps Routes - Inventory > Configuration > Warehouse Management > Routes - Unarchive MTO - Create a storable product P with MTO buy and a set…
### Steps to reproduce: - In the settings enable: Multi-Steps Routes - Inventory > Configuration > Warehouse Management > Routes - Unarchive MTO - Create a storable product P with MTO buy and a set vendor - Create and confirm a sale order for 1 unit of P - Confirm the assocaited PO and change the pol quantity from 1 to 10 > the associated receipt is updated from 1 to 10 - Change the pol quantity from 10 to 7 #### > The quantity on the receipt is updated from 10 to 16. ### Cause of the issue: Changing the quantity of the POL will adapt the picking related quantity via these lines: https://github.com/odoo/odoo/blob/3bf89b4f467390807c20f7b007a875a77542e76f/addons/purchase_stock/models/purchase_order_line.py#L115-L117 https://github.com/odoo/odoo/blob/3bf89b4f467390807c20f7b007a875a77542e76f/addons/purchase_stock/models/purchase_order_line.py#L342-L349 by creating new stock moves to be merged: https://github.com/odoo/odoo/blob/3bf89b4f467390807c20f7b007a875a77542e76f/addons/purchase_stock/models/purchase_order_line.py#L220-L251 Now, the issue is that this flows relies both on a negative `qty_to_attach` of `1 - 10 = -9` and a positive `qty_to_push` of `7 - 1 = 6`. However, the `qty_to_attach` is only used if is positive: https://github.com/odoo/odoo/blob/3bf89b4f467390807c20f7b007a875a77542e76f/addons/purchase_stock/models/purchase_order_line.py#L243-L251 The receipt is therefore updated by a `+6` move to push but not by the `-9` move to attach. Leading to a 10 -> 16 rather than 10 -> 7 result. opw-6218307 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270547 Forward-Port-Of: odoo/odoo#264994
This update fixes an issue where the VAT displayed in the product information popup was incorrect, leading to an inaccurate total price. The fix ensures that the product's tax rate is correctly calculated based on the pricelist and fiscal position settings, resulting in the correct total price being shown.
Original PR description
Steps to reproduce: ------------------- 1. Create a product with a tax (e.g. 15%). 2. Create a pricelist that changes the price (e.g. 100 to 200). 3. Create a fiscal position mapping the tax (e.g.…
Steps to reproduce: ------------------- 1. Create a product with a tax (e.g. 15%). 2. Create a pricelist that changes the price (e.g. 100 to 200). 3. Create a fiscal position mapping the tax (e.g. 15% to 30%). 4. Add the pricelist and the fiscal position in PoS. 5. Add the product to the cart, and select the tax and the pricelist created in the previous steps. 6. Long press on the product to see its info. The price should be 200 now after selecting the pricelist. Also the tax should be 30% bc of the FP mapping, i.e. total price should be 200 + 30% = 260. However, we observe that VAT shows 15 (15%) instead of 60 (30%), and Price incl. Tax shows 230 instead of 260. What's happening: ----------------- On the frontend, `getTaxDetails()` is called with no options, so it uses the product `list_price` (100) and `taxes_id` (15%), giving VAT = 15. Alos, on the backned, `self.taxes_id` is used directly to compute the taxes, even though the pricelist price is correct (200), fiscal position is ignored, hence 200 + 15% = 230 instead of 200 + 30% = 260. The fix: -------- On frontend, we pass the pricelist and fiscal position to `getTaxDetails`, and compute the tax name from the mapped taxes. On the backend, we read the `fiscal_position_id` from the context and apply the tax mapping, so the correct taxes are used. opw-6200632 Forward-Port-Of: odoo/odoo#271396 Forward-Port-Of: odoo/odoo#266012