Wednesday, September 23, 2026
20 changes · 19.0
Enhancements to existing features
Users can now manage IAP-related settings, such as credit purchases, low balance warnings, auto-refill, and SMS options, from one dedicated page. This reduces switching between different configuration areas and makes paid service management easier to understand and maintain.
Original PR description
This commit aims at improving how users interact with IAP. Instead of having to configure some details (low balance warnings, auto-refill, SMS) on its db, and others on IAP (buying of credits), we will now present to the user a new IAP page (served by the IAP server) where he/she can manage everything in a single place. task-6128821 Partial backport of https://github.com/odoo/odoo/pull/269021
This update reduces the number of products processed when preparing barcode stock data. It should make barcode-based inventory workflows faster and more efficient, especially when handling larger product sets.
Original PR description
`_get_stock_barcode_data` currently does a union to get a set of products to compute on, this set of products is currently much larger than it needs to be. I'll update with more when I can.
Point of Sale now calculates customer amounts due in one batch instead of repeating the same work for every cached customer. This reduces database load and helps stores open sessions faster, avoiding slowdowns on large production systems.
Original PR description
Issue ----- Opening a PoS sends every partner cached on the device to `get_all_total_due`. On a production database, six of those calls within 30 minutes ran 7061 queries, the worst one 4837 queries…
Issue ----- Opening a PoS sends every partner cached on the device to `get_all_total_due`. On a production database, six of those calls within 30 minutes ran 7061 queries, the worst one 4837 queries in 46.7 seconds, keeping a worker busy for the whole duration and slowing down the entire system. Cause ----- `get_all_total_due` loops over the recordset and calls `get_total_due` once per partner. Each iteration runs its own `pos.order` search for the open pay later orders and re-reads `point_of_sale.group_pos_user`, hence exactly two queries per partner. Every other value the method needs (`total_due`, `pos_orders_amount_due`, `invoices_amount_due` and the `_load_pos_data_read` payload) is already computed for the whole recordset at once by the ORM prefetching, so only those two remained unbatched. That search is also the expensive one: `commercial_partner_id` is not indexed, so each of them scans the whole set of orders in state 'paid'. Fix ----- Move the computation to `_get_total_due_data`, which queries the orders, the pay later payments and the partners data once for the whole recordset. `get_total_due` and `get_all_total_due` keep their signature and their return value, and both delegate to it. Measured on a database with 300k pos orders, for 1000 partners: 2193 queries / 3.98s before, 16 queries / 0.27s after. opw-6530561
Resolved issues and error corrections
Dropship and similar inventory operations that only create lots no longer show the misleading "Pick From" popup when adding a detailed line. This ensures the lot number and expiration date entered by the user are the values actually used, reducing delivery mistakes for tracked products.
Original PR description
Steps to reproduce 1. In Inventory Settings, enable Lots & Serial Numbers and Expiration Dates. 2. Create a storable product tracked by lot with Expiration Date enabled. 3. Create and confirm a…
Steps to reproduce 1. In Inventory Settings, enable Lots & Serial Numbers and Expiration Dates. 2. Create a storable product tracked by lot with Expiration Date enabled. 3. Create and confirm a dropship of it, then open the move's Detailed Operations. 4. The pre-filled line works: typing a batch and an expiration date creates exactly that lot at validation. 5. To split the quantity, click "Add a line": it opens the "Pick From" popup, and choosing "Create" there creates the lot and attaches it to the line. 6. On that line, edit "Lot/Serial Number" and "Expiration Date", then Validate. -> Expected: the values entered on the line are used. -> Actual: they are ignored; the lot created through "Pick From" is delivered with its own expiration date, the name and date typed on the line have no effect. Issue --- On a create-lots-only non-incoming operation like `Dropship`, `show_quant` is derived from `picking_code` alone, so the "Pick From" quant picker is shown even though the operation only creates lots. Adding a line goes through "Pick From", which creates the lot and attaches its `lot_id` to the move line; from then on the line's `lot_name` and `expiration_date` stay editable but do nothing, since a set `lot_id` is used as-is at validation and the `expiration_date` is recomputed from the lot, so anything typed there is silently dropped. Such an operation has no existing stock to pick from, so `show_quant` is now gated on the lot settings too: "Pick From" is hidden and the line's `lot_name` and `expiration_date` become the only inputs, created into the lot at validation like receipts already do. https://github.com/odoo/odoo/blob/68dcb950df83d70ff2aea0e05c96cc9b57c1a8a9/addons/stock/models/stock_move.py#L646-L647 opw-6530563 Forward-Port-Of: odoo/odoo#287474
Hungarian electronic invoice reports sent to NAV now leave out cash rounding adjustment lines, aligning the reported invoice content with local legal requirements. This helps avoid treating settlement differences as taxable goods or services while keeping invoice totals calculated correctly.
Original PR description
Global cash rounding can be applied to customer invoices. Before this commit, the rounding would be included in the XML file sent to NAV. It would be included as a new invoice line (same as the products lines) and the ATK tax is applied on it. As stated in the legal Hungarian Documentation, an invoice line should always relate to the supply of a good or the service provided. In this case, a cash rounding (which is not a financial advantage or disadvantage) will be considered by the law as a settlement difference, that is not part of the invoice. So, this commit removes cash rounding lines from the NAV XML. Moreover, it uses base_lines for the amounts computation instead of line_ids. task-6527383 Forward-Port-Of: odoo/odoo#286258
The pickup location list now only shows stores that belong to the same company as the customer’s current cart. This prevents shoppers on a company-specific website from selecting an invalid pickup store and encountering an error during checkout.
Original PR description
# How to reproduce - Have two company A & B - Install the Sales + Click & Collect modules - Create a Warehouse for each Company - Enable both company A & B - Go to the Delivery Methods & select "Pick…
# How to reproduce - Have two company A & B - Install the Sales + Click & Collect modules - Create a Warehouse for each Company - Enable both company A & B - Go to the Delivery Methods & select "Pick up in store" - Add two Stores, one for each Company - Go to a website that is limited to Company A - Go to the page of a product with Click & Collect Available > Both Stores are available - Select the Store associated for Company B - Click on "Choose this location" # Issue We get an error notification saying : "Invalid Operation. Uh-oh! You’ve got some company inconsistencies here: ..." # Cause When displaying the available stores, we don't filter them using the website's company. When we select a store with a different company than the website, the write on `sale.order` will trigger a call to `_check_company`, that will detect an inconsistency between the SO's company and the warehouse's company # Proposed solution We limit the available locations to warehouses that have the same company as the cart's SO. opw-6417309
Searches for archived bills of materials now correctly find records even when the linked product is also archived. This helps manufacturing users locate historical or inactive BOM data without missing results due to related archived products being filtered out.
Original PR description
When searching for an archived bom (filer archived activated), if the main product is also archived, the search on the produt will not find the bom. Steps to reproduce: ------------------- * Create…
When searching for an archived bom (filer archived activated), if the main product is also archived, the search on the produt will not find the bom.
Steps to reproduce:
-------------------
* Create product AA
* Create a bom for product AA
* Archive product AA
* Products > bom
* Search for "Archived" and Product: "AA" -> It will not find the bom
Observation:
-------------
When doing the search, it will start a web_search_read ['&', ('active', '=', False), ('product_tmpl_id', 'ilike', 'AA')].
While in _search it will decide if we filter active elements: https://github.com/odoo/odoo/blob/60b5b99d569dbe183ef9c980217ccfe9bf243756/odoo/orm/models.py#L5363-L5369 In the first level since "active = False" is in the domain (for the bom) it will not add the filter.
The _search will optimize the domain query level by level by calling optimize_full.
First the outher layer :
optimize_full (Domain '[]')> _optimize (DomainNary '&')> _optimize_step (DomainNary '&')
Then the children [1: (active bom), 2: (product template display name)]:
_optimize>optimize_step> ...
While optimizing the second child the domain condition is: [('product_tmpl_id', 'any', [('display_name', 'ilike', 'AA')])]
Since it's a any operation, in optimize_step, it will use the _optimize_any_domain_at_level:
https://github.com/odoo/odoo/blob/16aaaafd7d314c04f39b1f633af3b5e15b46d78b/odoo/orm/domains.py#L970-L972
After doing some checks it will continue to optimize per level with the related comodel:
https://github.com/odoo/odoo/blob/60b5b99d569dbe183ef9c980217ccfe9bf243756/odoo/orm/domains.py#L1389-L1393
Since it's an "any" operator it need to respect the following guidelines : https://github.com/odoo/odoo/blob/60b5b99d569dbe183ef9c980217ccfe9bf243756/odoo/orm/domains.py#L98-L101
this is no done in `_optimize_any_domain_at_level` since it resolves a relational field's 'any' domain by looking up a fresh comodel recordset without adjusting its context.
Which means that for the following level, on the product template, the active_name is not on the domain anymore and the context was not passed corretly, so _search will automaticly add the filter ("active = True").
opw-6514762Italian invoices using the Import/Export fiscal position no longer apply two different 0% taxes to the same line by default. This prevents incorrect tax setup on invoices and helps businesses produce cleaner, more accurate accounting records.
Original PR description
Upon Import/Export, the 0% EX N7 tax is added by default on each tax excluded line, as it incorrectly shares the same default Import/Export fiscal position with the standard 0% EX tax. This causes both to be applied simultaneously to a single invoice line. 1. Install Accounting and `l10n_it` 2. Switch to IT company 3. Go to Invoices and create a new one 4. Select the Import/Export fiscal position 5. Add a line with a new product (so it's clean of custom product taxes) 6. Both `0% EX` and `0% EX N7` taxes are applied, instead of just `0% EX` Ticket [link](https://www.odoo.com/odoo/project.task/6518926) opw-6518926
Users can now download all files from a chatter message as a ZIP even when some attachments are stored in cloud storage. This fixes a download failure and makes cloud-stored attachments behave consistently with regular attachments in bulk downloads.
Original PR description
Clicking "Download Files" on a chatter message fails when one of its attachments is stored in the cloud. Cloud attachments normally return a stream containing a signed URL. For individual downloads, Odoo redirects the browser to that URL, letting the cloud provider serve the file directly. The ZIP controller instead needs the file's bytes to build the archive on the server. It calls `Stream.read()`, which cannot read URL streams and raises `ValueError: Cannot read an URL`. Enable a `cloud_storage_force_download` context flag on the attachments when the cloud controller delegates ZIP creation to the mail controller. With this flag, the cloud attachment model fetches the file through the provider's signed URL and returns a data stream that the existing ZIP controller can read. opw-6570065
Point of Sale now correctly loads newly added product option values even when the main option category was already cached. This prevents errors when selecting products and avoids requiring users to clear browser data or restart sessions to recover.
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
Point of Sale now correctly opens the weighing prompt when a cashier scans a product that must be weighed, unless the barcode already includes the weight. This prevents sales of weighed items from being added without capturing the required weight.
Original PR description
Since 3604790e1fee, `needToConfigure()` returns a real `false` for a product without attribute lines instead of `undefined`. The barcode handlers of the product screen pass its result as the `configure` argument of `addLineToCurrentOrder`, whose default value is `true`. The scale popup was gated on that argument, so it only opened on scan because the `undefined` fell back to the default. It now stays closed for every scanned weighed product. Weigh the product whenever it was added interactively or scanned with a barcode that does not already carry a weight. opw-6561067 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Fixed an issue where all-day calendar events created from approved time off could be shortened if their start time changed, such as during Google Calendar sync. This helps ensure multi-day leave remains accurately reflected in calendars.
Original PR description
**Problem:** When modifying the start time of an all-day Calendar Event created from the Validation of a Time Off Request, the end time will be shortened dramatically, no longer spanning the correct…
**Problem:** When modifying the start time of an all-day Calendar Event created from the Validation of a Time Off Request, the end time will be shortened dramatically, no longer spanning the correct timespan. In the case of the ticket referred to below, this occurs during Google Calendar sync. **Cause:** When a Calendar Event is created from a Time Off Request being Validated, its duration is set according to the amount of days the Time Off Request spans multiplied by the employee's daily work hours as defined on their contract, even if the Calendar Event will be an all-day event. When the start time of a Calendar Event is modified, the end time is recomputed according to the duration added to the new start time. As such, if the duration of the Event does not match the duration of the dates covered, it will be shortened dramatically. **Purpose:** Modify the values defined when creating a Calendar Event as a result of a Validated Time Off Request so that, if the created Event will be an all day Event, the duration is computed according to `calendar.event._get_duration`. **Steps to Reproduce in Runbot:** 1. Create and Validate a Time Off Request of the Paid Time Off leave type spanning multiple days. 2. Modify the start time of the Calendar Event that is created as a result of the validation. (This step will likely require a nonstandard flow as the start time field is normally hidden for all-day Calendar Events) opw-6426586 Forward-Port-Of: odoo/odoo#289152 Forward-Port-Of: odoo/odoo#284552
French companies can now select and send multiple invoices without the process crashing. This restores access to the batch sending wizard, helping teams process electronic invoices more reliably.
Original PR description
Sending several invoices at once from the list view of a French company crashes instead of opening the batch sending wizard. Steps to reproduce: - Activate French Electronic Invoicing - Create a French customer with a valid PDP endpoint - Post two invoices for that customer, select them in the invoice list and click 'Send' Issue: The batch sending wizard raises ``` AttributeError: 'account.move.send.batch.wizard' object has no attribute 'company_id' ``` opw-6558940 Forward-Port-Of: odoo/odoo#289547
This fix ensures Point of Sale receipt numbers are only reused after a deleted draft order is fully removed from local browser storage. It prevents two paid orders from receiving the same receipt or invoice reference, reducing confusion and avoiding problems with fiscal integrations that require unique invoice numbers.
Original PR description
Steps to reproduce: - Add a product to a draft order, do not pay - Open the same PoS in a second tab of the same browser (the first tab is sent to the backend), or reload while a draft is being…
Steps to reproduce: - Add a product to a draft order, do not pay - Open the same PoS in a second tab of the same browser (the first tab is sent to the backend), or reload while a draft is being removed - Pay the restored draft, then make and pay a new order Issue: Both orders carry the same pos_reference (e.g. 264-3-000701). The number is printed on both receipts and sent to fiscal integrations that use it as the unique invoice number. Cause: The receipt number is issued client side by DeviceIdentifierSequence, a per-device counter kept in localStorage that all tabs of a browser share. When a draft is removed, its number is pushed on `unsynced_number_stack` to be reused by the next order. `removeOrder` pushes the number synchronously, then deletes the order from IndexedDB without waiting for the transaction. If the page unloads before it commits (the tab is closed by another tab opening the same PoS, a redirect to the backend, a reload), the next load restores the draft from IndexedDB with its number while the stack hands that same number to the next new order. Both are then created on the server with different uuids and the same pos_reference: the server only deduplicates by uuid. Fix: Recycle the number only once the order is gone from IndexedDB. A new `recycleOrderNumber` helper waits on the deletion of the order before calling `saveUnusedNumber`; `removeOrder` uses it after `localDeleteCascade`. `deleteOrders` awaits the recycling so that a delete followed by a new order still reuses the freed number, as before. opw-6558992 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Cancelling several deliveries at once now sends each carrier only the shipment information that belongs to it. This prevents failed or incorrect cancellation requests and avoids duplicate cancellation attempts when multiple delivery carriers are involved.
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 fix prevents errors when adding users to payroll groups from the Groups screen in Australian Payroll. It also ensures both added and removed group members are properly audit-logged, improving reliability and compliance tracking.
Original PR description
#### Description of the issue/feature this PR addresses:
Adding a user to a group from the Groups form crashes on an Australian Payroll-API database, and removals from that form are never audit-logged.
#### Current behavior before PR:
The audit-logging mixin reads the changed users out of the raw write command with vals.get("user_ids")[0][2], which assumes a 3-element command tuple. The Groups form sends the 2-element (4, id) LINK command, so the write raises an IndexError; UNLINK commands are silently not logged for the same reason.
#### Desired behavior after PR is merged:
Group membership changes made from the Groups form are audit-logged for both additions and removals, regardless of the command used to write user_ids. The mixin reads the members before and after the write instead of interpreting the command. No field, model or method-signature change, so it is safe in stable.
opw-6397011Budgets now correctly account for purchase receipts when calculating committed amounts. This prevents the same purchase value from being counted twice, so budget totals reflect the expected cost rather than an inflated amount.
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 with one line of 100$ and add the budget project (setting the analytical account on the line). 4. Create a bill from the purchase (receive the item if necessary) and set the move type to receipt. 5. Goes to the budget, and see the committed amount is not correct (you will see 200$ instead of the expected value of 100$). expected behavior: committed amount should be correct (ie, not repeating the purchase order amount that is already in the receipt). opw-6527042
Resetting an expense report to draft now also clears Studio approval records for its posting action. This prevents a previously approved posting step from being reused after changes, ensuring expenses require fresh approval when resubmitted.
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
This fix prevents Ecuadorian invoice confirmation from failing when a company partner has a VAT number containing letters or other non-digit characters. Instead of a system error, users receive a clear validation message so the VAT can be corrected before invoicing.
Original PR description
When the VAT of an Ecuadorian company's partner contains characters other than decimal digits, confirming an invoice raises a traceback. Steps to reproduce the error: - Install ``l10n_ec_edi`` module…
When the VAT of an Ecuadorian company's partner contains characters other than decimal digits, confirming an invoice raises a traceback. Steps to reproduce the error: - Install ``l10n_ec_edi`` module and switch to EC Company - Open the partner of EC Company > Set the VAT to ``BE0477472701`` - Create a new invoice > fill the required fields > Confirm Traceback: ```py ValueError: invalid literal for int() with base 10: 'E' ``` After this [commit], companies outside the EU can use European VAT numbers. Consequently, an Ecuadorian partner can have a RUC number such as ``BE0477472701``, which is valid as a Belgian VAT number but not as a RUC. During invoice confirmation, ``_l10n_ec_set_authorization_number()`` method is called to generate the authorization number at [1], It builds the ``key_value`` using the vat of company's partner at [2]. The generated ``key_value`` is then passed to ``_l10n_ec_get_check_digit`` method, which converts each character of ``key_value`` to an integer using ``int()`` at below line. https://github.com/odoo/enterprise/blob/799e9e94efcc338a8f3a6b7ab85cf77f9898f005/l10n_ec_edi/models/account_move.py#L830 When the VAT contains a character that cannot be converted to an integer, It will raises the above traceback. [commit]: https://github.com/odoo/odoo/commit/a2afe3292e1cd0a4f339dc47707e469653d13ea0 [1]: https://github.com/odoo/enterprise/blob/799e9e94efcc338a8f3a6b7ab85cf77f9898f005/l10n_ec_edi/models/account_move.py#L806 [2]: https://github.com/odoo/enterprise/blob/799e9e94efcc338a8f3a6b7ab85cf77f9898f005/l10n_ec_edi/models/account_move.py#L825-L826 Solution: Raise the ValidationError when the vat contains non-digit character. sentry-7642961403 Related Enterprise PR: https://github.com/odoo/enterprise/pull/126519 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280047
Peruvian delivery guides for itinerant issuer transfers now omit a field that SUNAT does not allow for this transfer reason. This prevents SUNAT rejection errors and helps users successfully generate compliant delivery guides.
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