Tuesday, September 22, 2026
30 changes · saas-19.1
Resolved issues and error corrections
Users who receive an @-mention while their Odoo tab is open but not active will no longer get two separate alerts for the same message. The mail notification logic now better matches server behavior, reducing confusion and unnecessary interruptions.
Original PR description
Steps to reproduce: - Grant browser notification permission (subscribes to web push). - Set user A's `notification_type` to `inbox` in preferences and reload. - Keep A's tab open but unfocused. - As user B, post an @-mention on a non-channel chatter record targeting A. A receives two alerts: the JS one from the `mail.message/inbox` bus event and the native notification from web push. `OutOfFocusService.notify()` gated JS skip on `message.thread.model`, but the server never sends `mail.thread`, so chatter fell through. Replace it with a `message_type` + `!isSelfAuthored` filter mirroring the server side `_notify_get_recipients_for_extra_notifications`. Also swap the `isInbox` SW handshake workaround added by [1] when the user is busy. [1]: https://github.com/odoo/odoo/pull/232431 Forward-Port-Of: odoo/odoo#282981
Payments in Mexican electronic invoicing now use the stored official currency rate when the recalculated rate is only slightly different. This keeps invoice and payment XML files consistent and helps avoid avoidable discrepancies during CFDI validation.
Original PR description
Issue: Currency rate for payment is recomputed up to 6 digit. However, it may differ from the official one stored in db up to 4 digits. Which creates differencies between invoice and payment rate…
Issue: Currency rate for payment is recomputed up to 6 digit. However, it may differ from the official one stored in db up to 4 digits. Which creates differencies between invoice and payment rate made at the same date. Steps to reproduce: - In MX company, - Enable USD, - Set currency rate for today to 1 USD = 17.4455 MXN - Create a PPD invoice (due date > 40 days) in USD - Add a line with - qty: 1, - unit_price: 3.488 - tax: 16% (default tax) - Send it to CFDI - Create payment - On the invoice Form click on "Update Payment" Current behavior: - In the CFDI sheet, Payment and Invoice XML files will have different currency rates Expected behavior: - In the CFDI sheet, Payment and Invoice XML files will have the same currency rate Cause: PACs require having the payment `amount` to be equal to `currency_amount * currency_rate`. For huge amout it may happen that using the 6 digits rounding of currency rate to compute the amount won't fall exactly on the two digit precision for the amount and payment would be refused. Therefore, for all payment, we recompute a 6 digits precision `currency_rate` from `amount` and `currency_amount` then using it to compute the final amount. However, Banxico (Mexican central Bank) publish rates with a 4 digit precision. Recomputing the currency rate up to 6 digits may slightly change it from the 4 digit precision official currency rate. opw-6411530 Forward-Port-Of: odoo/enterprise#130963 Forward-Port-Of: odoo/enterprise#129700
Date and datetime fields now show in the proper format when users set default values in debug mode. This prevents confusion and helps avoid saving incorrect default dates.
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 fixes an issue where employees could receive repeated chatter messages when they were automatically enrolled in an eLearning course more than once. It keeps employee records cleaner and reduces unnecessary duplicate notifications or activity history.
Original PR description
Steps to reproduce: - connect with a user with an employee record - recreate a new eLearning course - enable debug mode - add "Role / User" to "Auto Enroll Groups" fields => message is duplicated in the employee's chatter `_action_add_members` in `website_slides` is designed to be idempotent (calling it twice is a no-op if the partner has already joined) but the override in `hr_skills_slides` is not. We have many many duplicated messages on odoo.com (see task) task-6508615 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284483
Manufacturing order overviews now calculate costs correctly even when related work orders have no employee assigned. This prevents misleading production cost totals and gives managers a more reliable summary of manufacturing performance.
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
The point of sale product search now includes products that have a single attribute value, such as Size M, when staff search for that value. This prevents valid product variants from being missed during checkout and makes product lookup more reliable.
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 an issue where creating a new website with eCommerce and selected design themes could fail at the final setup step. Odoo now checks all existing website themes for the needed building blocks, so the configurator can complete reliably without missing-template errors or misleading module-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
This fix ensures sales order costs are calculated using the actual outgoing delivery move, rather than mixing in manufacturing production moves. Businesses using made-to-order manufactured products get more accurate margins and profitability reporting.
Original PR description
**Issue** Cost of SO might be wrong with MTO+Manufacture product **Steps to reproduce** - Activate margin in settings - Create 2 products: - product A: MTO + Manufacture, 1 qty onHand, standard price…
**Issue** Cost of SO might be wrong with MTO+Manufacture product **Steps to reproduce** - Activate margin in settings - Create 2 products: - product A: MTO + Manufacture, 1 qty onHand, standard price to 10 with avco valuation - product B with a standard price of 5 - Create a BOM with the product B as comp - create and confirm a SO for 1 unit of product A - Go to the associated MO and produce all - confirm the delivery linked to the SO -> The cost on the SO is 6.25, while the standard price remains 7.5 **Cause** While confirming the MO, it linked the producing move (which has a unit value of 5), to the SO: https://github.com/odoo/odoo/blob/a6f22922a97399265c04855b5d9b18ac7ac609a0/addons/sale_mrp/models/mrp_production.py#L41-L48 While confirming the delivery, it triggers `_compute_purchase_price` since the picking state changes: https://github.com/odoo/odoo/blob/a6f22922a97399265c04855b5d9b18ac7ac609a0/addons/sale_stock_margin/models/sale_order_line.py#L10-L11 which will eventually takes all the moves links to the sale order to compute the price: https://github.com/odoo/odoo/blob/a6f22922a97399265c04855b5d9b18ac7ac609a0/addons/sale_stock_margin/models/sale_order_line.py#L21 https://github.com/odoo/odoo/blob/a6f22922a97399265c04855b5d9b18ac7ac609a0/addons/stock_account/models/stock_move.py#L701-L714 Which gives an averaging between 5 and 7.5 -> 6.25. Indeed, the unit value of the out move is the standard price: https://github.com/odoo/odoo/blob/8387c28423e8208e53939da3ef5a074c072d33a6/addons/stock_account/models/stock_move.py#L353 opw-6418948 Forward-Port-Of: odoo/odoo#280712
Opening an order from the Sales report list is now much faster on databases with many order lines. The system now finds the related order directly instead of running an expensive report lookup, reducing delays for sales and point-of-sale reporting 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
Email template editors now correctly block video embedding options that would not work in outgoing emails. This prevents users from saving templates with YouTube videos that get stripped out later, avoiding empty or broken email content.
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
Fixes a crash that could occur when a Studio user removed an Inventory Overview card containing the dashboard graph. The overview now ignores missing graph data, so users can customize the page without breaking access to inventory information.
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#285593When a project's company is changed, its related document workspace is now updated when all linked projects share the same company. This prevents workspaces and documents created from quick project creation from being left without the correct company assignment.
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
Default terms and conditions pages can now keep interactive accordion content created in the website editor. This prevents broken accordion layouts and errors when users save or add items, making terms page editing more reliable.
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
Duplicating a tax now correctly preserves whether it is marked as domestic for the company. This prevents copied tax records from silently losing the domestic setting, helping keep tax configuration accurate.
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
This fixes Spanish TicketBAI/Batuz credit notes that include an equivalence surcharge tax, preventing them from being rejected by the tax agency. The surcharge percentage now stays positive as required, while refund amounts remain correctly negative.
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 fixes an issue where some Factur-X e-invoices could miss the customer name when an invoice address had no name of its own. Odoo now uses the related company's display name as a fallback, reducing failed compliance checks and helping invoices remain valid for exchange.
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
Point of Sale refunds now preserve manually calculated tax amounts on discounted orders instead of recalculating them incorrectly. This prevents session closing failures caused by unbalanced accounting entries in specific rounding cases.
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
This fix prevents French e-invoicing tests from failing when an optional French PDP component is not installed. It keeps automated validation accurate across different installation setups without changing customer-facing invoicing behavior.
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
This update fixes how employee work contact details are calculated so the system uses the right access level. It helps prevent unexpected behavior when viewing or updating HR employee records.
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 access to the Employees app can now open the Employees menu from Attendances without seeing an access error. The menu now shows an attendance-focused employee view using information they are allowed to access, improving daily attendance management without granting extra 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
This fixes an intermittent issue where Guatemala invoices could sometimes use the wrong tax setup because fiscal positions were not consistently ordered. The update sets a clear order so domestic tax rules are applied first, improving reliability without changing normal workflows.
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
This fix restores the intended filtering for the HR responsible person field. It helps ensure users only select appropriate responsible people in HR records, reducing incorrect assignments.
Original PR description
Issue: ---------------------------------------- The field `hr_responsible_id` did not have a domain. Cause: ---------------------------------------- The domain of the field is returned by `_get_hr_responsible_domain()` as a string which is not supported. https://github.com/odoo/odoo/blob/2ea452d03aa0cbfe360c539fb3b29a7b89aa7297/odoo/orm/fields_relational.py#L112-L123 `validated()` returns `None` so the domain is left empty. Solution: ---------------------------------------- Return a list instead of a string. opw-6545508 Forward-Port-Of: odoo/odoo#289153
This fix makes fiscal position selection consistent when imported records have the same priority value. Businesses get more predictable accounting behavior and avoid cases where the system may choose different tax mappings depending on database ordering.
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
This update corrects minor English wording issues in the Peppol activation flow. It helps make the setup experience look 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 shows the weekday in the company contact's selected language instead of always displaying it in English. This improves consistency for multilingual workplaces and ensures the kiosk header matches the rest of the translated interface.
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#274655Employees without an active contract or version will no longer be automatically checked out when they are on approved leave that counts as working time. This keeps attendance records accurate for cases such as homeworking or attendance-type time off.
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#282477
This fixes an issue where manufacturing users could be blocked from completing production after generating a lot number and then increasing the order quantity. The change ensures valid manufacturing orders with lot-tracked products can still be produced, reducing disruption in warehouse and production workflows.
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 Source PO smart button now appears only on subcontractor resupply receipts, avoiding misleading links on unrelated purchase receipts. This helps purchasing and manufacturing users identify the right subcontracting purchase order without confusion.
Original PR description
After a recent change, the smart button for Source POs uses the reference_ids on a stock.picking. This altered the logic to fetch all orders from any picking that has the same reference. However,…
After a recent change, the smart button for Source POs uses the reference_ids on a stock.picking. This altered the logic to fetch all orders from any picking that has the same reference. However, `subcontracting_source_purchase_count` should specifically relate to the resupply of subcontractor picking to their subcontracted purchase order. **Steps to Reproduce:** 1. Enable multi-step routes and subcontracting 2. Unarchive the MTO route 3. Create 4 products Finished Product, A, B, C, and set MTO on both A and B 4. Put 10 units of C in stock 5. Create a BoM for Finished Product using A and B as components 6. Create a subcontracted BoM for B using C as a component 7. Set the purchase vendor for product A as the vendor 8. Set the purchase vendor for product B as the subcontractor 9. Create and confirm a manufacturing order for Finished Product 10. Confirm the POs The two POs are: 1. Purchase Product A from its vendor 2. Subcontract Product B from its subcontractor and use Product C as a component **Current Functionality:** - Product A's receipt has a **Source PO** smart button pointing to the subcontracting PO for Product B (BUG) - Product B's receipt has a **Source PO** smart button pointing to its own PO (BUG) - Product C's picking has a **Source PO** smart button pointing to its own PO **New functionality:** - Product A's receipt has no **Source PO** smart button - Product B's receipt has no **Source PO** smart button - Product C's receipt has a **Source PO** smart button pointing to its own PO opw-6420168 Forward-Port-Of: odoo/odoo#278908
Peru currency rates fetched from SUNAT are now stored with the correct date so they match SUNAT's official daily rate. This prevents mismatched exchange rates when reporting to SUNAT, although existing incorrect rates are not automatically adjusted.
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
Peruvian PLE sales and purchase ledgers now exclude ISC tax amounts from taxable base columns when ISC affects later taxes such as IGV. This prevents the same tax from being reported twice and keeps ledger figures 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