Tuesday, September 22, 2026
34 changes · saas-19.2
Resolved issues and error corrections
Manufacturing order overviews now calculate costs correctly even when related work orders do not have an employee assigned. This prevents misleading totals in production summaries and gives managers a more reliable view of manufacturing costs.
Original PR description
Description of the issue/feature this PR addresses: Resolves an issue where Manufacturing Order (MO) summary calculations produced incorrect totals when associated Work Orders had no assigned employee. Current behavior before PR: <img width="2419" height="802" alt="mo-wo-employee-fix-before" src="https://github.com/user-attachments/assets/9a0d64b4-2ca3-4732-9ac7-39673e0ae319" /> Short-circuiting the logic when a work order has no associated employee causes an error in the overview's cost calculation. Behavior after PR: <img width="2389" height="641" alt="mo-wo-employee-fix-after" src="https://github.com/user-attachments/assets/f2b67e84-c149-4d05-be30-f834e62e0882" /> By updating it to no longer short-circuit it now correctly calculates the cost of the manufacturing order. opw-6464637 Forward-Port-Of: odoo/enterprise#131181 Forward-Port-Of: odoo/enterprise#128465
This fixes how Peruvian SUNAT exchange rates are saved so they match the official rate expected for each day. It prevents newly fetched rates from being dated in a way that could cause mismatches in tax reporting, though existing incorrect rates are not automatically corrected.
Original PR description
In this commit https://github.com/odoo/odoo/pull/231948 the base functionality of exchange rate fetching was changed in order to have a consistent exchange rate value per day. The problem is that in l10n_pe, SUNAT already does this, setting each day's official exchange rate to be the closing rate of the previous day. Because SUNAT expects the exchange rate being sent to it to be the same as the one generated in the morning, the new logic is defaulting to a mismatched rate instead. This commit changes how new currency rates are stored through SUNAT by dating them one day in the past to align with the new architecture. This will not realign existing currency rates that do not work with the change. This is a mirror of a similar adjustment that was made for Banxico in Mexico here: https://github.com/odoo/enterprise/pull/118999 opw-6468065 Forward-Port-Of: odoo/enterprise#131483
This update fixes an internal test setup issue by ensuring employee time zones are consistent during planning field service tests. It helps prevent false test failures caused by demo user time zone differences, improving release reliability without changing user-facing behavior.
Original PR description
Employees created in TestPlanningFieldServiceCommon had no explicit tz, so they inherited the acting user's timezone. With --with-demo that user's tz is Europe/Brussels, shifting the resource calendar's work hours by 1h in January and making test_planning_slot_allocated_hours_on_multiple_resources compute 7 allocated hours instead of the expected 8. runbot-946587
Delivery slips now show rounded quantities when a receipt or delivery is split across multiple stock move lines. This prevents confusing decimal artifacts from appearing on printed reports, making documents clearer for customers and warehouse teams.
Original PR description
**Issue** When a move has several move lines, the printed quantities are not rounded, causing floating-point arithmetic artifacts to appear on the report. **Steps to reproduce** - Create and confirm…
**Issue** When a move has several move lines, the printed quantities are not rounded, causing floating-point arithmetic artifacts to appear on the report. **Steps to reproduce** - Create and confirm a PO for 15.6 units of a product. - On the receipt, change the quantity to 17.8 (this splits it into two stock move lines). - Validate the receipt and print the delivery slip. -> The ordered quantity on the generated PDF is not rounded (floating-point arithmetic issue). **Cause** While printing the delivery slip, quantities are aggregated by product: https://github.com/odoo/odoo/blob/189df3538aa07d22f683008522b9143421f411a2/addons/stock/report/report_deliveryslip.xml#L174 No rounding is performed while subtracting quantities from the ordered quantity (resulting in 13.399..): https://github.com/odoo/odoo/blob/189df3538aa07d22f683008522b9143421f411a2/addons/stock/models/stock_move_line.py#L927 Nor while adding the second move line's quantity back to the ordered quantity (resulting in 15.599..): https://github.com/odoo/odoo/blob/189df3538aa07d22f683008522b9143421f411a2/addons/stock/models/stock_move_line.py#L938 **Additional note** Same issues arise for `packaging_qty_ordered`, `quantity` and `packaging_quantity`. opw-6469422 Forward-Port-Of: odoo/odoo#283792
Employees without an active contract or version who are on leave counted as working time are no longer automatically checked out by the attendance scheduler. This prevents incorrect attendance records and preserves accurate working time tracking for cases such as homeworking or attendance-type leave.
Original PR description
## Short functional explanation of the error When an employee doesn't have an active version, and they're on a leave considered working time. When we run the scheduled action `Attendance:…
## Short functional explanation of the error When an employee doesn't have an active version, and they're on a leave considered working time. When we run the scheduled action `Attendance: Automatically check-out employees`, the employee is checked out after the defined tolerance time. The employee shouldn't be checked out, as they're considered working at the time. ## Reproduction steps 1. Create an employee without contract. 2. Go to Attendances > Configuration > Settings. Check Automatic Check-out. Select based on Tolerance and set the Tolerance at 2 hours. 3. Go to Time off > Management > Allocations and click on New. As Time Type, select a type considered as Working (like Homeworking or Attendance), set your employee and an Allocation duration. Click Validate. 4. Click on Management tab > Time off. Click on New and use for Time Type and Employee fields the same values you used on the allocation. Set the date as today. 5. Go to Attendances and click on New. Select the employee you're working with. Set the Check in at today, 3 hours before your local time. Remove the check out. 6. Enable the debugger and go to Scheduled Actions. Search for `Attendance: Automatically check-out employees` and click on Run manually. 7. Go back to Attendances. ### Expected Behavior The attendance of the employee is still ongoing as they're on a leave considered as working time. ### Unexpected Behavior The attendance of the employee has ended, and its duration is equal to the tolerance time defined in the settings. ## Origin of the issue This bug doesn't occur when an employee has an active version, as we retrieve the work intervals taking into account the leaves of the employee: https://github.com/odoo/odoo/blob/50298287733ce4ed8c5372495778e69cac5e114d/addons/hr/models/hr_employee.py#L1804 However, we don't take such leaves in consideration when an employee doesn't have a version: https://github.com/odoo/odoo/blob/50298287733ce4ed8c5372495778e69cac5e114d/addons/hr/models/hr_employee.py#L1780-L1788 __ task-6453044 Forward-Port-Of: odoo/odoo#289607 Forward-Port-Of: odoo/odoo#282477
Closing an AI live chat conversation now ends the session without unexpectedly opening a separate chat window. This improves the website visitor experience by avoiding confusing follow-up popups after the conversation is closed.
Original PR description
Closing the chat with the ai agent on the ai_livechat snippet results in a chatwindow popping up which gives a bad user experience. Steps to reproduce: - Log in as Mitchell Admin. - Go to website. -…
Closing the chat with the ai agent on the ai_livechat snippet results in a chatwindow popping up which gives a bad user experience. Steps to reproduce: - Log in as Mitchell Admin. - Go to website. - Click on edit and choose `Contact & Forms`. - Add the AI Livechat website snippet. - From the snippet options, add an AI Agent and choose a livechat team. Make sure that Mitchell Admin is configured as an operator for that livechat team (livechat channel). - Click on save. - Open an incognito tab. Log in as Marc Demo. - Go to Website. - Type a message inside the `ASK AI` text area and press enter. - Wait until you receive a response and then click close. - A chat window will popup with the messages of the conversation with the AI along with a message saying `Visitor has left the channel`. This happens because `close` button will call `closeConversation` => `livechatService.leave()` => `visitor_leave_session` => `_close_livechat_session` that posts a message that the visitor has left the channel. This commit solves the issue by setting the user who left the channel as the author of the `visitor left chat` message instead of OdooBot. Forward-Port-Of: odoo/odoo#258836
This fixes how date and datetime fields appear when administrators set default values in debug mode. Dates now display and save in the expected format, reducing confusion and the risk of incorrect default values.
Original PR description
Problem: When setting default values in debug mode, the date and datetime values are not formatted correctly. This leads to confusion and incorrect values being set for date fields. Steps to reproduce: 1. Enable debug mode 2. Create a journal entry, set the date to some date (August 31st, for example), and save. 3. Click the bug icon, click Set default values. 4. Click on the dropdown, notice how the date is displayed as datetime instead of date. Cause: The displayed values were not being formatted for date and datetime fields when setting default values. They were just output as strings. Also, dates were not serialized correctly before being sent to the server, they were serialized as datetime instead of date, because the code was checking the constructor name of the value instead of the field type. opw-6533491 Forward-Port-Of: odoo/odoo#287813
This fix ensures Point of Sale refunds keep the manually calculated tax amounts from discounted orders instead of recalculating them incorrectly. It prevents session closing failures caused by small rounding differences in refund accounting entries.
Original PR description
Closing a session fails with an unbalanced journal entry when it holds the refund of an order whose global discount falls on a specific rounding. Steps to reproduce: - configure a 20% tax and sell 2…
Closing a session fails with an unbalanced journal entry when it holds the refund of an order whose global discount falls on a specific rounding. Steps to reproduce: - configure a 20% tax and sell 2 x 2.12 in the PoS - apply a 10% global discount, pay the order, then refund it - close the session The tax of the discount line cannot be recomputed from its price: the UI splits the rounded tax included amount, 0.51 here, into 0.42 of base and 0.09 of tax, where 0.42 taxed at 20% would give 0.08. It pins both amounts in 'extra_tax_data' with the quantity they were computed for, and they are used again only if the line still matches that snapshot. Refund orders reverse that quantity, so the discount line the UI builds for them no longer matches and its taxes are computed again. Reverse those amounts only when the quantity they were computed for has the opposite sign to the one now used. Refunds made with "Return Products" copy the line with its quantity already negated, so reversing them as well would break those instead. opw-6533657 Forward-Port-Of: odoo/odoo#288573
The point of sale product search now includes products whose variants have a single attribute value, such as Size M. This helps cashiers find the right products more reliably and avoids missing items during checkout.
Original PR description
In the POS search bar, searching for an attribute value (e.g., "M") returned products with multi-value attributes (e.g., Size: M - L), but omitted products with single-value attributes (e.g., Size: M). This occurred because backend `_compute_display_name` filters out single-value attribute lines from variant display names. As POS `searchString` relied on `display_name`, `name`, `default_code`, and `barcode`, the attribute value name was missing from single-value variant search strings. task-id: 6431718 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279428
This fixes a website setup failure that could block users when creating a new website with eCommerce and design themes installed. Odoo now checks all existing website themes when adding required snippets, so the configurator can complete without missing-template errors or misleading operation popups.
Original PR description
# How to reproduce - Start a new db with the design-themes addons - Install the eCommerce module - Skip the first website configurator - Go to Website > Configuration > Settings > New website - Go…
# How to reproduce - Start a new db with the design-themes addons - Install the eCommerce module - Skip the first website configurator - Go to Website > Configuration > Settings > New website - Go through all the steps as usual # The issue At the last step, you will get, depending on the version either : - A traceback, pointing out the fact that a template is missing - A popup signaling that another module operation is being processed, even though there are none The configurator won't go through in both cases # Cause When calling `configurator_apply`, we will try to get the content of every snippet specific to theme installed with the website : https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/website.py#L910 But, when trying to find the 'website_sale.configurator_homepage_s_dynamic_snippet_category_list' view, we find nothing and an error is thrown. This view should be created by the `_generate_primary_snippet_templates` function, which is called when loading any theme module or `website` or `website_sale`: https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/ir_module_module.py#L594 The issue is that, to know which snippets to install, that function relies on `get_current_website()`: https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/ir_module_module.py#L694 Which relies on multiple things to find the current website, notably the current request's session : https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/website.py#L1374-L1381 Since [this commit], the request is not kept when creating a new registry, which is done when loading a module. `get_current_website()` fallbacks on the first website's theme, which is the default theme (since we skipped the first configurator). That theme does not have the `website_sale` snippet in its manifest, contrary to others. # Proposed solution Since a new registry is created, we cannot use the context, nor the current request. We also cannot add a parameter to the `_generate_primary_snippet_templates` function since we are in stable (It would not help much anyway because this function is called when loading a module, so giving the current website is not possible). This means that we have to stop relying on `get_current_website()` to differentiate between the two use case : - Installing a theme module - Installing a module with specific snippets We can determine if the module being installed is a theme by looking at its category. If it is not, we assume its the other use case as there should not be any other. In that case, instead of installing the snippets of the theme of the current website, we check the theme of all websites. Later in the code, we take only distincts snippets and check that a corresponding view do not already exist, so there should be no risk of duplicate : https://github.com/odoo/odoo/blob/1f2ea7be8377c906ded75da893d739109db93c4d/addons/website/models/ir_module_module.py#L692 https://github.com/odoo/odoo/blob/1f2ea7be8377c906ded75da893d739109db93c4d/addons/website/models/ir_module_module.py#L712 https://github.com/odoo/odoo/blob/1f2ea7be8377c906ded75da893d739109db93c4d/addons/website/models/ir_module_module.py#L608 Furthermore, I believe it makes more sense to check every website anyway. If the `website_sale` module is installed, and there already exists multiple website with different themes, these themes might require different `website_sale` specific snippets. opw-6517556 [this commit]: https://github.com/odoo/odoo/commit/be8c85b4ccb10d6f16ddea3ab493e4c9aac749e1 Forward-Port-Of: odoo/odoo#285935
Employees with schedules that define only a daily number of hours can now request hourly time off later in the day without it being incorrectly counted as zero hours. This prevents valid time off requests from being rejected when no specific working start or end time is imposed.
Original PR description
Issue: A time off in hours requested after 16:00 cannot be taken by an employee whose working schedule only defines a number of hours per day, without any start and end time. The request is computed…
Issue: A time off in hours requested after 16:00 cannot be taken by an employee whose working schedule only defines a number of hours per day, without any start and end time. The request is computed with a 0:00 hours duration and its approval is rejected with "The following employees are not supposed to work during that period", even though the employee has no imposed working hours that day. Steps to reproduce: * Give an employee a working schedule with no start/end time (not flexible) * Allocate them a time off type expressed in hours * Request that time off from 16:00 to 20:00 and approve it Cause: A duration based attendance states how many hours are worked on a day, never when they are worked, so the intervals built in `_attendance_intervals_batch` place them in an arbitrary block centered on midday, 08:00 -> 16:00 for a full day. A period requested outside of that block intersects no attendance at all, hence a duration of zero and a period considered as non working. https://github.com/odoo/odoo/blob/6b037d0ef4e10120ce76cd21f43e5378a26dd55a/addons/resource/models/resource_calendar.py#L296-L327 Solution: Since the placement of those hours is arbitrary, it should only be kept when it is compatible with the period being computed. When the attendances of a day do not overlap the requested period, slide them, without letting them leave their day, towards that period. Only the first and the last day of a period can be concerned, as the days in between always overlap their centered block, so the working time reported for full days, as well as schedules with explicit start and end times, keep their current behaviour. opw-6446477 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Spanish Mod 349 reporting now keeps refund amounts when those refunds have simply been paid or matched with bank transactions. Amounts are only removed when an invoice or bill is directly matched against a refund, improving report accuracy for affected businesses.
Original PR description
This completes the commit https://github.com/odoo/enterprise/commit/c0d581965bc6dfe626746f9089ef6190acac1375 but for refund. Currently, the refunds were removed from mod349 report if they were reconciled with a payment, bank transaction or any "entry" type move. Now, we only remove the amounts if an invoice/bill was directly reconciled with a refund. opw-6297666
Time Off now displays selected start and end times correctly for languages that use different abbreviations for hours or minutes, such as Dutch. This prevents confusing 12:00 AM displays while keeping the underlying leave request values accurate and visible to users.
Original PR description
## Issue When using a language that does not use `h` for "hours" and/or `m` for "minutes", times are not properly displayed in the Time Off app. For example, changing the `request_hour_to` of a time…
## Issue
When using a language that does not use `h` for "hours" and/or `m` for "minutes", times are not properly displayed in the Time Off app. For example, changing the `request_hour_to` of a time off request in Dutch will always show 12:00 AM, regardless of the time we set.
## Steps to reproduce
1. Install *Time Off* (`hr_holidays`)
2. Change the user language to Dutch
3. In Time Off, create a new time off request
- Time Off Type: Any type with a "Custom Hours" duration type
4. **The times displayed are "12:00 a.m" for both the start and the end of the time off. We can change the times but they will always display 12:00 a.m, even if the proper values are used in the backend.**
## Cause
The displayed time is formatted in the FloatTimeSelectionField component:
https://github.com/odoo/odoo/blob/dcaba2dd99d0553bb3df7b9b5f0b77fec2d53159/addons/hr_holidays/static/src/components/float_time_selection/float_time_selection.js#L45-L62
The getter attempts to parse the provided `unitAmount` (e.g. "13h 14m") by looking for `h` and `m` to determine the hours and minutes. This works fine in multiple languages such as French or English, but it does not work for languages that use other letters for hours and/or minutes (such as Dutch, which uses "u" for hours).
This issue was introduced by https://github.com/odoo/odoo/commit/e80750d20b7ddbd015451a885016151347f3975b, which was correcting an issue where *"Invalid DateTime"* being displayed instead of actual time values.
## Fix
The solution is to use the correct hour and minute identifiers depending on the user language. This is done by the `durationUnitsRegex` function:
https://github.com/odoo/odoo/blob/dcaba2dd99d0553bb3df7b9b5f0b77fec2d53159/addons/web/static/src/core/l10n/time.js#L287-L307
which is then used by the [`parseDuration`](https://github.com/odoo/odoo/blob/dcaba2dd99d0553bb3df7b9b5f0b77fec2d53159/addons/web/static/src/views/fields/parsers.js#L144) to parse a string such as "13u 14m" or "13h 14m".
<img width="394" height="164" alt="6373786" src="https://github.com/user-attachments/assets/d64647f0-4bd7-4f06-bd32-fd3f258fdb63" />
opw-6373786Duplicating a tax now preserves its correct domestic tax status instead of incorrectly marking it as non-domestic. This helps prevent copied tax records from carrying wrong fiscal settings that could affect accounting configuration.
Original PR description
_compute_is_domestic checks whether the company's domestic_fiscal_position_id is part of the tax's fiscal_position_ids. Because this field is both stored and precompute=True, duplicating a tax recomputes it on a virtual new() record before insert. On that virtual record, fiscal_position_ids is resolved through NewId pseudo-ids while company_id (a plain Many2one) keeps its real id, so the containment check always failed, silently turning is_domestic into False on any duplicate. Use fiscal_position_ids._origin to compare against the real underlying records instead. opw-6575113 Forward-Port-Of: odoo/odoo#289246
Opening the rental schedule from a product that no longer has variants now works without triggering an error. This prevents interruptions for rental teams when managing products with dynamic variants that were later removed.
Original PR description
Currently, an error occurs when opening the Rental Order Lines Schedule view. **Steps to Reproduce:** - Install the `sale_stock_renting` module. - Go to `Settings` and enable `Variants`. - Go to…
Currently, an error occurs when opening the Rental Order Lines Schedule view. **Steps to Reproduce:** - Install the `sale_stock_renting` module. - Go to `Settings` and enable `Variants`. - Go to `Rental` > `Products` and create a product. - In the `Attributes & Variants` tab, add an attribute with two values and save. - Delete all variants using the `Variants` smart button or from `Inventory` > `Products` > `Product Variants`. - Return to the `Product` and click the `In Renting smart button`. `IndexError: list index out of range` When a product template is created, a product variant is automatically generated if the product has no attributes. Deleting this variant also deletes the product template [1]. By explicitly creating attribute values, a dynamic product variant is generated. Deleting this variant does not delete the product template because of the dynamic attribute [2]. When the In Renting button is clicked, it it going to open the rental order lines Schedule view and adds default_product_id to the action context. Since no product variants exist anymore, accessing the default product variant raises an error [3]. This commit ensures that default_product_id is not added to the context when the product template has no product variants [1]: https://github.com/odoo/odoo/blob/1f70b81eeebcc62b64c18772915e2f4696e189e7/addons/product/models/product_product.py#L486 [2]: https://github.com/odoo/odoo/blob/1f70b81eeebcc62b64c18772915e2f4696e189e7/addons/product/models/product_product.py#L478-L480 [3]- https://github.com/odoo/enterprise/blob/98ec35f3f17a2b706ad4fcc4d6161d12d05eadfd/sale_renting/models/product_product.py#L69 [4]: https://github.com/odoo/odoo/blob/942d3892c33e7ccdcfec260c3da37095069b699e/addons/stock/models/product.py#L635-L643 [5]: https://github.com/odoo/odoo/blob/942d3892c33e7ccdcfec260c3da37095069b699e/addons/stock/models/product.py#L601-L612 sentry-7637715724 Forward-Port-Of: odoo/enterprise#132028 Forward-Port-Of: odoo/enterprise#126356
When a project's company is updated after creation, its related document workspace now updates to the same company when all linked projects share it. This keeps project documents aligned with the correct company and avoids workspaces unintentionally remaining company-neutral.
Original PR description
**Steps to Reproduce** - Install the `Documents` and `Project` modules. - Create two companies. - Create a project using the `Kanban quick create` option, without setting a company. - A related…
**Steps to Reproduce** - Install the `Documents` and `Project` modules. - Create two companies. - Create a project using the `Kanban quick create` option, without setting a company. - A related workspace is automatically created without a company. - Set a company on the project from the form view. - The company of the related workspace remains unset. **Observation** - Projects created through the Kanban quick-create flow do not have a company assigned at creation time, so their related workspace is also created without a company. - When the project's company is subsequently changed from the form view, the workspace company was not updated. As a result, the workspace and its documents remained without a company. **Solution** When a project's company is changed, all projects linked to the same workspace are checked: * If all projects belong to the same company, the workspace company is updated accordingly. * If the linked projects belong to different companies and the workspace has no company, the workspace remain unchanged and accessible to all companies. * If the linked projects belong to different companies and the workspace already has a company, an error is raised, preserving the existing behavior. Backport of https://github.com/odoo/enterprise/pull/111760 OPW-6487090 Forward-Port-Of: odoo/enterprise#129315
Users can now customize the Inventory Overview by removing dashboard graph cards without causing the page to crash. This keeps Studio customizations stable and prevents an error when graph data is no longer present.
Original PR description
Steps to reproduce:
- Go to Inventory > Overview
- Enable Studio, delete a kanban card that contains the "picking_type_dashboard_graph" widget (field kanban_dashboard_graph)
> UncaughtPromiseError > OwlError
> TypeError: Cannot read properties of undefined (reading 'includes')
at StockKanbanRenderer.getGroupsOrRecords
Cause of the issue:
`StockKanbanRenderer.getGroupsOrRecords` assumes the field `kanban_dashboard_graph` is always present on every record to detect if all Inventory Overview graphs are sample data and, if so, replace them with randomized values.
By removing the card with studio, we remove the field from the fields fetched for the record, and so `r.data.kanban_dashboard_graph` is `undefined`.
Fix by ignoring records for which the field isn't fetched instead of assuming it's always there.
opw-6512743
Forward-Port-Of: odoo/odoo#285593Email template editors now properly block embedded videos, such as YouTube clips, before they can be added. This avoids saved templates losing unsupported video content and potentially leaving broken or empty sections in outgoing emails.
Original PR description
Steps to reproduce: 1. Go to Settings > Technical > Email Templates. 2. Open any email template. 3. In the Body editor, either paste a YouTube URL and select Embed YouTube Video from the popup, or…
Steps to reproduce: 1. Go to Settings > Technical > Email Templates. 2. Open any email template. 3. In the Body editor, either paste a YouTube URL and select Embed YouTube Video from the popup, or type `/media` and open the 'Videos' tab in the Media dialog. 4. Select a video or embed a YouTube video. 5. Save the email template. Issue: - The YT URL is converted into an `<iframe>` video element in the email template. However, the `<iframe>` element is removed when the template is saved because it is not supported by the email HTML sanitization. - This leaves the surrounding content empty and can result in broken HTML in the email template. The `body_html` field was configured with `allowCommandVideo: false` to prevent video embedding, but this option is not recognized by the HTML field and is therefore ignored. Solution - Use the supported `allowVideo: false` option on the `body_html` field of email templates to disable video embedding in the editor as [this PR](https://github.com/odoo/odoo/pull/219288) has changed the html_field components's attribute. Expected behavior - Video embedding should be disabled in email templates, preventing users from inserting YouTube videos and avoiding `<iframe>` elements that are later removed by HTML sanitization. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286133
Opening an order from the Sales report is now faster on databases with many sales lines. The system now identifies the related order directly, avoiding a slow report lookup that could delay users.
Original PR description
Versions -------- - 18.0+ Steps ----- 1. On a database with many sale order lines, open Sales > Reporting > Sales and switch to the list view. 2. Click a line to open its order. Issue ----- Opening the order takes seconds. Cause ----- `action_open_order` reads `order_reference` off the `sale.report` view. The view's `id` is `MIN(sale_order_line.id)`, so reading one record filters on an aggregate. PostgreSQL therefore has to aggregate every sale order line and POS order line before keeping one row. Solution -------- The report id is itself a line primary key, so the order can be resolved without reading the view. Add a `_get_order_reference` hook returning the order from the `sale.order.line` record directly. Forward-Port-Of: odoo/odoo#289284 Forward-Port-Of: odoo/odoo#289143
This fix ensures Factur-X electronic invoices include the required customer name even when an invoicing address has no name of its own. It helps prevent compliance validation failures and reduces the risk of rejected electronic invoices.
Original PR description
Description of the issue/feature this PR addresses: The Factur-X Refactoring (https://github.com/odoo/odoo/pull/284123) introduced an error that can result in the generated XMLs not being compliant…
Description of the issue/feature this PR addresses: The Factur-X Refactoring (https://github.com/odoo/odoo/pull/284123) introduced an error that can result in the generated XMLs not being compliant due to the partner name being missing (BT-44) Current behavior before PR: When a contact has an invoicing address without a name set, then the new Factur-X generation does not fall back to the parent name, leaving the corresponding XML entry empty, which is therefore pruned, leaving the Factur-X without a BT-44 and thus failing BR-07 Desired behavior after PR is merged: The new generation method uses the same data source and fallback method as the old Factur-X generator, pulling the `display_name` of the `commercial_partner_id` if the address doesn't have a `name`. This greatly reduces the chance of a generated Factur-X failing BR-07. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#289661 Forward-Port-Of: odoo/odoo#289498
This fixes an issue where accordion sections added to default invoice terms and conditions could break after saving. Businesses can now edit richer terms pages without losing interactive content or encountering errors.
Original PR description
# How to reproduce - Go to Settings > Default Terms & Conditions > Preview - Open the editor - Add a Content Block - Add an Accordion in that Block - Save # The issue The accordion gets messed up and…
# How to reproduce - Go to Settings > Default Terms & Conditions > Preview - Open the editor - Add a Content Block - Add an Accordion in that Block - Save # The issue The accordion gets messed up and trying to add a new item to that accordion raises a traceback # Cause `invoice_terms_html` is an html field with some sanitization enabled : https://github.com/odoo/odoo/blob/c11ca6d10cac99b367078823dafef46808346267/addons/account/models/company.py#L172 This sanitization will remove the accordion snippet's buttons that controls the functionality of the snippet : https://github.com/odoo/odoo/blob/c11ca6d10cac99b367078823dafef46808346267/addons/website/views/snippets/s_accordion.xml#L9 This issue was already addressed by : https://github.com/odoo/odoo/commit/b6b4db5fb5690436a4284f6a22abf9f3b346a324 But it is not enough in the case of the accordion. The buttons will be removed by the lxml clean.Cleaner : https://github.com/odoo/odoo/blob/c11ca6d10cac99b367078823dafef46808346267/odoo/tools/mail.py#L364 # Proposed Solution Disable sanitization entirely, like for blog's content, which can also be edited in the website editor : https://github.com/odoo/odoo/blob/c11ca6d10cac99b367078823dafef46808346267/addons/website_blog/models/website_blog.py#L29 opw-6500378 Forward-Port-Of: odoo/odoo#285255
Credit notes using Spain’s equivalence surcharge are now sent with the surcharge rate as a positive percentage, as required by the tax agency. This prevents TicketBAI/Batuz submissions from being rejected when businesses issue these credit notes.
Original PR description
In credit notes, `TipoRecargoEquivalencia` was multiplied by the reversal sign, producing negative values (e.g. -1.40) that violate the Batuz `Tipo3.2Type` pattern and get rejected by the tax agency (`B4_2000001: cvc-pattern-valid`). Unlike `BaseImponible`/`CuotaImpuesto` (amounts that must be negative), `TipoRecargoEquivalencia` is a percentage and must stay positive, as `TipoImpositivo` does. Steps to reproduce: 1. Configure a Spanish company with TicketBAI (Bizkaia). 2. Create a credit note (out_refund) with an equivalence surcharge tax. 3. Send it to TicketBAI; the send fails with a schema validation error. Task: MT-15939 OPW: https://www.odoo.com/es_ES/my/tasks/6573801 @jco-odoo could you review? It's essential to be able to send to Tbai/Batuz with equivalence surcharge tax. Forward-Port-Of: odoo/odoo#289618
This fix updates how employee work contact information is calculated so the system uses the right access rights. It helps prevent unexpected behavior when HR records are viewed or updated by different users.
Original PR description
Ensure correct access to avoid unexpected behavior opw-6560194 Forward-Port-Of: odoo/odoo#288661 Forward-Port-Of: odoo/odoo#288063
Attendance officers without full Employees app access can now open the Employees menu from the Attendance overview without hitting an access error. The menu now uses an attendance-specific employee view, so managers can review attendance-related employee information without needing broader HR permissions.
Original PR description
A user configured with "Officer: Manage all attendances" but no access to the Employees app can see the "Employees" entry under Attendances > Overview, since that menu only requires Attendance…
A user configured with "Officer: Manage all attendances" but no access to the Employees app can see the "Employees" entry under Attendances > Overview, since that menu only requires Attendance rights. Opening it, however, raises an access error naming the "Current Time Off Type" field. Steps to reproduce: ------------------- * Go to Settings > Users, open a user and set their access rights to: Employees app: no access, Attendances app: "Officer: Manage all attendances" * Log in as that user * Go to Attendances > Overview > Employees > Observation: Error: "You do not have enough rights to access the field "current_leave_id" on Employee (hr.employee). Please contact your system administrator." Why the fix: ------------ The "Employees" menu pointed to `hr.open_view_employee_list_my`, the full HR Employees action (model `hr.employee`, no view_id override), which resolves to the same default kanban view used by the Employees app. That view is extended by hr_holidays to embed `current_leave_id` (and other Time Off fields), declared with `groups="hr.group_hr_user"` on the model. That group restriction is enforced by the ORM on read, independently of whether the field is actually shown in the view, so any attendance-only officer opening the menu had that read rejected outright. The menu now points to `hr_employee_attendance_action_kanban`, an attendance-scoped action already shipped in the module for this exact purpose: it uses the `hr.employee.public` model with a minimal kanban view (name, avatar, job, work location, check-in state), none of which require HR app access, restoring the ability to browse employees from the Attendance app without needing `hr.group_hr_user`. opw-6488582 Forward-Port-Of: odoo/odoo#287140
Fiscal positions are now sorted consistently when multiple records share the same priority. This prevents unpredictable tax or accounting mapping choices after data imports, improving reliability without changing normal workflows.
Original PR description
The algorithm to find the right fiscal position works on top of deterministically ordered fiscal position records. This invariant is not enforced by the functional code. Importing data without explicit sequence number results in multiple records ending up with the same value in this column. Sorting by column 'sequence' is not good enough to have repetitive results in this case. The fiscal position algorithm can return a different record due to database implementation details. Adding field "id" to the default ordering ensures the order of fiscal position records remains deterministic. This is because insertion order is preserved and locked into order-preserving id-values when importing data. runbot-945738 Forward-Port-Of: odoo/odoo#289546 Forward-Port-Of: odoo/odoo#289242
The Guatemala electronic invoicing setup now applies fiscal positions in a consistent order. This prevents occasional invoices from using the wrong tax configuration and reduces random test or processing failures.
Original PR description
Test TestGtFlow.test_gt_edi_basic_invoice nondeterministically fails, but the reported error has always the same values. Turns out the code is picking the wrong fiscal position to apply taxes on the prices of the invoice. `res.partner._get_fiscal_position()` searches auto_apply fiscal positions without explicit order, so it relies on the model's default order-by sequence. `account.fiscal.position-gt.csv` carries two records into the database without sequence number, so the order they are returned in is nondeterministic. The resultset is afterwards stable sorted (no tie-breaker between equal sequence numbers) and filtered, so the wrong/unexpected tax can be applied randomly. Explicit sequence numbers are added to account.fiscal.position-gt.csv so the fiscal positions are always returned in-order (domestic first). This approach matches the convention of the other fiscal-position CSVs. runbot-945738 Forward-Port-Of: odoo/enterprise#132377 Forward-Port-Of: odoo/enterprise#131587
Corrects minor English wording issues in the Peppol activation flow. This helps the activation wizard appear more polished and trustworthy for English-speaking users.
Original PR description
There were some minor English mistakes on the Peppol activation wizard that might make Odoo look cheap and untrustworthy to English-speaking audiences. This commit fixes these english mistakes. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285462
The attendance kiosk now displays the weekday in the same language as the rest of the interface. This avoids confusing mixed-language headers for companies using Odoo in languages other than English.
Original PR description
Problem:
The weekday displayed in the attendance kiosk header is always shown in English, even when the comapany contact's language is changed.
This happens because `DateTime.toFormat("cccc")` uses Luxon's locale, but the `DateTime` instance is created without setting the current UI locale.
Steps to reproduce:
1. Install `hr_attendance`.
2. Change the company contact language to a language other than English (e.g. French).
3. Open the Kiosk Mode.
4. Observe that the date and UI are translated, but the weekday remains in English (e.g. "Monday" instead of "Lundi").
Fix:
Set the Luxon locale from the document language before formatting the weekday. This ensures `toFormat("cccc")` returns the weekday in the active Company contact language.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#289173
Forward-Port-Of: odoo/odoo#274655Invoices now show the correct amount due immediately as lines are added, rather than only after saving. When applying outstanding credit, the system also saves the invoice first so newly added lines are not lost.
Original PR description
Before this commit, the account.move amount due field would only update on record saving (as it depended on line_ids). This caused a temporary out of sync situation between the total and the amount due on the move form view. This commit ensures that the amount due is computed even before saving the record so that out-of-sync situation is avoided. It also solves the following bug: Repro steps: 1) Create a new invoice 2) Add a customer and save the record 3) Add some invoice lines 4) without saving, add a payment from the outstanding credit Issue: The added (unsaved) invoice lines are removed Solution: This commit forces a record save before assigning outstanding credit task-6534794 Forward-Port-Of: odoo/odoo#288894
Fixed an issue that could block users from completing a manufacturing order after generating a lot number and then increasing the production quantity. This helps manufacturers complete valid production adjustments without unnecessary lot/serial number errors.
Original PR description
Currently, when the user tries to produce a Manufacturing Order after generating a lot and increasing the quantity, they get a user error stating that a lot must be assigned. ## Steps to produce: -…
Currently, when the user tries to produce a Manufacturing Order after generating a lot and increasing the quantity, they get a user error stating that a lot must be assigned. ## Steps to produce: - Install Manufacturing - Go to the setting enable Lots & Serial Numbers and Storage Locations - Create a product 'Soda can' which is tracked by lots - Create a bill of material for Soda can with component as 'Metal sheet' - Create a storage location called 'Fridge' with parent location WH/Stock - Create a Putaway rule for the Soda can to be stored in the fridge when it arrives in WH/Stock. - Create and confirm a Manufacturing Order for soda can - Click 'Generate Lot' - Increase the total quantity to be produced from 1 -> 2 - Click 'Produce All' ## Observed Behaviour: ``` Invalid Operation: You need to supply a Lot/Serial Number for product: - Soda can ``` ## Root cause: When the user confirms the Manufacturing Order, `_apply_putaway_strategy` is called through: ``action_confirm -> _action_assign -> _apply_putaway_strategy`` This happens for both finished-product move lines and component-product move lines. It updates the finished product move lines' destination location to `WH/Stock/Fridge` at: https://github.com/odoo/odoo/blob/fd06c4df5889e23cfb701c12d743b5652141a111/addons/stock/models/stock_move_line.py#L288-L292 However, the `move_finished_ids` destination location remains` WH/Stock`. After the user generates a lot and increases the production quantity using the wizard, `change_prod_qty()` updates the finished move's demand and re-reserves it through `_update_finished_moves()`: https://github.com/odoo/odoo/blob/f586c1dad0b9a7653c45d1f82a9c3989a287c641/addons/mrp/wizard/change_production_qty.py#L77 This calls `_action_assign` on the finished move: https://github.com/odoo/odoo/blob/f586c1dad0b9a7653c45d1f82a9c3989a287c641/addons/mrp/wizard/change_production_qty.py#L49 Within `_action_assign` because finished moves originate from the production location, they bypass the normal reservation logic at: https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/stock/models/stock_move.py#L2086 So `_action_assign() `then tries to reuse an existing move line. However, it only searches for move lines whose `location_dest_id` matches the move's destination location (WH/Stock): https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/stock/models/stock_move.py#L2108-L2117 The existing move line has `WH/Stock/Fridge` as its destination, so it does not match. As a result, a new move line is created for the finished move: https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/stock/models/stock_move.py#L2182 The new move line gets the lot value from the Manufacturing Order: https://github.com/odoo/odoo/blob/f586c1dad0b9a7653c45d1f82a9c3989a287c641/addons/mrp/models/stock_move.py#L557 Later, when Produce All is triggered,` button_mark_done()` calls` _post_inventory()`: https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/mrp/models/mrp_production.py#L2236 Because the finished move already has a `lot_id`, the condition below is not satisfied: https://github.com/odoo/odoo/blob/708e5ebcd8b3338d3ce2c092aafadfe0f941aa56/addons/mrp/models/mrp_production.py#L1929-L1933 Therefore, the `lot_id` is not assigned to the move line that was created earlier. When `button_mark_done()` validates the finished move lines, it finds that one move line has no lot assigned and raises an 'Invalid Operation' error. ## Solution: Allow the user to produce the Manufacturing Order after changing the quantity. Increasing the quantity is a valid operation, so the user should not be blocked from completing the production afterward. opw-6563498 Forward-Port-Of: odoo/odoo#289171
The test expectations for French e-invoicing now adapt when the French PDP module is not installed. This prevents false test failures and helps keep validation reliable across different installation setups.
Original PR description
### Issue: Tests in `TestCIIFR` fail when run without `l10n_fr_pdp` installed The expected XML files contain `PMT`, `PMD` and `AAB` notes that are only generated when `l10n_fr_pdp` is installed ### Cause: The note generation for FR e-invoicing lives in `l10n_fr_pdp` When it is not installed, the notes are absent from the generated XML but still present in the expected test files When `l10n_fr_pdp` is not installed, the expected tree is stripped of `PMT`, `PMD` and `AAB` notes before comparison ### Steps to reproduce: - Install `l10n_fr_account` without `l10n_fr_pdp` - Run `TestCIIFR` from `l10n_account_edi_ubl_cii_tests/tests/test_xml_cii_fr.py` Before the fix, the affected tests fail on the `cbc:Note` comparison runbot-945999 Forward-Port-Of: odoo/odoo#284661
Users can now click amounts in the aged receivable or payable reports when using an “Open On” date filter without triggering an error. This restores access to the underlying journal item details needed for reviewing outstanding balances.
Original PR description
Currently, an error occurs when a user clicks on a cell in the aged partner balance report. **Steps to Reproduce:** - Install the `account_reports` module. - Go to `Invoicing` > `Reporting` >…
Currently, an error occurs when a user clicks on a cell in the aged partner balance report. **Steps to Reproduce:** - Install the `account_reports` module. - Go to `Invoicing` > `Reporting` > `Partner Reports` > `Aged Receivable`. - Click on the `date filter`, select `Open On`, and set `any date`. - Click on any `amount` in the `Total Aged Receivable line`. `TypeError: the JSON object must be str, bytes or bytearray, not dict` After the [recent commit] that adds the context to the action, when preparing the action to open the journal items corresponding to the selected cell in the aged partner balance report, the context is added to the action [1]. When an "Open On" date is set, the code attempts to convert the context using json.loads() before adding the search_default_open_on key to it [2]. but, the context is already a dictionary, which raises the error [3]. This commit ensures that, since the action context is already a dictionary, the search_default_open_on key and its value are added directly to the context. [recent commit]: https://github.com/odoo/enterprise/commit/9678a1b987a5aa87922b72d972dd7f73a689afee [1]- https://github.com/odoo/enterprise/blob/8fceecea541564a9877babfc31b7e0bf896573a6/account_reports/models/account_aged_partner_balance.py#L357-L359 [2]- https://github.com/odoo/enterprise/blob/8fceecea541564a9877babfc31b7e0bf896573a6/account_reports/models/account_aged_partner_balance.py#L362 [3]- https://github.com/odoo/enterprise/blob/8fceecea541564a9877babfc31b7e0bf896573a6/account_reports/models/account_aged_partner_balance.py#L361 sentry-7685491449 Forward-Port-Of: odoo/enterprise#129142
Opening image attachments linked to products could trigger a client error in the Attachments screen. This fix ensures the attachment selector receives the expected data format, preventing the crash and keeping attachment management reliable.
Original PR description
### Steps to Reproduce: 1. Go to Sales > Products and select a product 2. Upload an image for the product 3. Go to Settings > Technical > Attachments 4. Click on one of the image attachments 5.…
### Steps to Reproduce: 1. Go to Sales > Products and select a product 2. Upload an image for the product 3. Go to Settings > Technical > Attachments 4. Click on one of the image attachments 5. Observe the Client Error: "Uncaught Promise > Invalid props for component 'Many2One': 'domain' is not a function" ### Issue: In `attachment_patch.js`, the domain for the `Many2OneReferenceField` is overridden when an attachment is linked to another attachment. The patch calculates the domain and incorrectly assigns a static Array (using `.toList()`) to `props.domain`. In Odoo 19, the `Many2One` component enforces strict prop validation (caught when Owl's debug mode is enabled during testing), causing a crash because it expects the domain prop to be a function. ### Solution: We can just wrap the resulting domain Array in an arrow function so that it evaluates dynamically when the `Many2One` component mounts. Also added a unit test to explicitly cover this patched scenario. The test manually applies a local patch to `Many2OneReferenceField.prototype` to accurately replicate the environment and verify the fix. opw-6563954 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287951
Peruvian PLE sales and purchase ledgers now exclude ISC tax amounts from taxable base columns when ISC affects later taxes. This prevents overstated ledger values and keeps tax ledgers aligned with electronic invoices sent to SUNAT.
Original PR description
When a tax affects the base of the subsequent ones — `include_base_amount`, which is how the ISC is set up in Peru — Odoo links the tax line it generates to those subsequent taxes, so the ISC tax…
When a tax affects the base of the subsequent ones — `include_base_amount`, which is how the ISC is set up in Peru — Odoo links the tax line it generates to those subsequent taxes, so the ISC tax line ends up carrying the IGV in its `tax_ids`. The PLE ledgers build their base columns by summing every move line related to a given tax group, so that ISC tax line was added to `base_igv` as well and the ISC ended up reported twice: once inside the taxable base and once in its own column. A line of 1000.00 with 100.00 of ISC and 198.00 of IGV was declared with a base of 1100.00 instead of 1000.00. `l10n_pe_edi` already subtracts the ISC from the `cbc:TaxableAmount` of the CPE sent to SUNAT, so the electronic invoice and the ledger disagreed on the very same document. ### What changed The base taxes are now joined only for base lines, instead of repeating the condition on each of the twelve `base_*` columns. Besides keeping tax lines out of the base columns, this also stops a tax line from being counted once per subsequent tax: an ISC followed by both the IGV and the ICBPER produced two rows in the join, and its own column was reported twice. ### Scope `_get_ple_report_data` is shared, so this covers the sales ledger 14.1 and the purchase ledgers 8.1 and 8.2. A configuration where no tax affects the base of a later one is unaffected: the standard Peruvian chart template has no purchase ISC, so ordinary IGV-only purchases report exactly the same values as before. ### Tests - `test_sale_report_isc_base`: an ISC preceding the IGV, on an invoice and on a credit note; the base column excludes the ISC. - `test_sale_report_isc_reported_once`: an ISC followed by the IGV and the ICBPER is reported once. Both fail without the fix. Supersedes odoo/enterprise#128300, which targeted 19.0. Forward-Port-Of: odoo/enterprise#131475