Daily updates from Odoo
Wednesday, August 5, 2026
22 changes · 18.0
Enhancements to existing features
Appointment pages now calculate bookable capacities much more efficiently, especially when many linked resources are involved. This makes availability pages load far faster and also corrects capacity values in some resource-selection cases.
Original PR description
Before --- - When opening the appointment page, the possible capacities that a user can book needs to be calculated to show the capacity dropdown. - This is currently done by getting all the possible…
Before
---
- When opening the appointment page, the possible capacities that a user can book needs to be calculated to show the capacity dropdown.
- This is currently done by getting all the possible combinations that could arise from each resource along with its linked resources (Linked resources are resources that are used to combine with the main resource to allow a bigger capacity)
- The complexity of this approach blows up with the increase in linked resources. For each resource we end up with O(2^m) where m is the number of linked resources it has. If they have the same number of linked resources, and n is the number of resources, we end up with O(n*2^m)
Solution:
- Flipping the algorithm, we create a structure for possible capacities and only keep the best combination for a found capacity.
- We first store all available capacity for relevant resources.
Algorithm
---
For a resource:
- We initialize the solution S to {0: []}
- Greedy dynamic programming is then used to get all the possible combinations by building the result dict, adding one resource at a time. We add its capacity to all entries of S -> we use the combination if a new capacity is reached OR update the combination if the number of elements of combination is less than the existing one.
Then, for the global solution:
- We go through S of resources (in the order of self), and add entries to G (the general dict of solutions). Again, on collision, we only keep the lower-cardinal combination.
The complexity of this algorithm is O(n*u) where u is the dynamic programming complexity. u would be quadratic O(m^2) if the sub-sums of capacities overlap heavily, for instance when resources have the same capacity, like tables of a restaurant, but could reach (2^m) in the worst case scenario.
Related changes / side effects
---
1. Fixing an issue
2. Cleaning strange logic
3. Cleaning max capacity computation
Benchmark:
- opw-5177932 goes down from 6 mins to less than 2 seconds using the new algorithm.
Also include
---
Fix the max capacity on resources when skipping resource selection. It computed the max capa based on values that were not yet emptied in the website_appointment controller.
Task-6233563The preparation display now uses the clearer label “Footer Note” instead of “General Note,” alongside an updated icon. This makes the action button easier for staff to understand when managing order notes.
Original PR description
Following this commit : ==== - General Note is renamed to Footer Note. task- 4210929
The Amazon sales integration now uses Amazon's newer Orders API ahead of the 2027 retirement of the old version. This keeps order imports compatible with Amazon's platform and should improve synchronization performance by retrieving order details more efficiently.
Original PR description
Amazon has announced the deprecation of the Orders v0 API, with a removal date of March 27, 2027. In this commit, we migrate to the new v2026-01-01 API. This new version restructures how order data is queried and delivered, shifting from a multi-request architecture to a nested consolidated payload. This optimizes our sync performance by eliminating the N+1 query problem when fetching order items. Key changes: - Operation Consolidation: `getOrders` is replaced by `searchOrders`. Because Amazon now embeds orderItems directly inside each order object natively, we remove our secondary item-fetching loops. - Financial aggregation: Item prices, taxes, shipping, and discounts are no longer flat fields on the item but are centralized into a `proceeds` object. - Replacing of deprecated flags. - Reorganization of order-related fields. task-5972714 Forward-Port-Of: odoo/enterprise#114591
The checks the Tax Agency performs are always based on the latest version of their XSD (no API versioning), but the XSD URI changes with each new version of the checks. Since it's not functionally used, it's not so important to keep it always updated and yet now: - we now update the export templates to have the latest `schemalocation` URI for once. - we start ignoring namespaces in l10n_it_* XML tests - we start ignoring the root FatturaElettronica tag's namespace attributes so there
Original PR description
The checks the Tax Agency performs are always based on the latest version of their XSD (no API versioning), but the XSD URI changes with each new version of the checks. Since it's not functionally used, it's not so important to keep it always updated and yet now: - we now update the export templates to have the latest `schemalocation` URI for once. - we start ignoring namespaces in l10n_it_* XML tests - we start ignoring the root FatturaElettronica tag's namespace attributes so there won't be a problem in case we change the `schemalocation` again.
**=pos_restaurant,pos_sale Following this commit : ==== - General Note is renamed to Footer Note and icon is also added. - Quotation/Order is renamed to Quotation. - Edit Order Name is renamed to Name. - Customer Note is renamed to Item Note. - Cancel Order is renamed to Delete Order. task- 4210929
Original PR description
**=pos_restaurant,pos_sale Following this commit : ==== - General Note is renamed to Footer Note and icon is also added. - Quotation/Order is renamed to Quotation. - Edit Order Name is renamed to Name. - Customer Note is renamed to Item Note. - Cancel Order is renamed to Delete Order. task- 4210929
This change cleans up the requested data from `/mail/thread/data` route, ensuring it aligns with what is actually needed depending on the user and thread.
Original PR description
This change cleans up the requested data from `/mail/thread/data` route, ensuring it aligns with what is actually needed depending on the user and thread.
Resolved issues and error corrections
The Vietnam reports module now classifies short-term loan balances under Held to Maturity Investment as required by Circular 99/2025. This helps businesses produce compliant balance sheet reports without manually adjusting the classification.
Original PR description
### Expected behavior: As per circular 99/2025, short-term loan (12831) balance is required to fall under Held to Maturity Investment (Code 123) instead of 112 ### Steps to reproduce: Install `l10n_vn_reports` module ### Fix: Update the Balance Sheet code formula for the 12381 account opw-6411771
This fix makes the batch picking barcode test wait for the first scanned item quantity to update before selecting the next item. It prevents timing issues that could make the test record quantities on the wrong line, improving confidence in barcode batch picking behavior.
Original PR description
Problem: When scanning the first product, the second move line is clicked immediately after.…
Problem: When scanning the first product, the second move line is clicked immediately after. https://github.com/odoo/enterprise/blob/bfb8bab7636a84c829f16087771e00bf31ad2cce/stock_barcode_picking_batch/static/tests/tours/tour_test_barcode_batch_flows.js#L1529-L1541 If this happens before the first scan has finished, its quantity is incorrectly applied to the second move line that is clicked. This causes a 0 - 3 split instead of a 1 - 2 split, which results in there only being 6 move lines instead of 7. We updated our quantity on the `currentLine` https://github.com/odoo/enterprise/blob/bfb8bab7636a84c829f16087771e00bf31ad2cce/stock_barcode/static/src/models/barcode_model.js#L1478 When finding `currentLine`, we go through `_findLine` and use `this.selectedLineVirtualId`, which is the one that is currently selected in the UI https://github.com/odoo/enterprise/blob/bfb8bab7636a84c829f16087771e00bf31ad2cce/stock_barcode/static/src/models/barcode_model.js#L1705-L1712 Purpose: By adding this step, we wait until the first line’s quantity to be updated before it moves on and clicks on the second product. runbot-941211
The Vietnam reports module now classifies short-term held-to-maturity loan balances under the correct balance sheet category required by Circular 99/2025. This helps businesses produce financial statements that better align with the latest Vietnamese reporting rules.
Original PR description
### Expected behavior: As per circular 99/2025, short-term loan (12831) balance is required to fall under Held to Maturity Investment (Code 123) instead of 112, translated: ``` Short-term held-to-maturity investments (Code 123): includes held-to-maturity investments with a remaining term of 12 months or less from the end of the accounting period, such as term deposits, bonds, commercial paper, loans, and other debt securities. This item does not include held-to-maturity investments that have been presented in the item “Cash equivalents” ``` ### Steps to reproduce: Install `l10n_vn_reports` module ### Fix: PO validated: Update the Balance Sheet code formula for the 12381 account opw-6413120
Documents uploaded from a contact are now saved in the intended default workspace instead of reusing the last folder selected in Documents. This prevents files from being accidentally placed in unrelated folders, making document organization more reliable for users.
Original PR description
Steps to reproduce ================== 1. Open Documents. 2. Select Finance. 3. Return to the home page and open the Contacts app. 4. Open any contact. 5. Click the Documents stat button. 6. Upload a document. Issue ===== The document is uploaded to the Finance folder instead of My Drive. Reason ====== When uploading a document using the upload button, we use `currentFolderAccessToken` to determine the destination folder. When opening the Documents view from a contact, `searchpanel_default_folder_id` is set to `False` so that documents are uploaded to the `All` workspace. However, when the search model is loaded, we do not reset `currentFolderAccessToken` when `folder_id` is `False`, causing the previously selected folder (Finance) to be reused. Task-6352242
The portal now updates the number of documents awaiting a user's signature after they sign. This prevents users from seeing already completed signature requests as still pending, reducing confusion in the signing workflow.
Original PR description
Version: 18.0 Steps to reproduce: - Create a sign request with two signers. - Assign the first signature to a portal user. - Log in as the portal user and sign the document. Issue: After signing, the to-sign count in the portal does not decrease. This is because the query only checks the overall sign request state instead of the individual signer's item state, so the count remains unchanged Fix: Added an item level state check to the count query so it only counts items that are still pending for that specific user. Task ID: 6412976
## Issue When logging timesheet from the *Recorded* smart button on a Sale Order containing multiple service items, the recorded hours will be added to the first sale order line, regardless of the task ID set on the timesheet entries. ## Steps to reproduce 1. Install *Sales Timesheet* (`sale_timesheet`) 2. Create a Product P: - *Product Type*: Service - *Create on Order*: Task - *Project*: Any 3. Create a SO: - *Customer*: Any - Add the product P on two different
Original PR description
## Issue When logging timesheet from the *Recorded* smart button on a Sale Order containing multiple service items, the recorded hours will be added to the first sale order line, regardless of the…
## Issue
When logging timesheet from the *Recorded* smart button on a Sale Order containing multiple service items, the recorded hours will be added to the first sale order line, regardless of the task ID set on the timesheet entries.
## Steps to reproduce
1. Install *Sales Timesheet* (`sale_timesheet`)
2. Create a Product P:
- *Product Type*: Service
- *Create on Order*: Task
- *Project*: Any
3. Create a SO:
- *Customer*: Any
- Add the product P on two different lines and give them two different descriptions D1 and D2
- Confirm the SO, this will create two tasks with the names D1 and D2
4. On the SO, click the *Recorded* smart button and create two entries:
1. Task D1, 2 hours spent
2. Tsk D2, 3 hours spent
5. **Back on the SO, there are 5 hours registered for the first SOL (with the description D1), which does not match the entries we created from the smart button.**
It is worth noting that when we create the Timesheets entries from the project itself (instead of the SO's smart button), the hours are correctly distributed among the different SOLs.
## Cause
The SOL linked to the timesheet entry (`account.analytic.line`) is computed by `_compute_so_line`:
https://github.com/odoo/odoo/blob/8b102f500f5a122e99b07a08cc43814e7c6f0f75/addons/sale_timesheet/models/hr_timesheet.py#L79-L82
This method sets the correct SOL under the condition that `is_so_line_edited` is False and `_is_no_billed()` returns True.
When opening the *Recorded* smart button from a SO, the `is_so_line_edited` is set to True by default, even if no SOL was modified.
https://github.com/odoo/odoo/blob/8b102f500f5a122e99b07a08cc43814e7c6f0f75/addons/sale_timesheet/models/sale_order.py#L113-L117
As that value is never set to False, when trying to compute the SOL for the timesheet entry, the entry is skipped and the default SOL (which is the first one) is used instead.
opw-6133473This reverts commit 64cf4afabd0c5ef040cf87fc6f3125fe0bb81bbb, as it only fixes the specified case while introducing other issues (see https://github.com/odoo/odoo/pull/277727#issuecomment-5185048764) ## Steps to reproduce (one of those issues) 1. Create a service product: Invoicing Policy = Based on Timesheets, Create on Order = Task 2. Create a SO with it (quantity 1) and confirm 3. On the task, log 4.5 h on 15/06 and 3.5 h on 23/07 4. Create Invoice, no Timesheets Period → 8 h. Post it
Original PR description
This reverts commit 64cf4afabd0c5ef040cf87fc6f3125fe0bb81bbb, as it only fixes the specified case while introducing other issues (see https://github.com/odoo/odoo/pull/277727#issuecomment-5185048764) ## Steps to reproduce (one of those issues) 1. Create a service product: Invoicing Policy = Based on Timesheets, Create on Order = Task 2. Create a SO with it (quantity 1) and confirm 3. On the task, log 4.5 h on 15/06 and 3.5 h on 23/07 4. Create Invoice, no Timesheets Period → 8 h. Post it with invoice date 04/08 5. On that invoice: Reverse → Partial Refund, set the credit-note quantity to 3.5 h, post it (date 04/08) → qty_invoiced becomes 4.5 6. Log 1 h on 31/07 → qty_delivered becomes 9.0 7. Create Invoice with Timesheets Period 01/06 → 31/07 8. **The June hours are billed twice** (related to) opw-6373870
Before this commit, this test was sometimes failing. `edit("...")` validates the value by default (i.e. is followed by enter). It may happen that the dropdown is already open when doing so, and in this case, the first value is selected. However, it may often happen that it isn't open yet, so nothing happens. To turn tests more robust, we typically turn off the auto confirm and call runAllTimers() to ensure the dropdown is open, then select the value. That's also what we did here. runbot er
Original PR description
Before this commit, this test was sometimes failing. `edit("...")` validates the value by default (i.e. is followed by enter). It may happen that the dropdown is already open when doing so, and in this case, the first value is selected. However, it may often happen that it isn't open yet, so nothing happens.
To turn tests more robust, we typically turn off the auto confirm and call runAllTimers() to ensure the dropdown is open, then select the value. That's also what we did here.
runbot error-944120
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-prOn some mobile setup, the virtual keyboard is displayed when showing an image in fullscreen. This commit is an attempt at using `virtualkeyboardpolicy` to remedy to this problem. task-6370220
Original PR description
On some mobile setup, the virtual keyboard is displayed when showing an image in fullscreen. This commit is an attempt at using `virtualkeyboardpolicy` to remedy to this problem. task-6370220
Before this commit, this test sometimes failed because it couldn't find a dialog containing "camera" within 200ms. In the test scenario, we click to open the BarcodeDialog, which uses the BarcodeVideoScanner. The latter, in its `onMounted`, checks whether it has the necessary permission, which isn't the case as the `getUserMedia` function is mocked in the test to return a rejected promise. As a consequence, the `onError` callback given in props is called, which changes the state of the parent
Original PR description
Before this commit, this test sometimes failed because it couldn't find a dialog containing "camera" within 200ms. In the test scenario, we click to open the BarcodeDialog, which uses the BarcodeVideoScanner. The latter, in its `onMounted`, checks whether it has the necessary permission, which isn't the case as the `getUserMedia` function is mocked in the test to return a rejected promise. As a consequence, the `onError` callback given in props is called, which changes the state of the parent component, which re-renders itself so display "Unable to access camera" instead of the BarcodeVideoScanner. To make this test more robust, we do 2 things: 1) load the zxing library before running the test, which avoids the BarcodeVideoScanner component to load it in onWillStart. 2) explicitly wait for the 2 animationFrames, as in the scenario, we must wait for the BarcodeDialog to be rendered twice, and those renderings are now synchronous. runbot error-237933
Previously, we did not prevent FRCTC from registering through Peppol, even though FRCTC is not supported in Peppol. French companies could attempt to register in Peppol with FRCTC, which caused many errors. task-6421930
Original PR description
Previously, we did not prevent FRCTC from registering through Peppol, even though FRCTC is not supported in Peppol. French companies could attempt to register in Peppol with FRCTC, which caused many errors. task-6421930
Steps: - Install sale_management - Make sure you have 100 quotations with 1 activity each - Open activity view - Default pager displays `1-100/100` - Activity count display `To-do 80` ActivityController uses `useModel` which passes the raw `component.props` to `model.load()`, including the limit from `ir.actions.act_window` (default 80). This value ended up in `fetchActivityData` via `params.limit || this.initialLimit`, overriding `ActivityModel.DEFAULT_LIMIT` (100). The records li
Original PR description
Steps: - Install sale_management - Make sure you have 100 quotations with 1 activity each - Open activity view - Default pager displays `1-100/100` - Activity count display `To-do 80`…
Steps: - Install sale_management - Make sure you have 100 quotations with 1 activity each - Open activity view - Default pager displays `1-100/100` - Activity count display `To-do 80` ActivityController uses `useModel` which passes the raw `component.props` to `model.load()`, including the limit from `ir.actions.act_window` (default 80). This value ended up in `fetchActivityData` via `params.limit || this.initialLimit`, overriding `ActivityModel.DEFAULT_LIMIT` (100). The records list was not affected because `RelationalModel._getNextConfig` never reads `params.limit` (limit is not a `SEARCH_KEY`), so it always loaded 100 records correctly. But `fetchActivityData` used 80, causing a mismatch between the records shown and the activity counts in the column headers. ```js export const SEARCH_KEYS = ["comparison", "context", "domain", "groupBy", "orderBy"]; ``` The fix strips `params.limit` in `ActivityModel.load()` before passing params to `fetchActivityData`, so it falls back to `this.initialLimit (100)`. The pager `onUpdate` handler calls `fetchActivityData` directly with its own `params.limit` and is not affected. However, `ActivityController` never forwards `limit` to the model. This is why we always have `ActivityModel.DEFAULT_LIMIT (100)` without taking into account actions's limit. To fix this we have to add the limit via `this.props.limit`, as `ListController`. `useModelWithSampleData` already had the correct behavior by calling `model.load(getSearchParams(props))` which filters out non-search params like limit. In 19.0 useModel was updated to do the same, so the issue does not exist there. Link to 19.0 fix: https://github.com/odoo/odoo/pull/211697 opw-6281125
**Steps to reproduce:** - Install l10n_pl_edi and enable Allow KSeF integration from **Accounting** settings. - Switch to a Polish company. - Create an EU customer with a valid VAT number. - Create a sale order containing a service product taxed with 0% Steel (or any tax that has a tag of K11). - Confirm the sale order and create a down payment invoice. - Send the invoice to KSeF and inspect the generated XML. **Observed behavior:** The generated KSeF XML does not contai
Original PR description
**Steps to reproduce:** - Install l10n_pl_edi and enable Allow KSeF integration from **Accounting** settings. - Switch to a Polish company. - Create an EU customer with a valid VAT number. - Create a…
**Steps to reproduce:** - Install l10n_pl_edi and enable Allow KSeF integration from **Accounting** settings. - Switch to a Polish company. - Create an EU customer with a valid VAT number. - Create a sale order containing a service product taxed with 0% Steel (or any tax that has a tag of K11). - Confirm the sale order and create a down payment invoice. - Send the invoice to KSeF and inspect the generated XML. **Observed behavior:** The generated KSeF XML does not contain the `P_13_8` field. **Cause:** For invoices involving the tax of tag `K11` (mainly these taxes are used for the supplies that are outside the territory of Poland), the value corresponding to `P_13_8` was not being assigned during XML generation, causing the tag to be omitted from the exported KSeF document. **Fix:** Populate the value of `P_13_8` during KSeF XML generation for invoices, ensuring the field is correctly included in the exported XML. This PR updates the computation of tag `P_13_10` with its test case to ensure consistency with the expected reporting logic, where the tag is computed solely from `K_31`. Here is the [Documentation](https://ksef.podatki.gov.pl/media/gtjhkeek/information-sheet-on-the-fa-3-logical-structure-04032026.pdf) link for the reference of the Ksef structure. **opw**-6294181
When a product carrying a multi-select (no_variant) attribute is added to an order by scanning its barcode, the extra price of the selected attribute value was dropped and the product was added at its base price. The order line total is computed from `price_unit` alone. The `price_extra` of a no_variant attribute is never included in the variant price (a "multi" attribute requires create_variant="no_variant"), so it only reaches the total once folded into `price_unit`. `addLineToOrder` did th
Original PR description
When a product carrying a multi-select (no_variant) attribute is added to an order by scanning its barcode, the extra price of the selected attribute value was dropped and the product was added at…
When a product carrying a multi-select (no_variant) attribute is added to an order by scanning its barcode, the extra price of the selected attribute value was dropped and the product was added at its base price. The order line total is computed from `price_unit` alone. The `price_extra` of a no_variant attribute is never included in the variant price (a "multi" attribute requires create_variant="no_variant"), so it only reaches the total once folded into `price_unit`. `addLineToOrder` did that fold-in only when the product was not scanned (`!isScannedProduct`). That guard was added to avoid counting the extra price twice when scanning an "always" variant barcode, whose extra is already part of its price. Since then, the extra price reaching this block is filtered to no_variant values only, both in the configurator and in the direct-variant branch, so an "always" extra can no longer reach it and the guard now only drops legitimate no_variant surcharges. Remove the guard so the no_variant extra price is always applied. Steps to reproduce: - Create a product with a multi-checkbox attribute whose value has an extra price, and give the product a barcode. - Open the PoS and scan the barcode. - Pick the attribute value in the configurator and validate. => The extra price is not added to the order line. opw-6413924 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Steps to reproduce: 1. Run a local SMTP server that responds with a server error. The server file is provided in the task [refuse_smtp.py](https://github.com/user-attachments/files/27166855/refuse_smtp.py) 2. Create an outgoing server for this SMTP server 3. Send an email Issue: The delivery failure reason displays Mail delivery failed via SMTP server 'None' instead of the configured server name. Cause: ir.mail_server.send_email() builds the failure message from the smtp_server argume
Original PR description
Steps to reproduce: 1. Run a local SMTP server that responds with a server error. The server file is provided in the task [refuse_smtp.py](https://github.com/user-attachments/files/27166855/refuse_smtp.py) 2. Create an outgoing server for this SMTP server 3. Send an email Issue: The delivery failure reason displays Mail delivery failed via SMTP server 'None' instead of the configured server name. Cause: ir.mail_server.send_email() builds the failure message from the smtp_server argument, but in the common path the mail is sent via mail_server_id. In that case, the actual SMTP server is resolved in connect(), while smtp_server remains unset, so the error message shows None. Solution: Store the resolved server label on the SMTP connection when opening it, and reuse that value when formatting send failures. opw-6139168
When a customer invoice is digitized through OCR, the salesperson may be set to the "Public User" instead of the internal user who uploaded the document. Steps to reproduce: - Upload a PDF invoice of an existing customer - Send the PDF to OCR - Reload the page Issue: Observe the Salesperson field: it is set to the Public User. Analysis: This occurs because when the partner is filled in, the compute of the salesperson will trigger. On SaaS this happens through the extraction c
Original PR description
When a customer invoice is digitized through OCR, the salesperson may be set to the "Public User" instead of the internal user who uploaded the document. Steps to reproduce: - Upload a PDF invoice of an existing customer - Send the PDF to OCR - Reload the page Issue: Observe the Salesperson field: it is set to the Public User. Analysis: This occurs because when the partner is filled in, the compute of the salesperson will trigger. On SaaS this happens through the extraction completion webhook a public route processed in sudo. that does not change the current user (public user). As self.env.user is the fallback of the compute, it may be set as salesperson. opw-6296330