Friday, April 10, 2026
15 changes · saas-18.4
Resolved issues and error corrections
Portal task lists now avoid counting sub-task allocated hours twice when the parent task is also shown. Customers see a more accurate total for planned and spent time, reducing confusion in project tracking.
Original PR description
Steps to Reproduce: - 1. In the Project app, create a new project and enable "Timesheets". 2. Create a parent task with allocated hours (e.g., 20h). 3. Create one or more sub-tasks under the parent,…
Steps to Reproduce: - 1. In the Project app, create a new project and enable "Timesheets". 2. Create a parent task with allocated hours (e.g., 20h). 3. Create one or more sub-tasks under the parent, also with allocated hours (e.g., 8h and 5h). 4. Log in to the portal and navigate to the project's task list. 5. Observe the "Total" allocated time shown in the list header. Issue: - - The total allocated time displayed in the portal incorrectly sums the hours of the parent task and all its sub-tasks (e.g., 20h + 8h + 5h = 33h). This leads to an inflated and confusing total for the customer. Cause: - - The `_get_portal_total_hours_dict` method calculated the sum of `allocated_hours` on the entire recordset of tasks passed to it, without distinguishing between parent tasks and their children when both were present. Fix: - - This commit excludes sub-task hours from the total allocated time computation if their parent task is also present in the view. - The total time spent now uses parent `total_hours_spent` which includes both time spent on parent and sub-task. task-4939234 Forward-Port-Of: odoo/odoo#221563
This fix stops users from removing a lot or serial number from a completed manufacturing order when the product's inventory value depends on that lot. This prevents orders from getting stuck in a state where the lot cannot be corrected and the order cannot be undone, protecting traceability and valuation accuracy.
Original PR description
When we erase a sn/lot on a MO for a product that is tracked by lot, we will be deadlocked. ### Steps to reproduce: * Create a product tracked by lot and lot_valuated * Create a BoM for this product…
When we erase a sn/lot on a MO for a product that is tracked by lot, we will be deadlocked. ### Steps to reproduce: * Create a product tracked by lot and lot_valuated * Create a BoM for this product * Create a MO for this product * Confirm and Produce all * erase the lot and save -> the MO is deadlock, it's not possible to modify the lot number, nor it's possible to unbuild. ### Current behavior: A user is able to erase a lot/SN from a MO of a lot_valuated product. ### Expected behavior: It should not be possible to erase a lot/SN form a MO of a lot_valuated product. ### Observation: When the MO is done, its valuation will be calculated which in our case, it means, we have to have a lot number. When trying to add a lot/SN of a MO where it has been erased, we will remove the quantities from the previous lot, but in our case, since we don't have one, it will trigger the user error. https://github.com/odoo/odoo/commit/33e192de30526ea7fe320bcaa7a16dac8108feff This error is not triggered when removing the SN/lot because when we remove the value, it will be update to False which means it will skip _update_svl_quantity(): https://github.com/odoo/odoo/blob/109f829c2b461b14167e9227e42d096d4410a3b3/addons/stock_account/models/stock_move_line.py#L35-L36 https://github.com/odoo/odoo/blob/109f829c2b461b14167e9227e42d096d4410a3b3/addons/stock_account/models/stock_move_line.py#L62-L65 This use case is also protected when creating a product tracked by lot but not lot_valuated: https://github.com/odoo/odoo/commit/4963103e5587d36364b8df84d0b3407a1977efdf opw-6039885 Forward-Port-Of: odoo/odoo#257949 Forward-Port-Of: odoo/odoo#255098
Event sales totals now use the correct currency conversion when tickets are sold in a currency different from the company currency. This prevents overstated or understated sales figures on the event page, helping teams rely on accurate event revenue reporting.
Original PR description
Steps to reproduce: 1. Create a currency with a non-1 exchange rate with the company's currency (e.g. VEF with a rate of 0.000005 against USD). 2. Create a pricelist in that currency. 3. Create an…
Steps to reproduce: 1. Create a currency with a non-1 exchange rate with the company's currency (e.g. VEF with a rate of 0.000005 against USD). 2. Create a pricelist in that currency. 3. Create an event. 4. Create a sale order with the new pricelist. 5. Add a sale order line with a ticket of the event and confirm the order. 6. Go to the event's page and check the total sales smart button. 7. Check the total sales of the event: it should be equal to the sale order's total price converted to the company's currency, but it is not, because of the wrong conversion (it used the inverse of the correct exchange rate, which is 200000 instead of 0.000005 in our example). Problem: The total sales smart button in an event's page shows wrong totals when sales are in a currency other than the company's currency. Cause: The code converts the sale price from the event's currency (which is the same as the company's currency) to each sale order's currency, while it should be the other way around (from each sale order's currency to the event's currency). https://github.com/odoo/odoo/blob/3cd709172e997f5a726cf3ae85ffcb9965619fcb/addons/event_sale/models/event_event.py#L38 opw-5494790 Forward-Port-Of: odoo/odoo#258000 Forward-Port-Of: odoo/odoo#253605
Portal users can now use "Search More" on task tag fields without hitting an error. The fix ensures a task-specific filter is only applied where it belongs, improving reliability when shared project tasks are edited through the portal.
Original PR description
Steps to reproduce: -------------------------- 1. Install Project and create a portal user. 2. Create a project and a task with tags added to it. 3. Share the project with the portal user (Edit…
Steps to reproduce:
--------------------------
1. Install Project and create a portal user.
2. Create a project and a task with tags added to it.
3. Share the project with the portal user (Edit access).
4. Log in as the portal user and open the task.
5. Click on the 'Tags' field and select 'Search More'
Issue:
----------
A traceback occurs:
```ValueError: Invalid field project.tags.display_in_project in condition ('display_in_project', '=', True)```
Cause:
----------
The [ProjectTaskRelationalModel](https://github.com/odoo/odoo/blob/75fd7b05766c6789bf311f6a8257b89ecf4f86de/addons/project/static/src/views/project_task_relational_model.js#L4-L7) calls `_processSearchDomain.`
This method automatically appends a domain when 'showSubtasks' is not enabled.
https://github.com/odoo/odoo/blob/75fd7b05766c6789bf311f6a8257b89ecf4f86de/addons/project/static/src/views/project_task_model_mixin.js#L5-L12
When clicking 'Search More' on the 'Tags' field, the same domain is
applied to the **project.tags** model. However, since **project.tags**
does not have the `display_in_project` field, the ORM raises a ValueError.
In portal, only `project.webclient` assets are loaded, causing
ProjectTaskRelationalModel to be used for all relational fields.
In contrast, internal users use the standard RelationalModel, so the
issue does not occur.
Solution:
-------------
Restrict the domain modification in _processSearchDomain to apply only
when the current model is `project.task` and `report.project.task.user`.
**NOTE:**
Alternatively, we could guard this specifically for portal usage by
skipping the domain when the action is project_sharing and the model
is project.tags, e.g.:
```python
if (router.current.action === 'project_sharing' && this.env.searchModel.resModel === 'project.tags') {
return domain;
}
```
opw-5930427
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-prInvoice imports through Peppol are now more resilient when an attached XML file is empty or invalid. Instead of crashing, the system creates the invoice using safe defaults and records the issue so it can be investigated.
Original PR description
Empty attachments could crash parsing during import, this commit falls back on default journal and move type in case of error, allowing the invoice to be created and the issue logged properly. Root cause of empty XML remains unclear, likely a 3rd party error. opw-6060739 opw-6018364 Forward-Port-Of: odoo/odoo#258205 Forward-Port-Of: odoo/odoo#256011
Agents already participating in an active live chat can now still see the Live Chat button and start another conversation as a visitor. This prevents blocked workflows when agents need to open additional chats without leaving their current sessions.
Original PR description
Previously, users who were part of active livechats as agents could not see the livechat button to start a new conversation. This change ensures the button remains visible so users can start additional livechats as a visitor. task-[5119098](https://www.odoo.com/odoo/project/1519/tasks/5119098) Forward-Port-Of: odoo/odoo#253894
This update refreshes Odoo's spreadsheet engine and fixes several issues that could affect formatting, file exports, and browser-based spreadsheet rendering. Business users should see more reliable spreadsheet behavior, especially for accounting-style negative numbers and Excel exports involving formulas that return errors.
Original PR description
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/3487bc7740 [REL] 18.4.34 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0)…
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/3487bc7740 [REL] 18.4.34 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/644df5354b [FIX] format: keep negative accounting suffix in large number [Task: 6068834](https://www.odoo.com/odoo/2328/tasks/6068834) https://github.com/odoo/o-spreadsheet/commit/8c41458a64 [IMP] tests: add image snapshots for renderer store tests [Task: 5933130](https://www.odoo.com/odoo/2328/tasks/5933130) https://github.com/odoo/o-spreadsheet/commit/59d556da8b [FIX] renderer: rendering tests are wrong [Task: 5933130](https://www.odoo.com/odoo/2328/tasks/5933130) https://github.com/odoo/o-spreadsheet/commit/588cec13a0 [IMP] tests: update jest to v30 [Task: 5933130](https://www.odoo.com/odoo/2328/tasks/5933130) https://github.com/odoo/o-spreadsheet/commit/8bec83ed9f [FIX] cf: add sanity checks on conditional format type/operator [Task: 6106092](https://www.odoo.com/odoo/2328/tasks/6106092) https://github.com/odoo/o-spreadsheet/commit/facda730d2 [FIX] live-server: specify host to avoid issues with some browsers [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/4b563dc44f [FIX] xlsx: export formula that returns an error [Task: 6008904](https://www.odoo.com/odoo/2328/tasks/6008904) Co-authored-by: Florian Damhaut (flda) <flda@odoo.com> Co-authored-by: Anthony Hendrickx (anhe) <anhe@odoo.com> Co-authored-by: Alexis Lacroix (laa) <laa@odoo.com> Co-authored-by: Lucas Lefèvre (lul) <lul@odoo.com> Co-authored-by: Adrien Minne (adrm) <adrm@odoo.com> Co-authored-by: Ronak Mukeshbhai Bharadiya (rmbh) <rmbh@odoo.com> Co-authored-by: Dhrutik Patel (dhrp) <dhrp@odoo.com> Co-authored-by: Rémi Rahir (rar) <rar@odoo.com> Co-authored-by: Pierre Rousseau (pro) <pro@odoo.com> Co-authored-by: Vincent Schippefilt (vsc) <vsc@odoo.com> Co-authored-by: Marceline Thomas (matho) <matho@odoo.com>
German invoice previews now show only the delivery contact name instead of combining it with the parent company name. This helps avoid invoices that appear to list two separate legal entities, reducing tax compliance risk for German businesses.
Original PR description
If two GmbH (Ltd.) names appear in the invoice address field (e.g., "Proveco GmbH, Test GmbH"), the invoice is problematic from a tax perspective, as two GmbH are considered separate legal entities, and it is not clearly identifiable who the actual recipient of the service is. Consequently, the tax office can refuse the input tax deduction if the invoice recipient is not clearly identifiable. Steps: - Create a company contact (X) and a delivery address (Y) - Create an invoice for X, delivery address will be Y - Open preview -> The delivery partner's display name is 'X, Y', it should be only 'Y' Fix: Adding a context key to the invoice document to conditionally display the parent contact name in the display_name Ticket [link](https://www.odoo.com/odoo/project.task/5900567) opw-5900567 Forward-Port-Of: odoo/odoo#258452 Forward-Port-Of: odoo/odoo#257652
This update prevents editing tools from appearing when users are working entirely inside inline code, reducing accidental formatting changes. It also keeps pasted rich content as plain text inside inline code, helping preserve clean and predictable document formatting.
Original PR description
### Purpose of this commit: - Prevent the powerbox and toolbar from opening when the selection is fully inside inline code. When the selection spans inline code and regular text, keep the toolbar visible but ensure formatting commands are applied only to the non-inline-code content. - Ensure that pasted external and editor HTML is converted to plain text when inserted inside inline code. task-5502939 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#258190 Forward-Port-Of: odoo/odoo#250911
Swiss QR-bill payment codes now remove characters that banks do not accept, such as narrow non-breaking spaces. This helps prevent payment QR codes from being rejected and improves reliability for Swiss invoicing workflows.
Original PR description
**Description of the issue/feature this PR addresses:** QR code is rejected by the bank, when it contains an invalid character `U+202F`. **Current behavior before PR:** Unauthorized Unicode characters are encoded in the QR-Bill, and it is rejected on the receiving part. **Desired behavior after PR is merged:** Any Unicode codepoint which is not in the subset of 324 allowed codepoints has to be filtered out. > spec of QR-bill allows only a subset of characters, a precise list of 324 Unicode codepoints (section 4.1.1, page 30 of the Swiss Implementation Guidelines for the QR-bill) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#257961 Forward-Port-Of: odoo/odoo#254980
This fixes several cases where website and HTML editor image tools showed incorrect options or failed after replacing images with protected, SVG, or GIF files. Users will see more accurate image information and only compatible editing controls, reducing errors while editing website content.
Original PR description
[FIX] html_builder, *: hide the size of CORS-protected images *: html_editor Steps to reproduce: - Add an image on the website. - Replace it with a CORS-protected image. -> The image options display…
[FIX] html_builder, *: hide the size of CORS-protected images *: html_editor Steps to reproduce: - Add an image on the website. - Replace it with a CORS-protected image. -> The image options display a size, but it is incorrect. The problem is that it is not the real size of the image but the size of a default image (due to it, if you replace the image by another CORS protected one, you'll see that the size of the image remains the same). Indeed, the size of an image (in bytes) is computed from the length of the raw b64 content of the image, on which a ratio of 3/4 is applied. Because the image is CORS protected, we can not retrieve the raw b64 of the image so the image size should not be displayed. This commit hides the size of CORS protected image as it is impossible to retrieve. Note: example of a CORS protected image: https://tinyjpg.com/images/social/website.jpg task-5405262 --------------------------------------------------------------------------------------------------------------------------------------------- [FIX] html_builder, *: correctly determine image mimetype *: html_editor The goal of this commit is to improve the way the mimetype of an image is determined if the information is not in the DOM. Before this commit, the system relied on the extension of the image source to determine its mimetype. This is not really robust and it is easily trickable. For example, in `html_editor`, if an image in a html field comes from an attachment, its `src` attribute will end by the attachment name. If a user changes the attachment name extension, the next time the image is added on the DOM, the extension is changed but the mimetype of the image is unchanged. To solve the problem, the mimetype of the image is determined thanks to the headers of the http request to the `src` of the image. That way, the information comes from the server hosting the image. task-5405262 --------------------------------------------------------------------------------------------------------------------------------------------- [FIX] html_builder, website: enable the quality change on shaped img This commit improves [this one] by adding a test (this commit was created before [this one] was merged). [this one]: https://github.com/odoo/odoo/commit/738d5fb5ae2154e1f6993817fab5471d2d4384fa task-5405262 --------------------------------------------------------------------------------------------------------------------------------------------- [FIX] html_builder, *: hide shape option for CORS-protected images *: website Steps to reproduce the problem: - Add an image on the page. - Replace the image by a CORS protected one. - Try to apply a shape on the image. -> Traceback The goal of this commit is to hide the "Shape" option if the "original image" of an image is not retrievable. Indeed, in this case, the option will fail to apply correctly. task-5405262 --------------------------------------------------------------------------------------------------------------------------------------------- [FIX] html_builder, *: avoid displaying options that are not compatible *: html_editor, website Few options like filter, quality, format and cropping rely on canvas to work. The problem is that it does not work correctly for mimetypes like `svg` or `gif`. Indeed, if an image modification is done on such images, it will automatically be transformed into a `png` by default. To avoid it, this commit hides the options that rely on a canvas manipulation when clicking on a `svg` or `gif` image. The process image function has also been adapted to not try to transform an image if its mimetype is not compatible with a canvas transformation. Instead, those images are directly transformed into `b64` images without any transformation. Thanks to it, a shape can be applied on a `svg` or a `gif`. task-5405262 --------------------------------------------------------------------------------------------------------------------------------------------- [FIX] html_builder, *: apply shape on replaced svg and gif images *: website Steps to reproduce the problem: - Add a "Text-Image" snippet on the page and add a shape on the image. - Replace the image by a svg or a gif. -> A shape is displayed on the image options but the shape is not applied on the image. task-5405262 --------------------------------------------------------------------------------------------------------------------------------------------- [FIX] html_builder, *: avoid copying options on new incompatible images *: html_editor, website Steps to reproduce: - Add an image on the website. - Add a shape on the image. - Replace the image by a CORS-protected one. -> The image still has the shape data attributes on its HTML element while it should not as it does not have the prerequisites to have a shape (it does not have an original source). The same problem exists with the hover effect. This commit moves the logic that transfers the shape and hover effect on replaced images from `html_editor` to the responsible plugins in `html_builder` and `website`. It also adds a check to verify that the replaced image is eligible to have a particular option before transferring its data information. task-5405262
Accrual-based time off allocations now automatically calculate the correct number of days even when assigned by department, company, or employee tag instead of one employee at a time. This helps HR teams avoid missing or incorrect leave balances when creating grouped allocations.
Original PR description
### Steps to reproduce: - Create an accrual plan of one level to give 20 days at the start of the year - Create an allocation with different mode than 'By Employee' - Set the accrual plan for the…
### Steps to reproduce: - Create an accrual plan of one level to give 20 days at the start of the year - Create an allocation with different mode than 'By Employee' - Set the accrual plan for the allocation and date from 1st Jan - Notice the Allocation number of days doesn't get automatically calculated ### Cause: This is happening because when trying to process the accrual plan we won't have any records in the field employee_id https://github.com/odoo/odoo/blob/bcdd12d13d73915e565fd2c8478b936a16efb9f4/addons/hr_holidays/models/hr_leave_allocation.py#L892-L893 And since employee_id is computed field when computing it we don't handle the case of any other mode other than 'By Employee'. https://github.com/odoo/odoo/blob/bcdd12d13d73915e565fd2c8478b936a16efb9f4/addons/hr_holidays/models/hr_leave_allocation.py#L259-L270 ### Fix: If we have different mode in the allocation we fetch the employees in this mode (Department, Company, Employee Tag) and set them as the allocation employee_ids so when computing the employee_id we will have a record in the field and it won't be null P.S. In the forward port we will have to introduce another fix for the multi allocation wizard opw-5888023 Forward-Port-Of: odoo/odoo#258116 Forward-Port-Of: odoo/odoo#247091
Alternative purchase orders now keep the correct vendor price when products use taxes included in the price. This prevents underpriced purchase totals and helps buyers compare supplier offers accurately.
Original PR description
[FIX] purchase: set the correct price in alternative PO Steps to reproduce the bug: - Enable "Purchase Alternatives" in settings - Go to Accounting > Configuration > Taxes: - Configure a 15% purchase…
[FIX] purchase: set the correct price in alternative PO
Steps to reproduce the bug:
- Enable "Purchase Alternatives" in settings
- Go to Accounting > Configuration > Taxes:
- Configure a 15% purchase tax:
- Advanced Options tab:
- Included in Price: enabled
- Create a storable product "P1":
- Tax: 15%
- In the Purchase tab, add vendors:
- "Azure Interior": price = $10, min qty = 1
- "Deco Addict": price = $15, min qty = 1
- Create a purchase order for "Azure Interior":
- Order 100 units → total price is automatically computed as $1000
- Create an alternative purchase order:
- Vendor: "Deco Addict"
- Copy products: enabled
Problem:
The price is $1360, instead of $1500
When the alternative purchase order is created and the product is set
on the purchase order line, the required onchange methods are not
triggered:
https://github.com/odoo/odoo/blob/ad253ef4c2cb06536b99bb919a3e01ed980d2e96/addons/purchase/models/purchase.py#L1169
As a result, both the unit price and the taxes are missing on the
purchase order line. When `_compute_price_unit_and_date_planned_and_name`
is triggered, it attempts to compute the `price_unit`.
https://github.com/odoo/odoo/blob/fb24ad03fc47a303fa8719c0795e1afa9a7eb821/addons/purchase/models/purchase_order_line.py#L345-L346
At this point, it checks whether the purchase order has a vendor.
Since "Deco Addict" is set, it calls `_fix_tax_included_price_company`
using:
- the supplier price ($15)
- the supplier tax (15%)
However, since no taxes are yet set on the purchase order line,
`_fix_tax_included_price_company` incorrectly assumes the price is
tax-included and converts it to a tax-excluded price
(~13.04 instead of 15).
https://github.com/odoo/odoo/blob/7076b4f4d0d933d93b24e8e4c7cf21ef0b0008e5/addons/account/models/account_tax.py#L571-L573
Then, a 15% tax is applied on top of this incorrect base price, leading
to the wrong total.
opw-6047004Odoo now ignores empty vendor references when matching imported Peppol bills to purchase orders. This prevents large unnecessary searches that could exhaust memory and block automated bill import processing.
Original PR description
Description of the issue/feature this PR addresses:
A Peppol message contains a reference with a trailing comma.
```
<cac:OrderReference>
<cbc:ID> Uw bestelling,</cbc:ID>
</cac:OrderReference>
<cac:BillingReference>
<cac:InvoiceDocumentReference>
<cbc:ID> Uw bestelling,</cbc:ID>
</cac:InvoiceDocumentReference>
</cac:BillingReference>
```
Current behavior before PR:
A MemoryError occurs in `_cron_peppol_get_new_documents` because the trailing comma leads to a fetching hundreds of thousands of purchase orders with domain `[('partner_ref', 'in', [('Uw bestelling', '')]`
Desired behavior after PR is merged:
Purchase orders are not matched by empty reference.
opw-6102641
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#257851Changing a branch company's ZATCA API mode now only resets that branch's own journal setup. This prevents the parent company's Saudi e-invoicing onboarding from being unintentionally removed, reducing disruption for businesses with branch structures.
Original PR description
**Steps to reproduce:** * Install `l10n_sa_edi` module. * Create a parent company and a child company, both with the same VAT number. * Onboard the parent company's Sales journal with ZATCA. *…
**Steps to reproduce:**
* Install `l10n_sa_edi` module.
* Create a parent company and a child company, both with the same VAT number.
* Onboard the parent company's Sales journal with ZATCA.
* Onboard the child company's Sales journal with ZATCA. Create a journal for the child if there is no journal.
* Change the child company's ZATCA API mode to any other mode.
**Observed behavior:**
* Changing the child company's API mode resets and unboards the parent company's Sales journal as well.
**Cause:**
* In `res.company.write`, when `l10n_sa_api_mode` changes, journals to reset are fetched using `_check_company_domain(company)`.
* `account.journal` uses `check_company_domain_parent_of`, which returns journals where `company_id` is a parent of the given company — so passing a child company also matches journals belonging to the parent.
**Fix:**
* Replace `_check_company_domain(company)` with a direct `('company_id', '=', company.id)` filter, so only journals strictly owned by the company being modified are reset.
opw-6099340
Forward-Port-Of: odoo/odoo#258006