Tuesday, September 1, 2026
28 changes · saas-19.3
Resolved issues and error corrections
This change reverts a recent point-of-sale restaurant printing behavior that caused full orders to be reprinted when staff only wanted newly added items printed. It restores the expected kitchen or preparation printing flow, reducing duplicate tickets and confusion during service.
Original PR description
This reverts commit a1ee5faa141af45e38879a448ce6bbd5ed58cc54 (https://github.com/odoo/odoo/pull/266070) which is causing issues (reprint whole order when I only want to print newly added order lines). task-id: 6230594 FW of https://github.com/odoo/odoo/pull/285993 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fixes an issue where timesheets could lose their connection to a replacement invoice after an invoice was reversed and recreated through a credit note. Businesses can now keep accurate links between billed timesheets, sales orders, and corrected invoices, reducing billing confusion and manual cleanup.
Original PR description
### Description of the issue/feature this PR addresses: Fixes an issue where timesheets lose their invoice reference when reversing and re-creating an invoice via a credit note. ### Current behavior…
### Description of the issue/feature this PR addresses: Fixes an issue where timesheets lose their invoice reference when reversing and re-creating an invoice via a credit note. ### Current behavior before PR: When reversing an invoice tied to timesheets and creating a replacement via a credit note, the timesheets linked to the original invoice have their timesheet_invoice_id cleared. Because the modify_moves function builds the replacement invoice directly via copy_data()/create(), it bypasses the normal sale order invoicing flow. As a result, the unbilled timesheets are left permanently unlinked from the newly created invoice, leaving the new invoice with no reference to the timesheets linked to the original sale order. ### Desired behavior after PR is merged: When a replacement invoice is created, each timesheet is properly relinked to the corresponding line on the new invoice. This linkage matches on the sale order line (so_line) rather than line position, ensuring accuracy since line order and count are not guaranteed to be preserved between the original and modified invoices. opw-6449995 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285232 Forward-Port-Of: odoo/odoo#283104
Corrects the Mexican chart of accounts so accumulated depreciation and amortization accounts are classified as asset accounts instead of expense accounts. This ensures depreciation entries for fixed assets such as vehicles correctly reduce asset values and show the expected depreciation amounts.
Original PR description
Issue: When creating an asset using an "154.01.01 Vehicles", the depreciation values of the journal entries created are zero Steps to reproduce: 1. Install l10n_mx and enter a Mexican company 2.…
Issue: When creating an asset using an "154.01.01 Vehicles", the depreciation values of the journal entries created are zero Steps to reproduce: 1. Install l10n_mx and enter a Mexican company 2. Create an asset and setting the fixed asset account as 154.01.01 Vehicles 3. Click on "Compute Depreciation" and in the "Depreciation Board" page, all the journal entries created will not have any depreciation values Cause: In the l10n_mx chart of accounts, all accumulated depreciation accounts are set as "expense_depreciation" whereas they should be asset accounts because accumulated depreciation accounts are used to credit asset accounts to decrease asset values. If an account of type "expense_depreciation" is used, then when the depreciation account is credited and the expense account is debited, all financial events will occur within expense accounts, which is why no depreciation values were recorded as the asset balances stay the same. Additionally, the "asset_depreciation_account_id" field on "account.account" has a domain restricting selection to "account_type" of "asset_fixed" or "asset_non_current" Solution: Change the "account_type" of l10n_mx accumulated depreciation accounts from "expense_depreciation" to "asset_fixed" and l10n_mx accumulated amortization accounts from "expense_depreciation" to "asset_non_current" Updrade PR: [odoo/upgrade/pull#10936](https://github.com/odoo/upgrade/pull/10936) opw-6359618 Forward-Port-Of: odoo/odoo#283390 Forward-Port-Of: odoo/odoo#278185
The Guatemala electronic invoicing setup now reflects the new regular gasoline tax calculation starting August 22, where only 90% of gallons are taxable due to the 10% alcohol-exempt portion. This affects the default chart of accounts for new customers; existing databases can update their tax formula manually if needed.
Original PR description
Starting August 22nd Regular Gasoline is changing to a version that contains 10% alcohol. Because of this, the government is only taxing 90% of the gallons since the 10% alcohol portion is exempt. This will update COA for new customers, any current dbs who need to use the new formula can manually update theirs to include the * 0.9 portion. task-6483303 Forward-Port-Of: odoo/enterprise#129483 Forward-Port-Of: odoo/enterprise#129412
Fixed an accounting display issue where bill references could disappear in the bank reconciliation widget when a foreign-currency payment also created an exchange difference entry. This helps accountants correctly identify matched bills when reviewing reconciled bank transactions.
Original PR description
In the bank reconciliation widget, a reconciled transaction matched with a bill and a loss exchange entry no longer displays the bill reference next to the reconciled line. Steps to reproduce: - Have…
In the bank reconciliation widget, a reconciled transaction matched with a bill and a loss exchange entry no longer displays the bill reference next to the reconciled line. Steps to reproduce: - Have company currency USD and foreign currency EUR - Create a vendor bill in foreign currency (100 EUR, rate 1 USD = 1 EUR) - Create a bank transaction in the foreign currency for less than the bill total, at a rate making the amount higher in company currency (90 EUR, 108 USD at 1 EUR = 1.2 USD) - Open the bank reconciliation widget, reconcile transaction and bill - Unfold the reconciled transaction Issue: A bill reference is missing Analysis: The exchange difference line is reconciled with the transaction line. Being reconciled with two lines (bill and exch), the widget relies on `*_reconciled_lines_excluding_exchange_diff` to show the fields. However, `count_reconciled_lines_excluding_exchange_diff` has been defined as boolean instead of int, faulting the comparison. opw-6479015 Forward-Port-Of: odoo/odoo#283855
The web property editor no longer crashes when a saved rule cannot be checked by the server. Users can still open the editor to correct or remove the problematic property, preventing them from getting stuck with an uneditable configuration.
Original PR description
When a relational property has a domain the server cannot evaluate, opening its definition editor crashes. The editor calls search_count on that domain to show how many records match, both when it…
When a relational property has a domain the server cannot evaluate, opening its definition editor crashes. The editor calls search_count on that domain to show how many records match, both when it opens and on every later render. The server raises a ValueError and the call has no error handling, so the whole editor goes down. The bad domain stays saved on the property, so reopening the editor fails the same way and the property can no longer be edited or deleted. Wrap the search_count call in _updateMatchingRecordsCount (property_definition.js) in a try/catch and show no count when it fails. This is the only place the editor counts matching records, so guarding it here handles a bad domain from any source, the field selector or the code editor. The field selector still shows its warning on the invalid path, so the user can fix or delete the property. Steps to reproduce: 1. On a model that has a Properties field, add a Many2one property and set its Model to a model that itself has a Properties field. 2. Open the property Domain and click New Rule. 3. In the field selector pick the Properties entry, then close the selector. => An error dialog appears and the property can no longer be edited or deleted. Ticket [link](https://www.odoo.com/odoo/project.task/6101311) opw-6101311 Forward-Port-Of: odoo/odoo#283802 Forward-Port-Of: odoo/odoo#259886
Incoming VoIP calls that time out and go to voicemail are now clearly marked as missed. This avoids confusing softphone messages and prevents call records from staying stuck in a calling state.
Original PR description
Steps to reproduce: - Call your softphone with your smartphone. - Let the smartphone ring forever. => At some point, you reach the voicemail and the softphone stops ringing but: - The softphone…
Steps to reproduce: - Call your softphone with your smartphone. - Let the smartphone ring forever. => At some point, you reach the voicemail and the softphone stops ringing but: - The softphone displays a weird message with emojis. - The call record stays "Trying to call" (and might *display* "Ended unexpectedly" in 19.2). This commit fixes those two issues by showing a proper "Call missed" message and switching the call record status to "Missed". This is done in 19.2 and not before... because the behavior before is even more problematic: - 19.1: the softphone keeps ringing and crash if you try to answer, that was mostly fixed thanks to [1] and this commit takes profit of those big ameliorations to fix the issue here. - 19.0: you do not even reach voicemail (it stops without saying anything). This was apparently fixed as a side-effect of [2], which we do not consider worth even partially backporting as not critical. [1]: https://github.com/odoo/enterprise/commit/942f32316ab02d8c739fe7fdd5ec2bdde472a68e [2]: https://github.com/odoo/enterprise/commit/d24d7f3406ca47e7ac529d69957b0ad481d553bf task-6453500 Forward-Port-Of: odoo/enterprise#127075
Fixes an error that could occur when updating suggested products for unpublished eCommerce products. This helps store managers keep product recommendations up to date without being blocked by a system error.
Original PR description
Currently, an error occurs when the user tries to update suggested products. **Steps to Reproduce:** - Install the `website_sale` module. - Go to `Settings` and enable `Automate suggested products`…
Currently, an error occurs when the user tries to update suggested products.
**Steps to Reproduce:**
- Install the `website_sale` module.
- Go to `Settings` and enable `Automate suggested products` under the `eCommerce` section.
- Go to `Website` > `eCommerce` > `Products` > `Products`.
- Create a `product` and, in the `eCommerce` tab, add a `category`.
- Make sure the `product` is `not published`.
- On the `product`, click the `gear icon` and select `Update suggested products`.
`ValueError: TypeError('unsupported operand types in: product.template() | None') while evaluating 'records.action_update_suggested_products()'`
When the user updates suggested products, the system updates the product's suggested
products - optional, accessory, and alternative products [1]. While updating the alternative
products [2], the system tries to find products based on the categories and attributes shared
with the current product [3]. When retrieving products from the current product's category,
it gets None [4] because the products linked to that category are unpublished and are
therefore excluded by the domain [5]. Later, using this None value raise the error.
This commit ensures that when accessing a missing key in the category dictionary, then it falls
back to an empty product recordset.
[1]: https://github.com/odoo/odoo/blob/21e3310a3eba2ce915b33cf10ee4c14bdda7aad4/addons/website_sale/models/product_template.py#L346
[2]: https://github.com/odoo/odoo/blob/21e3310a3eba2ce915b33cf10ee4c14bdda7aad4/addons/website_sale/models/product_template.py#L384-L386
[3]: https://github.com/odoo/odoo/blob/21e3310a3eba2ce915b33cf10ee4c14bdda7aad4/addons/website_sale/models/product_template.py#L471-L483
[4]- https://github.com/odoo/odoo/blob/21e3310a3eba2ce915b33cf10ee4c14bdda7aad4/addons/website_sale/models/product_template.py#L482
[5]- https://github.com/odoo/odoo/blob/21e3310a3eba2ce915b33cf10ee4c14bdda7aad4/addons/website_sale/models/product_template.py#L445-L456
sentry-7660509183
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-prThe scheduled payroll data update has been optimized to avoid memory errors during execution. This helps payroll processing run smoothly in the background, reducing the risk of interruptions for HR teams.
Original PR description
From memory error to smooth execution
This fixes incorrect occupation calculations used for Belgian payroll holiday attestations. It helps ensure employee departure and payroll documents reflect accurate employment information, reducing payroll compliance and reporting errors.
Original PR description
This commit fixes the occupation computations which were wrong.
Fixes an issue where the Dimona action button could disappear after an employee record was auto-saved, preventing users from sending required Belgian employment declarations. The system now keeps these payroll actions correctly managed by the server and includes a test to prevent the problem from returning.
Original PR description
Steps to reproduce: - Create an employee, fill in name, CP, contract start date, wage. - Leave the form without saving manually (e.g. open another record). - Select the employee again: the Dimona button/badge is gone and stays gone regardless of further edits. l10n_be_needs_dimona_in and l10n_be_dimona_next_action are server-computed but readonly=False let autosave submit a stale value, permanently blocking the recompute. Drop the stray readonly=False on both and add a regression test. Task 6515877 Forward-Port-Of: odoo/enterprise#129731 Forward-Port-Of: odoo/enterprise#129648
This fixes an issue where manually typed dates or deadlines could be lost when users pressed Enter before saving. The change ensures entered date and time values are properly recognized, improving reliability in places like Fleet driver history and Sales milestones.
Original PR description
Example of steps: - Create a vehicle in Fleet - Set 2 drivers: one with a start and end date, one with no end date - Go back to the form view of the vehicle - Click on the smart button "Drivers…
Example of steps: - Create a vehicle in Fleet - Set 2 drivers: one with a start and end date, one with no end date - Go back to the form view of the vehicle - Click on the smart button "Drivers History" again - Add an end date manually (without using the calendar), e.g. 062626 - Hit the Enter key, then save (won't work if you don't hit enter) - Go back to the form view of the vehicle - Enter the "Drivers History" again => The change has not been saved. Also reproducible in Sales -> Milestone -> Deadline field `onInputKeydown` closes the picker on Escape and on Enter the same way: by calling `saveAndClose()` directly, without first calling `updateValueFromInputs()` to parse the raw text typed in the input into the reactive `pickerProps.value`. This is fine for Escape (its purpose is to discard the input), but not for Enter: when the calendar popover was never opened (i.e. the value was typed by hand instead of picked visually), `saveAndClose()` calls `apply()` directly, which only pushes `pickerProps.value` to `onApply`. Since that value was never refreshed from the input's text, `apply()` sees no change and silently returns without calling `onApply`, so the typed value is lost. Every other confirmation path (`onInputChange`, the popover's `onClose`, and `Ctrl+Enter`) already calls `updateValueFromInputs()` before proceeding, so plain Enter was the only path missing it. To fix this we call `updateValueFromInputs()` before `saveAndClose()` in the Enter case, like every other confirmation path already does. opw-6511435
Fixes an issue where choosing an Unsplash image from product image editing could fail instead of saving. Unsplash images are now converted and linked properly in the media dialog used by relational fields, reducing errors for users adding product visuals.
Original PR description
**Description of the issue/feature this PR addresses:** When selecting Unsplash images from a relational field (which uses `CustomMediaDialog`), the images would fail to process or link correctly to…
**Description of the issue/feature this PR addresses:** When selecting Unsplash images from a relational field (which uses `CustomMediaDialog`), the images would fail to process or link correctly to the target record. In the web editor, Unsplash image selections are handled by patching the base `MediaDialog`. When a user selects an Unsplash image, that patch intercepts the save action and uses the `unsplash` service to notify the backend. The server then fetches the external Unsplash URL and converts it into a native `ir.attachment` record before the frontend completes the save. Because `CustomMediaDialog` is a distinct component used for relational fields, it bypassed the existing `MediaDialog` patch entirely and lacked this specialized fetch-and-convert logic. This commit introduces a parallel patch specifically for `CustomMediaDialog`. It intercepts `imageSave`, routes any Unsplash records through the `unsplash` service, and replaces the raw Unsplash records with the newly generated Odoo attachments before executing the underlying save. **Steps to reproduce:** - POS > Products > Products > choose any product > click the ‘edit’ button in the image > search something, e.g. ‘burger’ > add Unsplash Access Key and Application ID when prompted > select one of the resulting Unsplash images **Current behavior before PR:** - Error when saving an unsplash image when editing product images **Desired behavior after PR is merged:** - No error when saving an unsplash image when editing product images opw-6445843
This fixes an error that could appear when creating a second accrual-based time off allocation for the same employee. Users can now select the accrual plan without the form failing, improving reliability for HR teams managing leave balances.
Original PR description
**Steps to Reproduce:** - Install the Time Off app and Belgian Payroll (l10n_be_hr_payroll). - Create and validate an accrual allocation for currently logged-in user with: No end date Any start date…
**Steps to Reproduce:** - Install the Time Off app and Belgian Payroll (l10n_be_hr_payroll). - Create and validate an accrual allocation for currently logged-in user with: No end date Any start date An accrual plan configured with a carry-over milestone Any Time Off Type - Create another allocation for the same employee, using the same accrual plan and same Time Off Type, but with a different start date that is not in the future. - Select the accrual plan. The error is raised immediately during the onchange. **Issue:** - When the accrual allocation onchange computes the accrued balance, a temporary allocation is used internally to simulate the accrual computation. - This temporary record is discarded after the computation. - The discarded temporary record can remain pending for computed field recomputation. - When the onchange later triggers recomputation, the stale temporary NewId can cause: `KeyError: <NewId origin=18>` **Root Cause:** - Temporary allocations created with 'new(origin=allocation)' can add computed fields to `env.transaction.tocompute`. - `invalidate_recordset()` clears the temporary record's cache but does not remove the `NewId` from the pending recomputation queue. - Since the temporary record has no database row to recompute from, the NewId can later be picked up during recomputation. - This can lead to a KeyError when the framework tries to access cached data for the discarded temporary record. **Solution:** - Properly discard temporary allocations after the accrual simulation. - In addition to invalidating the cache, remove the temporary NewId from `env.transaction.tocompute` using `remove_to_compute()`. - Use this cleanup for temporary allocations created with `new(origin=...)`. **Result** - Prevents the KeyError during accrual allocation onchange. - Allows users to create another allocation with the same accrual plan without triggering the RPC error. **opw-6390559** --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283772
Odoo now recognizes five new response codes introduced by Chile's tax authority for supplier electronic documents. This prevents affected supplier documents from getting stuck during processing and keeps the workflow aligned with the latest SII rules.
Original PR description
**Before this PR:** After the implementation of Resolution 161 of November 13th 2025, SII responses included keys not supported by the current l10n_cl_edi implementation. This resulted in supplier DTEs not being processed as they were before the change, because the five new keys were not found in Odoo's current `l10n_cl_claim` field, causing that the documents with these responses, were kept in a loop not solved. **After this PR:** The five new values from the resolution, along with their translations, were added to the selector field, fixing the process flow. **SII Reference:** https://www.sii.cl/normativa_legislacion/resoluciones/2025/reso161.pdf (see Event Code, page 5) Forward-Port-Of: odoo/enterprise#121833
CFDI invoice XML files generated through batch Send & Print now use the correct translated unit of measure instead of defaulting to English. This keeps Mexican electronic invoices consistent with the customer's language and the invoice PDF, reducing confusion and compliance errors.
Original PR description
### Issue before this commit: When a CFDI invoice was sent through a mass action (Send & Print executed by OdooBot), the unit of measure in the generated CFDI appeared in English instead of the…
### Issue before this commit: When a CFDI invoice was sent through a mass action (Send & Print executed by OdooBot), the unit of measure in the generated CFDI appeared in English instead of the configured language (e.g. Spanish. ### Steps to reproduce the issue: 1.Download Accounting, Contacts and l10n_mx 2. Switch to Spanish (MX) language 3. Set the language of "Inmoviliaria CVA" and "XENON INDUSTRIAL ARTICLES" to Spanish (MX) 4. In contacts select Archived in filters and switch OdooBot language to Spanish (MX) 5. Create and confirm two invoices in the database, one for each customer but don't send these invoices 6. Go to the list view and select these two invoices, and click on "Send and print" and select CFDI 7. Check any of the XML files generated in any of the invoices (in the CFDI tab of the invoice) 8. See that the UoM in the XML file, will be set in english rather than Spanish (MX) ### Cause of the issue: The context under which the batch action runs does not contain the lang key. As a result, translated fields (such as product_uom_id.name) were read in the source language (English) instead of the executing user's language, because nothing in the CFDI generation chain explicitly forced the correct lang into the context. ### Reason to introduce the fix: A CFDI must always report translated fields in the correct language. The fix ensures the invoice is read with the executing user's language when the context doesn't already specify one, so translated fields are consistently correct across both flows. opw-6399860 Forward-Port-Of: odoo/enterprise#129829 Forward-Port-Of: odoo/enterprise#125698
This fix prevents the AI website SEO update from failing when an AI agent uses certain document-based sources, such as generated invoice PDFs. Business users can now use “Update With AI” more reliably with documents managed through Odoo.
Original PR description
### Problem When an AI agent includes a document source whose underlying `ir.attachment` has `res_field` set (e.g., an invoice PDF generated from a Sale Order), triggering **Update With AI** from…
### Problem
When an AI agent includes a document source whose underlying `ir.attachment`
has `res_field` set (e.g., an invoice PDF generated from a Sale Order),
triggering **Update With AI** from **Website → Site → Optimize SEO**
raises a `KeyError` in `_build_rag_context`.
### Steps to Reproduce
1. Open the **AI** app.
2. Configure the **Odoo Agent**.
3. Add a source → **Add From Documents**.
4. Select a document whose underlying `ir.attachment` has `res_field` set
(e.g., an invoice PDF generated from a Sale Order).
5. Go to **Website → Site → Optimize SEO**.
6. Click **Update With AI**.
7. Observe the following error:
```
KeyError: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
```
[Video](https://drive.google.com/file/d/1AAg0AvC3TRQzMLhI9hWFuSarOTHzQE-b/view?usp=sharing)
### Root Cause
In `ai/models/ai_agent.py`, `_build_rag_context()` retrieves the
`ai.agent.source` records corresponding to the embeddings by searching on the
attachment checksum:
```python
agent_sources = self.env["ai.agent.source"].search([
("attachment_id.checksum", "in", embeddings_attachment_checksums),
("agent_id", "=", self.id),
])
```
This domain traverses `attachment_id.checksum`, which internally calls
`ir.attachment._search()`.
As part of the standard attachment search behavior,
`ir.attachment._search()` automatically injects a
`('res_field', '=', False)` filter unless:
- `skip_res_field_check=True` is set in the context,
- the domain explicitly references `id` or `res_field`, or
- `bypass_access` is enabled.
```python
domain = Domain(domain)
if (
not self.env.context.get("skip_res_field_check")
and not any(d.field_expr in ("id", "res_field") for d in domain.iter_conditions())
and not bypass_access
):
disable_binary_fields_attachments = True
domain &= Domain("res_field", "=", False)
```
[Reference](https://github.com/odoo/odoo/blob/19.0/odoo/addons/base/models/ir_attachment.py#L630)
Because of this implicit filter, attachments with `res_field` set are excluded
from the search. Consequently, `agent_sources` does not contain all the sources
corresponding to the retrieved embeddings.
Later, `_build_rag_context()` builds a checksum-to-source mapping:
```python
source_map = {
source.attachment_id.checksum: source
for source in agent_sources
}
for embedding in similar_embeddings:
checksum = embedding.attachment_id.checksum
agent_source = source_map[checksum]
```
Since `source_map` is built from the incomplete `agent_sources` recordset, it is
missing entries for attachments filtered by `ir.attachment._search()`.
However, `similar_embeddings` still contains embeddings for those attachments.
As a result, the lookup:
```python
agent_source = source_map[checksum]
```
raises a `KeyError`.
### Solution
Bypass the implicit `res_field` filter when searching `ai.agent.source`:
```python
agent_sources = (
self.env["ai.agent.source"]
.with_context(skip_res_field_check=True)
.search([
("attachment_id.checksum", "in", embeddings_attachment_checksums),
("agent_id", "=", self.id),
])
)
```
This ensures that all `ai.agent.source` records matching the requested
attachment checksums are returned, including those referencing attachments with
`res_field` set. As a result, `source_map` contains all expected entries and
`_build_rag_context()` no longer raises a `KeyError`.
opw-6379816
Forward-Port-Of: odoo/enterprise#125780Belgian employees without HR permissions can now create time off allocation requests without hitting an access error when selecting an allocatable time off type. The payroll rule parameter is read with the proper elevated access so the workflow works as expected while preserving user permissions.
Original PR description
Issue: ---------------------------------------- As a user without employee rights creating a new allocation and selecting a belgian time off type results in an Access Error. Steps to reproduce: ---------------------------------------- - On a Belgian company - Create a time off type "test" and allow allocation - Connect as a user without HR rights - Try creating a new allocation, select the created time off type - Error Cause: ---------------------------------------- In `_get_max_duration()` we call `_get_parameter_from_code()` without using `sudo()`. As the current user doesn't have access rights to `'hr.rule.parameter.value'` it raises later in `_get_cached_parameter_from_code()` when we try reading. Solution: ---------------------------------------- Call `_get_parameter_from_code()` with `sudo()` opw-6479412
Clearing an internal note on a restaurant point-of-sale order line no longer makes the preparation display cancel and recreate the item. This prevents kitchen staff from seeing the same quantity as a new order and reduces the risk of preparing items twice.
Original PR description
Steps to reproduce ------------------ 1. Open a restaurant PoS with a preparation display. 2. Add a product, put the internal note "A" on the line, press Send. 3. Change the note from "A" to "B",…
Steps to reproduce ------------------ 1. Open a restaurant PoS with a preparation display. 2. Add a product, put the internal note "A" on the line, press Send. 3. Change the note from "A" to "B", then open the note again and click "discard", that will clear it. 4. Press Send. -> The line is fully cancelled on the preparation display and the same quantity is sent again as a new order. Expected Behavour ----------------- The existing line should be updated in-place instead of cancelling and re-creating a new one. Why it's happening ------------------ In `_process_preparation_changes` we calculate a key for every line and the `note` is inside this key. For the order lines we take `line.note or "[]"`, but for the note history we take `note['new'] or ''`, in that case, the keys don't match, so we cancel the current one and create a new line!! The fix ------- We use `"[]"` now, in many places (for completness), a follow up of 484ef2e8b2b. opw-6467346 Forward-Port-Of: odoo/enterprise#128601
This fix ensures delivery charge lines keep the correct unit of measure when they are created. It prevents Odoo from recalculating related sales amounts incorrectly, helping avoid wrong prices or totals on sales orders with shipping.
Original PR description
In this PR, https://github.com/odoo/odoo/pull/186250, the `product_uom` field was renamed to `product_uom_id`. However, in the `delivery` module, `product_uom_id` is dropped from the values when the…
In this PR, https://github.com/odoo/odoo/pull/186250, the `product_uom` field was renamed to `product_uom_id`. However, in the `delivery` module, `product_uom_id` is dropped from the values when the delivery line is created. This causes `product_uom_id` to be recomputed. This commit reintroduces `product_uom_id` in the values to prevent the field from being recomputed. **Description of the issue/feature this PR addresses:** For a strange reason, when a module inherits from `sale.order.line` and adds some computed fields with `precompute=True`. `price_unit`, `price_subtotal`, and `price_total` are computed incorrectly. I have attached a module to demonstrate the issue. https://github.com/user-attachments/assets/ebdd8695-c9d8-477b-b5cf-ba6d8d41e84a Without this change, the test fails, and Odoo incorrectly recomputes the fields, as shown in the video. <img width="1232" height="515" alt="image" src="https://github.com/user-attachments/assets/27370f1f-e7b7-4102-a606-0181b9d1a97a" /> When the ORM computes fields marked as `precompute=True`, in this function `_add_precomputed_values` https://github.com/odoo/odoo/blob/0d44f26d9b0fb1c1a5db463cf1f8dd0d3c72ba26/odoo/orm/models.py#L4836, `price_unit` is 0, but the records get `price_unit` from the product. Therefore, when [_compute_amount](https://github.com/odoo/odoo/blob/0d44f26d9b0fb1c1a5db463cf1f8dd0d3c72ba26/addons/sale/models/sale_order_line.py#L855) is called, the values are computed with an incorrect `price_unit`. <img width="1087" height="940" alt="image" src="https://github.com/user-attachments/assets/92feadf0-7f93-4acc-8f1a-931db0655fcc" /> **Steps to reproduce the issue:** - Install the attached module. [sale_precompute.zip](https://github.com/user-attachments/files/31265993/sale_precompute.zip) - Configure a delivery carrier as free for orders over 1, and set the fixed price to 5, for example. - Create a sales order and add a product with a value greater than 1. - Add the shipping method. The price should be 0. In the sales order line, `price_unit` is 0, but `price_subtotal` and `price_total` are equal to 5 (the product's sale price). For more context, this module is a simple example extracted from the OCA `product_secondary_unit` module, which adds a mixin with these fields: https://github.com/OCA/product-attribute/blob/18.0/product_secondary_unit/models/product_secondary_unit_mixin.py. In the `sale_order_secondary_unit` module, `sale.order.line` inherits from this mixin. You can see the error in this PR: https://github.com/OCA/sale-workflow/pull/4535. https://github.com/OCA/sale-workflow/actions/runs/32259842816/job/96090194021?pr=4535#step:8:509 I understand that this requires a deeper investigation into precompute to solve the underlying issue, but I propose setting `product_uom_id` in the `_prepare_delivery_line_vals` method as a temporary solution while the final solution is being investigated. I understand that this field should not have been removed from `_prepare_delivery_line_vals`; the referenced PR only renamed the field and did not intend to remove the value from this method. @Tecnativa @pedrobaeza @kcv-odoo @Feyensv coudl you please review this? --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283551
This fixes week calculations when a business uses Monday as the first day of the week in a locale that normally starts weeks on another day. Weekly totals and limits, such as resource hours caps, will no longer be split incorrectly across two weeks in that scenario.
Original PR description
**Description of the issue/feature this PR addresses:** `weeknumber` takes a `first_week_day` override, documented as `(0 = Monday, ..., 6 = Sunday). If None, derived from the locale.`, but tests it…
**Description of the issue/feature this PR addresses:** `weeknumber` takes a `first_week_day` override, documented as `(0 = Monday, ..., 6 = Sunday). If None, derived from the locale.`, but tests it with `if not first_week_day`, so Monday (`0`) is discarded and the locale's own day is used. `resource` passes `int(get_lang(self.env).week_start) - 1`, which is exactly `0` for Monday, at three call sites. #259600 added the parameter for the opposite case, a Monday locale with a Sunday-start user, and that direction works; this one never has. **Current behavior before PR:** With `en_US` (first day Sunday) and week start set to Monday, `resource_resource.py:299` builds `week_start_date` from Monday while `:303` buckets by Sunday. Monday 2026-08-24 to Sunday 2026-08-30 comes back as W35 for six days and W36 for the Sunday, so one week's hours are split across two buckets and the `hours_per_week` cap lands on the wrong days. **Desired behavior after PR is merged:** The override is honoured and those seven days are all W35. I checked this with a partition property: every day of a `first_week_day`-aligned week must share one `(year, week)`. Over 9 locales x 7 values of `first_week_day` x 12 years, 275940 combinations, 5 locales failed and every failure was in the `first_week_day=0` bucket. After the change there are none. I also diffed every output before and after, 315360 combinations of locale, `first_week_day` and date: 4460 rows change, all of them `first_week_day=0` on a non-Monday locale. `None` and values 1 to 6 are byte identical, so the change is contained to the broken case. Targeting 19.0 because that is the oldest branch carrying the parameter; 17.0 and 18.0 do not have it. AI-assisted: I used Claude to sweep the parameter space and write the tests. Forward-Port-Of: odoo/odoo#285706 Forward-Port-Of: odoo/odoo#285508
Opening a default website page from the Pages list now keeps users on their current Odoo host instead of sending them to the website’s configured public domain. This prevents unexpected login prompts and helps users keep their editing context when managing website pages.
Original PR description
Steps to reproduce: =================== 1. Go to Website > Pages 2. Set a real domain on the default website 3. Open any page linked to that website => User is redirected to the real domain When clicking on a page linked to the default website from the Website > Pages list, if that website had a real domain configured, the action was redirecting the user to that domain instead of staying on the current host (e.g. <db>.odoo.com). since it's a different domain the user would then end up in the login page and lose the context of the page they wanted to open. Solution: ========= Now we change the behavior as requested by PO. On <domain>.odoo.com, in the Pages list view, if the page is linked to the default website, keep <domain>.odoo.com and don't redirect to the real domain => User should stay on the current host opw-6141457 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#261698
The purchase catalog's "Add All" action now uses the unit of measure defined on the product's vendor information, matching the behavior of adding items one by one. This helps avoid purchase orders being created with the wrong units or quantities, reducing ordering mistakes and manual corrections.
Original PR description
Issue ----- When adding all of the catalog's suggestions at once, the uom specified on the product's vendor lines is not respected. Steps to reproduce ----- - Create a product - Add a vendor line in…
Issue ----- When adding all of the catalog's suggestions at once, the uom specified on the product's vendor lines is not respected. Steps to reproduce ----- - Create a product - Add a vendor line in a different uom - Create a past outgoing shipment of 100 of the product - Create a PO (same vendor as vendor line) - Open the Catalog - Click "Add All" in the suggestion section on the left - Go back to the PO > The product is in units instead of the different uom Cause ----- Clicking the button calls `action_purchase_order_suggest` in Python directly https://github.com/odoo/odoo/blob/3aec1c317aad547b1ad0c85a21cc53f735c5cbfd/addons/purchase_stock/models/purchase_order.py#L131-L134 whereas clicking on a product will go through `addProduct` in JS https://github.com/odoo/odoo/blob/3aec1c317aad547b1ad0c85a21cc53f735c5cbfd/addons/purchase_stock/static/src/product_catalog/record/kanban_record.js#L20-L28 Which leads to `_update_order_line_info` where the purchase line is created https://github.com/odoo/odoo/blob/f59783d43f891fea14d03f67d7c017d7ffbbe5a7/addons/purchase/models/purchase_order.py#L1322-L1328 The uom is then retrieved in the create call through `_suggest_quantity` https://github.com/odoo/odoo/blob/f59783d43f891fea14d03f67d7c017d7ffbbe5a7/addons/purchase/models/purchase_order_line.py#L547-L549 Other issue ----- The quantity of the product also doesn't match the suggestion since the product `suggested_qty` is in the product's uom and not the vendor ones. ----- Ticket: opw-6417665 Forward-Port-Of: odoo/odoo#284841 Forward-Port-Of: odoo/odoo#280934
The activity menu now correctly opens Project Updates filtered to the current user's own activities and the selected timing bucket, such as late or upcoming. This prevents users from seeing unrelated project updates when following activity counts, making the menu more accurate and less confusing.
Original PR description
**Problem:** Opening a Project Update activity from the activity menu (the clock icon in the systray) lists every project update of every user, instead of the ones carrying the current user's…
**Problem:**
Opening a Project Update activity from the activity menu (the clock icon in the systray) lists every project update of every user, instead of the ones carrying the current user's activities.
**Steps to reproduce:**
1. Schedule an overdue activity on a project update
2. Have a colleague create another project update with no activity
3. Click the clock icon in the systray
4. Under "Project Update", click the "Late" count
**Current behavior:**
The list opens unfiltered and shows all project updates, including the ones of other users and the ones carrying no activity at all.
**Expected behavior:**
The list shows only the updates carrying the user's own activities, restricted to the bucket that was clicked.
**Cause of the issue:**
The activity menu never builds a "my activities" domain. `openActivityGroup` in `mail/static/src/core/web/activity_menu.js` narrows the generic act_window it opens purely through context keys — `search_default_filter_activities_my` plus `search_default_activities_overdue` / `_today` / `_upcoming_all` — and the only domain it forwards comes from `_get_activity_groups`, which is limited to `[('active', 'in', [True, False])]`. A `search_default_<name>` key is resolved against a filter of that name in the model's search view, and is silently dropped when no such filter exists. `project.update`'s search view never declared them, so every key the menu sends is discarded and the action opens on an empty domain.
**Fix:**
`project.project` and `project.task` — the addon's two other `mail.activity.mixin` models — already declare this block of invisible activity filters, as does every other model reachable from the activity menu. Declaring them on `project.update` is what makes the menu's context keys resolvable, and keeps the model consistent with the rest of the codebase instead of special-casing `project.update` on the client side.
opw-6416397
Forward-Port-Of: odoo/odoo#281511Fixed an issue where Argentina website product pages showed a tax-excluded price with the discount applied twice. This keeps product detail prices consistent with shop catalog prices, reducing customer confusion during online shopping.
Original PR description
Steps to produce: --- - Create a company with `Argentina` as the country. - Switch to the newly created company. - Create a new website for the `Argentina company`. - Create a pricelist with a `23%`…
Steps to produce: --- - Create a company with `Argentina` as the country. - Switch to the newly created company. - Create a new website for the `Argentina company`. - Create a pricelist with a `23%` discount on the sales price for all products. - Create a new product, set the sale price to 100, remove all tax, and publish. - Go to the website and switch to the newly created Argentina company website. - Go to the Shop page and open the product. Issue: --- - The tax-excluded price (`Precio s/Imp. Nac.`) displayed on the shop catalog card differs from the tax-excluded price displayed on the product detail page. Root cause: --- - In `_get_additionnal_combination_info`, [1] returns the unit price with the pricelist discount already applied. However, when `combination_info['has_discounted_price']` [2] is `True`, the method applies the discount again manually, resulting in the pricelist discount being applied twice on the product detail page. Solution: --- - Remove the redundant discount calculation block. This ensures that the tax-excluded price displayed on the product detail page matches the price shown on the shop catalog card. [1]https://github.com/odoo/odoo/blob/7f9560bd0ff66882459593a2d043c0197ceec0bb/addons/l10n_ar_website_sale/models/product_template.py#L61-L62 [2]https://github.com/odoo/odoo/blob/7f9560bd0ff66882459593a2d043c0197ceec0bb/addons/l10n_ar_website_sale/models/product_template.py#L71-L74 opw-6480531 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283761
Fixes Polish e-invoice reporting so K_12 tax is correctly treated as reverse charge. This helps invoices sent to KSeF include the expected reverse charge indicator and tax base, reducing compliance errors.
Original PR description
**PROBLEM** K_12 taxes needs to be reported as reverse charge tax. **STEP TO REPRODUCE** 1. Create an invoice with the tax 0% EU U. 2. Send the invoice the ksef. 3. Open the xml, an notice P_18 value is 2 (meaning no reverse charge), and there is no tag P_13_10. expected behavior: P_18 = 1, P_13_10 = base for the tax. opw-6460338 Forward-Port-Of: odoo/odoo#281934
The Brazilian AvaTax sales app now installs correctly when automatic installation is skipped. This prevents setup failures caused by missing sales tax fields, helping users enable the app reliably.
Original PR description
When installing l10n_br_avatax_sale with --skip-auto-install, you'll get an error about the l10n_br fields listed in views/sale_order_views.xml, because these fields don't fully exist without sale_external_tax. This happens because they're defined on a mixin, which is an abstract model. Abstract models only add their fields to a model that actually lists them in `_inherit`. sale.order should list this mixin, but currently doesn't. Adding that dependency is an unstable fix, so it will be added in master (20.0, or 20.1) runbot-237866 Forward-Port-Of: odoo/enterprise#128142
This update prevents an error when a contact has multiple signature requests and their email address is changed. It ensures each signing request is handled through the correct delivery channel, so users can update contact details without interruptions.
Original PR description
Steps to reproduce: ---------------------------------------- 1. Install `whatsapp_sign` and Contact modules 2. Go to Sign > templates 3. Make two sign requests for the same partner 4. Go to partner…
Steps to reproduce:
----------------------------------------
1. Install `whatsapp_sign` and Contact modules
2. Go to Sign > templates
3. Make two sign requests for the same partner
4. Go to partner record and try to change the email (remove or add a character)
Observation:
----------------------------------------
Traceback occurs:
```
File '/home/odoo/odoo/enterprise/whatsapp_sign/models/sign_request_item.py', line 101, in _send_signature_access_message
is_whatsapp = self.sign_request_id.send_channel == 'whatsapp'
File '/home/odoo/odoo/community/odoo/orm/fields.py', line 1657, in __get__
record.ensure_one()
File '/home/odoo/odoo/community/odoo/orm/models.py', line 5942, in ensure_one
raise ValueError('Expected singleton: %s' % self)
ValueError: Expected singleton: sign.request(3, 4)
```
Issue:
----------------------------------------
In `_send_signature_access_message()` which accessed `self.sign_request_id.send_channel` directly on the full multi-record recordset. When `res_partner.write()` detects an email change, it searches for all sent sign request items of that partner and calls `send_signature_accesses()` on the combined recordset. Accessing `sign_request_id` on this multi-record set resolved to multiple sign requests, and the subsequent field access triggered `ensure_one()`.
Solution:
----------------------------------------
* Refactored `_send_signature_access_message()` to iterate over each item individually, checking `send_channel` per item's own `sign_request_id`. Items are partitioned into `email_items` and `whatsapp_items` batches. Email items are delegated to `super()` and WhatsApp items are processed individually.
* Refactored to cleaner and more readable code and correctly handles mixed-channel recordsets where items may belong to different sign requests with different send channels.
opw-6387586
Forward-Port-Of: odoo/enterprise#128953
Forward-Port-Of: odoo/enterprise#125075