Monday, September 14, 2026
20 changes · saas-19.1
Resolved issues and error corrections
Fixed an issue that could stop users from creating rental orders when an optional rental product was added after the website rental feature was removed. Rental dates are now handled correctly during price calculation, so sales teams can add these products without encountering an error.
Original PR description
Problem: When the website_sale_renting module is uninstalled and you try to create a rental order with a product that has an optional rental product, the system crashes with a traceback. This happens…
Problem: When the website_sale_renting module is uninstalled and you try to create a rental order with a product that has an optional rental product, the system crashes with a traceback. This happens because the rental start and end dates are passed to the product pricing calculations as plain text strings instead of proper date formats, which breaks the timezone math. Solution: This commit ensures that the rental start and end dates are converted into proper datetime formats before any duration or pricing calculations occur, preventing the error and allowing the products to be added to the rental order smoothly. Steps to reproduce(runbot v18): 1. Install the Rental (sale_renting) and Website modules. 2. Uninstall the website_sale_renting module. 3. Create a rental product and configure another rental product as its optional product. 4. Open the Rental application and try to add the created product to a rental order. 5. A traceback is raised while calculating the rental price. opw-6485776 Forward-Port-Of: odoo/enterprise#130071 Forward-Port-Of: odoo/enterprise#128668
The system now blocks users from creating API keys that are already expired. This helps prevent mistakes when copying examples or entering dates, reducing confusion and avoiding unusable keys.
Original PR description
Prevent creating API keys that are already expired. One typical case is when you copy/paste the documentation and end up creating keys that are already expired, without noticing. Forward-Port-Of: odoo/odoo#286548
This fix ensures the Peru localization uses the same daily exchange rate expected by SUNAT. It prevents mismatched rates in electronic tax reporting, reducing the risk of validation issues for Peruvian companies.
Original PR description
In this commit https://github.com/odoo/odoo/pull/231948 the base functionality of exchange rate fetching was changed in order to have a consistent exchange rate value per day. The problem is that in l10n_pe, SUNAT already does this, setting each day's official exchange rate to be the closing rate of the previous day. Because SUNAT expects the exchange rate being sent to it to be the same as the one generated in the morning, the new logic is defaulting to a mismatched rate instead. This commit overrides the `_get_currency_rates` method with the only change being an inclusive `<=` search instead of the strictly `<`. opw-6468065 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
The Navarra SII tax reporting connection now uses the current web service address after the previous one stopped working. This helps Spanish localization users continue submitting tax information to the Navarra tax agency without service disruption.
Original PR description
The WSDL URL used for the Navarra tax agency SII web service was no longer working. It has been replaced with the updated endpoint 'ssii_1_1'. task-6457647 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285049
The shop page wishlist heart button now keeps a consistent square size in Safari. This prevents product cards from looking misaligned and gives customers a cleaner browsing experience.
Original PR description
The wishlist button on the shop page is misaligned in Safari Steps to reproduce: (in Safari) 1. Install eCommerce 2. Go to the shop 3. The wishlist button (heart icon in the top right corner of each product) is misaligned Issue: The wishlist button (`.o_add_wishlist`) is positioned with `position: absolute` with only top and right offsets set (through the `o-position-absolute` mixin), leaving its width and height on `auto`. https://github.com/odoo/odoo/blob/c4de5361fb207916175332dcf6815c0814ecf09c/addons/website_sale_wishlist/static/src/scss/website_sale_wishlist.options.scss#L62-L65 In other browsers, the button resolves to a 38 x 38px square box but in Safari, it is not a square which misaligns it in the product grid. Solution: Force width and height on `.o_add_wishlist`, so the button resolves to the same box size in every browser. opw-6456552 Forward-Port-Of: odoo/odoo#281935
This fix ensures Mexican electronic payment documents calculate fixed-rate taxes correctly when an invoice is partially paid. It helps prevent tax authority or certification provider rejections caused by mismatched tax base and tax amount values.
Original PR description
When generating a payment complement, tax base and importe coming from the related invoice are prorated by the percentage actually paid, each rounded independently to the currency precision. The…
When generating a payment complement, tax base and importe coming from the related invoice are prorated by the percentage actually paid, each rounded independently to the currency precision. The post-fix step that restores the SAT invariant uses a Tasa-only formula (`base = total / (1 + rate)`), so Cuota (fixed amount per unit) taxes keep mismatched values, ending up with `ImporteDR != round(BaseDR * TasaOCuotaDR)`. This leads to CFDIs rejected by the PAC/SAT. Steps to reproduce: - Create a customer invoice with a Cuota IEPS tax (e.g. 26.2569). - Register a partial payment whose amount is not an exact divisor of the invoice total (e.g. one third). - Send the payment CFDI: the resulting Cuota TrasladoDR has an ImporteDR that does not match BaseDR * TasaOCuotaDR, leading to a rejected CFDI. This commit recomputes `importe` from the prorated `base` for Cuota taxes (bypassing the Tasa post-fix) opw-6087564 Forward-Port-Of: odoo/enterprise#130380 Forward-Port-Of: odoo/enterprise#113395
This fix ensures that when a past point-of-sale order is later invoiced, the data sent to Spain's AEAT tax authority cancels only the original simplified receipt, not the newly created full invoice. This prevents incorrect tax reporting and reduces the risk of accounting discrepancies for Spanish POS operations.
Original PR description
When creating an invoice for a previous POS order, the data sent to AEAT cancels the full invoice instead of the order's simplified invoice. Steps to reproduce: - Open a POS and make a sale without invoicing it; - In the POS, go to the Order tab; - Select the order and fully invoice it. Issue: The AEAT cancellation line added to the pos order actually cancels the full invoice just created [opw-6471927](https://www.odoo.com/odoo/project/49/tasks/6471927) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284277
Restaurant point-of-sale orders are now saved when the bill is printed. This helps prevent order details from being lost if the system reloads or data is refreshed after printing.
Original PR description
We now sync the order when printing bill, to avoid loosing it in case of reload data. task-6527207 Forward-Port-Of: odoo/odoo#286157
This fixes Taiwan B2B e-invoices sent through ECPay so down payment deductions no longer cause tax rounding discrepancies or rejected submissions. The e-invoice now reports the same tax amounts as the posted invoice, improving reliability and consistency for Taiwan invoicing.
Original PR description
Current behavior: -- Sending a B2B invoice that deducts a down payment is rejected by ECPay with "(item tax discrepancy exceeds 1 NT$)", so the invoice cannot be issued at all. When it is accepted,…
Current behavior: -- Sending a B2B invoice that deducts a down payment is rejected by ECPay with "(item tax discrepancy exceeds 1 NT$)", so the invoice cannot be issued at all. When it is accepted, the tax on the e-invoice can still differ from the tax the invoice books. Expected behavior: -- The invoice is accepted, and the tax reported on the e-invoice is the one the invoice booked. Steps to reproduce: -- - Set a company up in Taiwan (TWD) with the ECPay credentials filled in - Create a sale order of 190,630 for a customer with a VAT number - Invoice a 50% down payment through the down payment wizard, then a 30% one, and post both - Invoice the remainder and post it - Send the final invoice to ECPay Cause of the issue: -- _l10n_tw_edi_prepare_item_list rebuilt the tax from the raw amount of every line and rounded that total once. Both steps also disagree with the invoice. Negative lines resulting from downpayments seem to be more strictly checked on ECPay and taxes cannot be re-derived easily by ECPay's system. There is a also a relevant but slightly different issue where the invoice amount is calculated differently but the ECPay page will show a different value. e.g. the invoice rounds each computation key on its own: 50.00 - 16.50 = 33.50 rounds to 34 where the invoice books 50 - 17 = 33. No issue was detected for purely positive lines (a transaction with different products but no downpayment) Fix: -- Use the tax amount the line carries when it has one, and fall back to the raw amount otherwise, so the payload reports what the invoice booked. Send the tax of each line in the ItemTax field. Let ECPay derive its own per-item tax and checks it against the declared total for purely positive lines, and recalculate them for negative so the rounding difference between them is spread one unit at a time across the lines, the way ECPay distributes it. opw-6424237 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281928
Nilvera e-invoices issued in foreign currencies now show the currency subunit label in Turkish within the written amount. This avoids mixed-language invoice notes and helps keep Turkish e-invoices compliant and clear for recipients.
Original PR description
### Issue before this commit: When generating a Nilvera e-invoice in a foreign currency (e.g., EUR), the amount written in words inside the <cbc:Note> tag contained a mix of languages. While the…
### Issue before this commit: When generating a Nilvera e-invoice in a foreign currency (e.g., EUR), the amount written in words inside the <cbc:Note> tag contained a mix of languages. While the numbers were correctly translated to Turkish, the currency subunit label was fetched using the customer's language. This resulted in a partially translated string (like "... SIFIR CENTS") instead of the expected fully Turkish text (like "... SIFIR SENT"). ### Steps to reproduce the issue: 1. Download Accounting and l10n_tr_nilvera_einvoice 2. Create an API KEY: https://docs.google.com/document/d/1EUzvTBnSm9-VwIfBsX299MHGXIVys-uijnsJ1fpz7vI/edit?tab=t.0#heading=h.e6i8a29lff5t 3. Go to a turkish client on the Accounting tab and click on 'Verify' for the Nilvera status 4. Go to currencies and activate EUR (be sure there is also the translation for currency subunit) 5. Go to invoices, create one for the Turkish customer you already verified setting the currency as EUR and send it with Nilvera 6. See that current output is <cbc:Note>YALNIZ : BEŞYÜZDÖRT EUR SIFIR CENTS</cbc:Note> (Turkish numbers with English subunit) but the expected output is <cbc:Note>YALNIZ : BEŞYÜZDÖRT EUR SIFIR SENT</cbc:Note> (Fully Turkish text) ### Cause of the issue: The issue occurs because the currency_subunit_label field is not translated but fetched using the language of the customer in the invoice. ### Reason to introduce the fix: To ensure that the amount in words inside the <cbc:Note> tag is completely formatted in Turkish, complying with Nilvera and local e-invoicing requirements, regardless of the customer language. opw-6523794 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287630
This fixes an issue in the HTML editor where copying and pasting list items with nested bullets could include extra content or lose the outer list structure. Users can now copy complex list content more predictably, preserving the intended formatting.
Original PR description
Problem: Copying content from a list item containing a nested list can either include the unselected nested list or lose part of the copied content. Solution: - Detect whether the whole `<li>` was selected before copying the full list item with its nested lists. - When only part of the list item is selected, copy only the selected content and rebuild the required `<li>` wrapper. - Apply this logic only to list items with multiple top-level children. Steps to reproduce: - Add bullet list with nested list. - CTRL+A - Press Enter twice to create new paragraph. - Paste. - Observe the outer list is lost. task-6438347 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281205
Fixes an inventory issue where products packed in separate packages could appear under the same package during the final delivery step. This helps warehouse teams keep package tracking accurate when deliveries are split and later merged.
Original PR description
Steps to reproduce --- 1. Set the warehouse to deliver in 3 steps. 2. Create a delivery pick not tied to a procurement group (not from a sale order) with a consumable product, and validate it. 3. On…
Steps to reproduce --- 1. Set the warehouse to deliver in 3 steps. 2. Create a delivery pick not tied to a procurement group (not from a sale order) with a consumable product, and validate it. 3. On the pack transfer, put the goods in a package and validate. 4. Duplicate the pick, validate it, put its goods in a second package on the pack transfer, and validate. 5. Open the final delivery: both lines sit in the same package instead of one line per package. Issue --- The two picks share no procurement group, so their delivery moves merge onto a single move keyed by partner. Validating the second pack tops up that already partially reserved move: for a consumable, `_action_assign` builds its lines from `_get_available_move_lines`, which reports the availability of every upstream package without discounting what the move already reserved. https://github.com/odoo/odoo/blob/05af9e6877fd0f044bb46c983fe7300eb1db9307/addons/stock/models/stock_move.py#L1907-L1909 The package already reserved on the first line is therefore offered again and reused, collapsing both lines onto one package. The reserved-product branch below already subtracts the move's own reservation before allocating; doing the same here leaves each package on its own line. opw-6498532 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287820 Forward-Port-Of: odoo/odoo#284956
Fixed an issue where one unschedulable task could incorrectly consume shared assignee availability and push later related tasks far into the future. Project schedules now evaluate each task separately, helping dependent tasks land in the next realistic available slot.
Original PR description
Steps to reproduce: 1. create three tasks A,B & C with the same assignee 2. make tasks B and C depend on A and start on the `date_deadline` of task A 3. set the duration of task B (processed before C) to be longer than the 53-week search window 4. reschedule task A to end after task C starts Problem: A candidate that couldn't be scheduled was putting its assignee in conflict, subtracting the inspected availability while failing from the shared pool. Consequently, every later candidate sharing that assignee got force-placed into the same forward reschedule fallback that lands near the tail of the 53-week search window instead of the next available slot. Solution: Each candidate should be evaluated on its own ability to fit, and the time it occupies should count against the shared pool. opw-6380163 --- Forward-Port-Of: odoo/enterprise#130922 Forward-Port-Of: odoo/enterprise#129326
PINT electronic invoices are now generated through the newer UBL export flow instead of the older BIS 2.0 process. This helps keep invoice exchange aligned with current standards and supports the gradual retirement of legacy export logic.
Original PR description
Problem --------- Currently, PINT uses the old BIS2.0 export. Objective --------- Decouple the exports as an effort to remove the old BIS implementation. no-task --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287793 Forward-Port-Of: odoo/odoo#283585
The website shop now keeps proper spacing around product cards when the Chips layout is used in Dynamic Products snippets outside the shop page. This prevents product displays on pages like the homepage from looking cramped or broken, improving the storefront presentation.
Original PR description
Steps to reproduce: --- - Install the website_sale module with demo data. - Go to /shop and switch the product layout to Chips. - Exit the editor. - On the home page, open the editor and add the…
Steps to reproduce: --- - Install the website_sale module with demo data. - Go to /shop and switch the product layout to Chips. - Exit the editor. - On the home page, open the editor and add the Dynamic Products snippet. Issue: --- The Products snippet has collapsed card padding when the Chips layout is active. Root cause: --- - The Chips card layout defines `--_padding-base` using `var(--o-wsale-products-grid-gap)` with no fallback value. https://github.com/odoo/odoo/blob/4307f657c73a860c755e1499138a878268882f7a/addons/website_sale/static/src/scss/product_tile.scss#L632 - When the Products snippet renders outside /shop, the variable is undefined, causing the `calc()` to resolve to a guaranteed-invalid value which collapses the card padding. All other layouts are unaffected because they either do not use `--o-wsale-products-grid-gap` in their padding chain, or already provide a `16px` fallback at the point of use. https://github.com/odoo/odoo/blob/4307f657c73a860c755e1499138a878268882f7a/addons/website_sale/static/src/scss/product_tile.scss#L407-L410 Solution: --- - 16px matches the default value of `shop_gap` on the website model, ensuring correct padding whenever the variable is not explicitly set. https://github.com/odoo/odoo/blob/4307f657c73a860c755e1499138a878268882f7a/addons/website_sale/models/website.py#L128 ### Before: <img width="1456" height="563" alt="image" src="https://github.com/user-attachments/assets/c28ab183-0af3-4a2a-858d-b85d9995eb07" /> ### After: <img width="1427" height="550" alt="image" src="https://github.com/user-attachments/assets/e0ba80ec-10ad-433f-bf9a-acbaffd56a76" /> opw-6511483 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286185
Time slots that have reached capacity are no longer hidden in Point of Sale and Self Order screens. Customers and staff can still see unavailable slots, making availability clearer and reducing confusion during ordering.
Original PR description
Before this commit: = * Slots that reached their maximum capacity for a given time frame were `hidden` from the `pos_self_order` & `point_of_sale` slot selection dialog. After this commit: = * Slots remain visible but are `disabled` when they reach their maximum capacity in `pos_self_order` & slot remains visible with a `red background` in the `point_of_sale` slot selection dialog. task-6340956 Forward-Port-Of: odoo/odoo#286467
Mexican electronic payment complements now report tax amounts using the decimal precision required for the payment currency. This prevents PAC rejections from Quadrum and improves successful submission of compliant payment documents, including mixed-currency cases.
Original PR description
The 'ImpuestosP' node of the payment complement (Pagos 2.0) is reported with 6 decimals while the SAT expects the amounts to be expressed with the number of decimals supported by the currency of the…
The 'ImpuestosP' node of the payment complement (Pagos 2.0) is reported with 6 decimals while the SAT expects the amounts to be expressed with the number of decimals supported by the currency of the payment ('MonedaP'), as published in the 'c_Moneda' catalog, for example 2 decimals for MXN.
Steps to reproduce:
- Have an MX Company setup with Quadrum as PAC.
- Create and sign a customer invoice in MXN with a 16% IVA tax.
- Register a full payment and send the payment complement to the PAC.
Issue:
Payment will be rejected
```
Code : CRPER654
Message : El importe del campo BaseP que corresponde a Traslado, no tiene la cantidad de decimales que soporta la moneda (MonedaP)
```
Analysis:
Quadrum recently aligned its validation on that rule and now rejects the document having fields with too much decimals and, currently, fields like BaseP, ImporteP are pinned to 6 decimals in the template.
Reporting them with the decimals of the payment currency is not enough when the payment settles a document expressed in another currency: those amounts are also compared with the ones of the related documents converted with 'EquivalenciaDR', and the PAC expects the values truncated or rounded
```
Code : CRP20274
Extra Info : Traslados: La sumatoria de ImporteDR es mayor al ImporteP 1.30.
Valores minimos permitidos: Truncado: 1.29 o redondeado: 1.3
```
opw-6561617
Forward-Port-Of: odoo/enterprise#131319
Forward-Port-Of: odoo/enterprise#131267The Insert in Spreadsheet dialog no longer shows an unnecessary horizontal scrollbar or duplicate scrolling on small screens. This makes selecting spreadsheets smoother and more consistent, especially for users working on smaller displays.
Original PR description
Opening "Insert in Spreadsheet" displayed an unwanted horizontal scrollbar in the spreadsheet selector. On small viewports, it could also produce a second scrollbar alongside the scrollable modal. The selector reused the `o-spreadsheet-templates-dialog` class, whose styles belong to `documents_spreadsheet`. This applied template-only max-height and overflow rules to the selector and made `spreadsheet_edition` rely on styles from a dependent module. Give selector dialogs their own class and keep the template-specific styles on the template dialog. Move the shared pager layout to `spreadsheet_edition` under a dedicated class, and update both pager consumers and the dashboard document selector. Task: 6526781 Forward-Port-Of: odoo/enterprise#130753 Forward-Port-Of: odoo/enterprise#130114
Portal users can now submit product reviews with attached files without hitting a generic Not Found error. This ensures the product review feature works consistently when discussion and ratings are enabled on a website.
Original PR description
A portal user cannot add an attachment when posting a review on a product page: submitting the review with a file attached fails with a generic "Not Found" error, while posting the exact same review…
A portal user cannot add an attachment when posting a review on a product page: submitting the review with a file attached fails with a generic "Not Found" error, while posting the exact same review without an attachment works fine.
Steps to reproduce:
-------------------
* Enable "Discussion and Rating" on a product page (Website > Customize)
* Log in as a portal user and open that product page
* Write a review, attach a file, then submit
> Observation:
"Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again."
Why the fix:
------------
`product.template._get_mail_message_access()` demotes a portal user's create access to 'write' whenever
`env['website'].is_view_active('website_sale.product_comment')` is False, since 'write' access is a deliberate way to block posting when the feature is toggled off for the website. `is_view_active` only picks the correct, website-specific view when `website_id` is present in `self.env.context`; that key is injected by the website frontend only for routes declared `website=True`.
`/mail/attachment/upload` is not `website=True` (attachments used to go through the dedicated, `website=True` `/portal/attachment/add` route, removed when portal's attachment uploader was unified with mail's), so `website_id` is missing from context on that route, `is_view_active` falls back to the generic, shipped-`active="False"` view, and always reports the feature as disabled - even when it is actually enabled for the current website. Create access is then wrongly demoted to 'write', which a portal user never has on `product.template`, so the thread lookup fails and the controller raises `NotFound()`.
Resolve the current website from the request itself (`env['website'].get_current_website()`) and inject its id into context before checking `is_view_active`, instead of relying on `website_id` already being in context. This makes the check accurate regardless of which route triggered it.
opw-6539184
Forward-Port-Of: odoo/odoo#287674
Forward-Port-Of: odoo/odoo#287345Fixed an issue where opening the Project task Pivot view from a saved favorite filter could fail. Users can now analyze their tasks in Pivot view reliably, including when personal task stages are involved.
Original PR description
Since the introduction of `read_grouping_sets` for Pivot views, opening the Pivot view from a saved favorite filter can crash. ### **Steps to reproduce:** - Install Project. - Open My Tasks. - Save…
Since the introduction of `read_grouping_sets` for Pivot views, opening the Pivot view from a saved favorite filter can crash. ### **Steps to reproduce:** - Install Project. - Open My Tasks. - Save the current filter as a favorite. - Switch to the Pivot view. ### **Error:** ``` ValueError: Cannot convert project.task.personal_stage_id to SQL because it is not stored. ``` ### **Root Cause:** Since [commit](https://github.com/odoo/odoo/pull/194413/changes/166a546ec52784c413f5b5d7d29af89d618d6519), Pivot views use `_read_grouping_sets` instead of `_read_group`. `project.task` only remaps `personal_stage_type_id` to the stored `personal_stage_type_ids` in [_read_group](https://github.com/odoo/odoo/blob/5f6fb63d5d7585805642c702d096b2f882e73761/addons/project/models/project_task.py#L2169-L2179), so the remapping is bypassed for Pivot views. The ORM then attempts to group by the non-stored `personal_stage_type_id` relation, leading to the SQL conversion error. ### **Fix:** Mirror the remapping logic in `_read_grouping_sets` so Pivot views use `personal_stage_type_ids` before the ORM generates the SQL query. **opw-6306389** Forward-Port-Of: odoo/odoo#272985