Thursday, September 10, 2026
14 changes · 18.0
Resolved issues and error corrections
Manufacturing order overviews now calculate costs correctly even when related work orders have no employee assigned. This prevents misleading production cost totals and gives teams a more reliable view of manufacturing performance.
Original PR description
Description of the issue/feature this PR addresses: Resolves an issue where Manufacturing Order (MO) summary calculations produced incorrect totals when associated Work Orders had no assigned employee. Current behavior before PR: <img width="2419" height="802" alt="mo-wo-employee-fix-before" src="https://github.com/user-attachments/assets/9a0d64b4-2ca3-4732-9ac7-39673e0ae319" /> Short-circuiting the logic when a work order has no associated employee causes an error in the overview's cost calculation. Behavior after PR: <img width="2389" height="641" alt="mo-wo-employee-fix-after" src="https://github.com/user-attachments/assets/f2b67e84-c149-4d05-be30-f834e62e0882" /> By updating it to no longer short-circuit it now correctly calculates the cost of the manufacturing order. opw-6464637 Forward-Port-Of: odoo/enterprise#128465
This fix stops Odoo from automatically saving a form at the wrong moment when users select a file inside an editable list row on mobile or tablet. It prevents in-progress edits from being silently discarded, especially in places like eLearning course additional resources.
Original PR description
The form view autosaves on 'visibilitychange' (e.g. when the user switches tab/app) to avoid losing unsaved changes. On mobile, opening the native file picker for a binary field also fires 'visibilitychange', which triggered this autosave. When that binary field was part of an editable x2many list, the autosave forced the row out of edition before the file could be selected, silently discarding the edition in progress. Skip the autosave when a x2many field of the root record currently has a row in edition. Can be reproduced in eLearning > course > content > Additional Resources, on mobile devices (must force "desktop mode" in the browser), and on tablets. opw~6517735 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
SEPA Credit Transfer files no longer include an address field that some European banks reject. This restores successful batch payment processing for Austrian, German, and other affected European bank exports while keeping the field only where required.
Original PR description
### Issue before this commit: Generating a SEPA Credit Transfer XML for certain European banks (e.g., Austrian and German banks) fails because the generated XML contains an unexpected `<CtrySubDvsn>`…
### Issue before this commit: Generating a SEPA Credit Transfer XML for certain European banks (e.g., Austrian and German banks) fails because the generated XML contains an unexpected `<CtrySubDvsn>` tag inside the `<PstlAdr>` node, leading to the rejection of the batch payment file. ### Steps to reproduce the issue: 1. Download Accounting and l10n_at 2. Go to settings and activate SEPA Credit Transfer / ISO20022 3. Go to Journals > Bank > set an account number 4. Create an austrian contact (ex. FK Austria Wien AG) and set in the invoicing tab a bank (ex. AT526000071856851733) and set it trusted 5. Go to Bills, create a new one with the contact created and confirm it 6. Then click 'PAY' and select SEPA Credit Transfer 7. Go to Vendors > Batch Payments 8. Create a new one with: 1. Bank as bank 2. SEPA Credit Transfer as Payment Method 3. Add the bill just created 9. Validate and download the XML 10. See that a wrong tag <CtrySubDvsn> is added. This makes some banks refuse it ### Cause of the issue: External commit 30b023d394a5e4de64873188aa1d5961fecccb10 introduced the `<CtrySubDvsn>` tag to support US and CA requirements. However, the change was incorrectly applied to the common `account_iso20022` file, making it leak into standard European SEPA exports where the tag is not compliant with certain strict banking validation rules. ### Reason to introduce the fix: Revert the generic addition of the `<CtrySubDvsn>` tag in the common ISO20022 XML generation and restrict it only to the specific localizations (US/CA) that require it. This brings the `<PstlAdr>` node back to compliance, allowing Austrian, German, and other European banks to successfully process the files. opw-6523035
This fix prevents fiscal position tax mappings from being changed when they are already used by Point of Sale orders. It helps keep historical POS tax records consistent with the taxes that were calculated and paid at the time of sale.
Original PR description
Step to reproduce: - install Accounting and pos - duplicate a existing sales tax, eg: `15%` - create a fiscal position and a mapping, for 21% -> 15% (copy) (sale) - create a customer and set this fp…
Step to reproduce: - install Accounting and pos - duplicate a existing sales tax, eg: `15%` - create a fiscal position and a mapping, for 21% -> 15% (copy) (sale) - create a customer and set this fp to it from "Sale & purchase" page - have a product, avaiable in pos and with 21% tax - for pos, go to settings page: enable `Flexible Taxes` and add our fp to it - start pos and create a order with that customer and product - pay and close the pos - open the pos order from backend and notice, we have 15% tax applied (product has 21% but due to FP it has 15%) - now, delete the FP tax mapping Observation: - the pos order now has 21% tax tag while all calcualtions were done for 15% - the change in FP mapping, affect all pos order, doesn't matter which stage it is in. Cause: - Tax field i.e. `tax_ids_after_fiscal_position` is a non-stored field which uses `map_tax` to get the mapping from pos.order's fiscal position - removing the mapping, we fallback to original tax Fix: - as this is non-stored, we cannot make it stored this in stable version, - For now, we can avoid updates in FP tax mapping if it is used in a pos.order - in master, we can remove this restricetion and make the field stored and only recompute this for draft orders opw-6482441 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fixes an issue where some custom PDF quote headers or footers lost filled-in field values when generating quotations with newer PDF processing libraries. Businesses using PDF Quote Builder can now rely on these fields appearing correctly in generated sales quotes.
Original PR description
Issue: --- When a quotation header/footer PDF has a hierarchical AcroForm field, the value set for that field is silently dropped from the generated PDF Quote when running on pypdf 5.4.0. Steps to…
Issue: --- When a quotation header/footer PDF has a hierarchical AcroForm field, the value set for that field is silently dropped from the generated PDF Quote when running on pypdf 5.4.0. Steps to reproduce: 1- Using a python 3.13 env, install requirements.txt (or otherwise run with pypdf==5.4.0 instead of PyPDF2). 2- Upload a header/footer PDF whose form field is a hierarchical field (`/T`/`/FT`/`/V` on the parent, not on the widget itself). The attachment from the ticket can be used as a sample to reproduce the bug. 3- Create a SO and select that document in the Quote Builder tab. 4- Print -> PDF Quote. 5- The value bound to that field does not appear in the printed PDF. Cause: --- After https://github.com/odoo/odoo/commit/4b02fbd717f62dd5345dad3ffb8d428c1c180007 `_add_pages_to_writer` renames the parent `/Field` object's `/T` when the widget itself has none. PyPDF2's `addPage` inserted the reader's page as is, keeping the widget's `/Parent`. pypdf 5.4.0 deep clones the page instead and ignores `/Parent` at every depth, so the widget loses the link to that field. It ends up with neither `/T` nor `/FT`, hence nothing matches the value mapping. Fix: --- Merge the field into its widget annotations instead: copy the prefixed `/T` and the inheritable keys onto each of them, then drop `/Parent`. The field's own `/T` is left untouched, so a field owning several widgets is filled on all of them. opw-6508728
Portal users can now attach files when submitting product reviews without hitting a generic Not Found error. This ensures the product review attachment flow correctly respects the website's Discussion and Rating setting.
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-6539184Budgets now correctly account for purchase receipts when calculating committed amounts. This prevents the same purchase value from being counted twice, giving users more reliable budget figures.
Original PR description
**PROBLEM** When using receipt with budget, the receipt are not taken into account for computing the `qty_invoiced_table`. This leads the budget committed amount to be false, because we take into account the invoiced qty 2 times, once for the receipt, and one for the purchase order line. **STEP TO REPRODUCE** 1. Install account_budget. 2. Create a budget, with a analytical account. 3. Create a purchase order and budget one line (setting the analytical account on the line). 4. Create a receipt from the purchase by uploading some random pdf and set the type to receipt. 5. Goes to the budget, and see the committed amount is not correct. expected behavior: committed amount should be correct (ie, not repeating the purchase order amount that is already in the receipt). opw-6527042
This fix prevents Odoo from automatically saving and refreshing a form while a mobile file picker is open. Users on Android can now attach files to editable form lines without having their first selection ignored, reducing repeated work and confusion.
Original PR description
Steps to reproduce (on Android): - Open a form with an editable x2many list that has a binary "Upload your file" column (Studio: Lines + File or eLearning → course → content → Additional Resources).…
Steps to reproduce (on Android): - Open a form with an editable x2many list that has a binary "Upload your file" column (Studio: Lines + File or eLearning → course → content → Additional Resources). - Add a line so the form is dirty and valid, without saving (on Additional Resources: set type File and fill Name). - Tap Upload your file and pick a file. The first selection is ignored. Repeating the same upload succeeds. Since 18.0, hiding the browser tab autosaves the form so data is not lost if the tab is discarded. Native mobile file pickers hide the document while they are open. That save reloads the record by default, which remounts the x2many row and destroys the file input before the chosen file is applied. If the new line is still invalid (for example a missing required Name), the save fails and the first pick works, which is why the line must be valid before opening the picker. Skip the hide-tab autosave while a native file picker is open, the same way form dialogs already skip it. Reset the flag when the tab becomes visible again so a later hide can still save. opw-6517735 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Sales teams will now receive the expected upsell warning when multiple smaller timesheet entries together exceed a service product's upsell threshold. This prevents missed follow-up opportunities when work is logged incrementally instead of all at once.
Original PR description
Steps to reproduce: -------------------------------------------- 1. Install `sale_timesheet` module 2. Create a product with: * Type: Service * Invoicing Policy: Prepaid/Fixed Price * Create on…
Steps to reproduce:
--------------------------------------------
1. Install `sale_timesheet` module
2. Create a product with:
* Type: Service
* Invoicing Policy: Prepaid/Fixed Price
* Create on Order: Project & Task
* Upsell Threshold: Set some value (e,g. 600%)
3. Create and confirm the sale order with that product
4. Create and confirm an invoice for the sale order
5. From Recorder Hours Smart button:
* Add two timesheets for hours 3 & 4 (Combined h > Threshold Percentage/100)
Observation:
--------------------------------------------
* When adding a single 7h timesheet, an upsell warning activity is correctly created for the salesperson in the chatter.
* When adding multiple incremental timesheets whose combined duration exceeds the threshold, no upsell warning activity is created.
Issue:
--------------------------------------------
* The `_compute_field_value` method filters on `invoice_status != 'upselling'` before recomputing the new invoice status.
* After the first incremental timesheet is added, the sales order already has the upselling status from the previous computation.
* As a result, subsequent computations skip the upsell activity logic, even though `has_displayed_warning_upsell` is still `False` and no warning activity has yet been created.
Solution:
--------------------------------------------
* Removes the `so.invoice_status != 'upselling'` condition from the filter.
* The order-level `invoice_status` was never the right place to control upsell activity creation. An order can have `invoice_status == 'upselling'` for various reasons (delivered > invoiced), but that doesn't mean all lines have already triggered their upsell warnings.
opw-6117754
Forward-Port-Of: odoo/odoo#266275This fix prevents products from appearing twice in the Point of Sale product list after viewing paid orders. It also ensures product names stay clean on other devices, so internal references do not unexpectedly appear on receipts or invoices.
Original PR description
Since 4d4c1ee9293f, `get_ticket_screen_order_data()` sends the orderlines' `product.product` records along with the paid orders. Two side effects of reloading products that are already in the client…
Since 4d4c1ee9293f, `get_ticket_screen_order_data()` sends the orderlines' `product.product` records along with the paid orders. Two side effects of reloading products that are already in the client store show up on the product screen and on the orderlines. 1. Duplicate cards for templates with variants Steps to reproduce: - A product template with a variant attribute (e.g. "Side": Bread/Rice) - Sell one of its variants and pay the order - In a new session, open Orders, select the "Paid" filter and go back to the product screen Issue: The template is displayed twice in the product list: one card per variant loaded from the paid order. Every template with variants that appears in the fetched orders is duplicated the same way. Cause: The product screen shows one card per template because `processProductAttributesByProducts()` marks every variant but one as `available_in_pos = false` on the client-side records. `loadData()` rebuilds an already-loaded record from the raw payload, so the server's `available_in_pos = true` overwrites the client-side flag and the variant reappears as its own card. The ticket screen was the only product-loading path not re-running the grouping afterwards; the barcode scan and "Search more" paths already do. Fix: Re-run `processProductAttributesByProducts()` on the products returned by `get_ticket_screen_order_data()` in `_fetchSyncedOrders()`, as the other loading paths do, so the reloaded variants are collapsed again. 2. Internal reference in the product name on the other devices Steps to reproduce: - Two devices (or two cashiers) on the same PoS session - A product with an internal reference, sold in a paid order - Device A: open Orders, select the "Paid" filter - Device B: add that product to a new order Issue: On device B the orderline is named "[REF] Product" instead of "Product", and that name is stored in `full_product_name`, so it also ends up on the receipt and on the invoice line. The name flips back to "Product" as soon as the record is reloaded through a regular loader. Cause: `callRelated()` dispatches every static-model record of a payload to `pos.config.notify_synchronisation()`, which reads those records with a plain `read()` and broadcasts them to the other devices of the session, where `loadData()` overwrites the existing records. Unlike every PoS product loader (`_load_product_with_domain()`, `find_product_by_barcode()`, the data service `read`/`search_read`), this read is done without `display_default_code=False`, so a product reaches the other devices with its reference in `display_name`. The gap was latent since the device synchronisation (0c8f0d730465): before 4d4c1ee9293f no product record went through that path. Fix: Read the synchronised records with `display_default_code=False` in `notify_synchronisation()`, as the PoS loaders do. opw-6543906 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Odoo now handles empty or incomplete attachment records safely when users open image-related features. This prevents the Media Dialog and Chatter from crashing if an upload was interrupted or a malformed email created an empty file.
Original PR description
Problem: Interrupted uploads or malformed email payloads can create 0-byte binary `ir.attachment` records where the `checksum` is `False`. Accessing the computed `image_src` field on these records…
Problem:
Interrupted uploads or malformed email payloads can create 0-byte binary `ir.attachment` records where the `checksum` is `False`. Accessing the computed `image_src` field on these records triggers a `TypeError: 'bool' object is not subscriptable` when attempting to slice `attachment.checksum[:8]`. This crashes the Media Dialog and Chatter.
Purpose:
Add a fallback boolean guard to `attachment.checksum` inside `_compute_image_src` so that empty attachments evaluate safely to a string ('0') instead of raising a traceback, allowing the UI to render gracefully.
Steps to Reproduce on Runbot:
1. Go to Settings > Technical > Database Structure > Attachments.
2. Create a new record: Name: `test.png`, Type: `File` (Binary), File Content: [leave empty], Is public document: Checked.
3. Open any record with a Chatter (e.g., Contact or CRM Lead) and click "Insert Image" to open the Media Dialog.
4. The system attempts to evaluate `image_src` and throws the `TypeError`.
Notes:
A test (`test_compute_image_src_empty_checksum`) was added to `test_ir_attachment.py`
opw-6482674French PPF response files for Flow 10 are now processed instead of only being stored. Rejected reports now show the rejection reason, move to the correct status, and can be corrected and resent by users.
Original PR description
Flow 10 PPF responses were stored as attachments without being processed. Consequently, rejected reports remained marked as sent, the rejection reason was not shown to users, and corrected reports could not be submitted again. Process the PPF response codes, update the flow state, and log the returned details in the chatter. Keep response attachments separate from the outgoing payload and allow rejected reports to be manually resent with their original moves and a new transmission identifier. no task id
The spreadsheet component was updated to the latest version with fixes for merged-cell copy and paste, combo chart value display, and invalid search-and-replace ranges. This improves day-to-day reliability for users working with spreadsheets and reduces errors in common editing and reporting tasks.
Original PR description
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/d0329aa335 [REL] 18.0.82 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0)…
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/d0329aa335 [REL] 18.0.82 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/f2e0edf1a7 [FIX] clipboard: typo on copy/paste on a merge [Task: 6515850](https://www.odoo.com/odoo/2328/tasks/6515850) https://github.com/odoo/o-spreadsheet/commit/852fa2e5de [FIX] charts: show value do not work for combo chart [Task: 6528059](https://www.odoo.com/odoo/2328/tasks/6528059) https://github.com/odoo/o-spreadsheet/commit/d9ead2f95f [FIX] search and replace: manage invalid range [Task: 6483222](https://www.odoo.com/odoo/2328/tasks/6483222) Co-authored-by: Florian Damhaut (flda) <flda@odoo.com> Co-authored-by: Anthony Hendrickx (anhe) <anhe@odoo.com> Co-authored-by: Alexis Lacroix (laa) <laa@odoo.com> Co-authored-by: Lucas Lefèvre (lul) <lul@odoo.com> Co-authored-by: Adrien Minne (adrm) <adrm@odoo.com> Co-authored-by: Ronak Mukeshbhai Bharadiya (rmbh) <rmbh@odoo.com> Co-authored-by: Dhrutik Patel (dhrp) <dhrp@odoo.com> Co-authored-by: Rémi Rahir (rar) <rar@odoo.com> Co-authored-by: Pierre Rousseau (pro) <pro@odoo.com> Co-authored-by: Vincent Schippefilt (vsc) <vsc@odoo.com> Co-authored-by: Marceline Thomas (matho) <matho@odoo.com>
Customers can now complete checkout when a loyalty reward adds a free product to their cart. This prevents valid promotional items from being mistaken for blocked zero-priced products, reducing checkout friction during Buy X Get Y campaigns.
Original PR description
Steps to reproduce: - In a fresh 18.0 database (not runbot), install `website_sale_loyalty` - Website > Configuration > enable "Prevent Sale of Zero Priced Product" - Set up a "Buy X Get Y" loyalty…
Steps to reproduce: - In a fresh 18.0 database (not runbot), install `website_sale_loyalty` - Website > Configuration > enable "Prevent Sale of Zero Priced Product" - Set up a "Buy X Get Y" loyalty program to reward a free product (the 3 Large Cabinet one from demo data will work) - Go to website and add one of the product to cart - Go the the shop page and increase the quantity one at a time Issue: The first additional product over the threshold works fine, but adding one more after that will disable the checkout button and give error message "Warning! Some products in your cart are not available for purchase in your country. Please remove them or contact us." Cause: When the reward line is first added, it has the default `price_unit` of the product and 100% discount. On subsequent changes, `price_unit` is reset to `0.00` and gets caught by this check introduced in #284959. The unit_price of the reward order line is changed to 0 in `_reset_loyalty()@sale_loyalty/models/sale_order_line.py` and is needed for other logic to successfully complete. Fix: Another exception is added to `_get_zero_priced_lines()` so order lines from reward products are exempt from the check, using `is_reward_line` opw-6528237 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr