Friday, April 24, 2026
37 changes · saas-19.1
Enhancements to existing features
This update optimizes how Odoo handles product pricing rules, specifically for businesses with many products and price lists. The previous process was slow and prone to errors when dealing with large datasets, but this change dramatically improves performance and prevents timeouts. It achieves this by intelligently querying data, reducing unnecessary calculations and memory usage.
Original PR description
The _check_pricelist_recursion constraint could cause performance issues when iterating over full recordsets or repeatedly querying large datasets.
Refactor the recursion logic to traverse pricelists as a graph (DFS on pricelist pairs) and replace item-level iteration with a targeted _read_group query to fetch only relevant pricelist-based rules:
- pricelist_id
- base = 'pricelist'
Avoid redundant path evaluations by tracking visited pricelist pairs.
This ensures that only necessary records are fetched and processed, significantly reducing memory usage and avoiding timeout issues on large datasets.
opw-6099182
Forward-Port-Of: odoo/odoo#259310Resolved issues and error corrections
This update fixes a bug where discounts on purchase orders weren't being correctly reflected in the final accounting. The system now accurately displays the discounted amount, ensuring accurate financial reporting for purchase transactions. This resolves issue OPW-5049848.
Original PR description
Steps to reproduce: [purchase] - Create a purchase order - add a line with a discount - confirm and receive - create an accrued expense entry Issue: The full tax excl amount is displayed but no discount is applied opw-5049848 Forward-Port-Of: odoo/odoo#231706 Forward-Port-Of: odoo/odoo#225375
This update resolves an issue where copying a user also duplicated their associated tasks, leading to shared task assignments. The fix ensures that new users have independent task assignments, preventing conflicts and simplifying task management. This improves data consistency and reduces potential errors.
Original PR description
Duplicating a user also duplicates all their task assignments because task_ids on res.users is missing copy=False. The new user ends up sharing the same tasks in project_task_user_rel, so removing a task from either user affects both. Forward-Port-Of: odoo/enterprise#114028
This update resolves an issue where links added through the website's media replacement feature (replacing images, videos, etc.) weren't correctly translated. The fix ensures that all media files, regardless of how they're added, are properly tagged for translation, improving the website's localization capabilities.
Original PR description
[FIX] website: translate links inline on media replacement On the website builder, files added through the `/file` command would go through the domPlugin `insert` method, which would call…
[FIX] website: translate links inline on media replacement
On the website builder, files added through the `/file` command would go
through the domPlugin `insert` method, which would call
`before_insert_processors` and apply `.o_translate_inline` as expected.
But there is another flow to add a document on the page: replace an
image (or video, or icon), then select the "Documents" tab and upload
a file. With this flow, `insert` is not called, so we have to add the
class through some other resource.
[FIX] html_editor: target documents with right class
The class `.o_image` was still associated with documents (in the context
of the file selector) and the expected tag of a document was `A`, in
spite of it not being true in `html_editor` since the introduction of
the file box in [1].
This has also been updated in the website builder since the introduction
of the `html_builder` module in 18.4, which swapped uses of `web_editor`
components for `html_editor`.
As a side-effect, in website, a double click on a file did not open the
media dialog on the "document" tab, unlike other media (images, videos,
icons). In such a case, the file was also not shown as already selected
in the media dialog (because it targetted the wrong tag name). Both of
those behaviors were lost as we used the new file box design in 18.4.
[1]: https://github.com/odoo/odoo/commit/7f9afa21dffba2f74f9fb8a68809db4af6c7c225
task-5876278
Forward-Port-Of: odoo/odoo#259840
Forward-Port-Of: odoo/odoo#245904This update resolves an issue preventing portal users from creating tickets via email when automatic assignment is enabled. The fix ensures the system correctly accesses employee calendars across companies, eliminating access errors that were blocking ticket creation. This improves the portal's functionality for users submitting support requests.
Original PR description
Problem: Portal email with auto-assignment crashes ticket creation due to calendar access. When a helpdesk ticket is created via email from a portal user and automatic assignment is enabled, the…
Problem: Portal email with auto-assignment crashes ticket creation due to calendar access. When a helpdesk ticket is created via email from a portal user and automatic assignment is enabled, the system computes working intervals for users to determine assignment. This computation goes into resource logic, where resource.calendar fields (flexible_hours) are read. If the assigned user is linked to multiple employees across companies, multiple resource.resource records are evaluated. The helpdesk email flow starts in sudo, but the employee calendar lookup explicitly drops sudo before returning the calendar. Then, the calendar is accessed in the portal context, which does not have permission to read the other company's resource.calendar, leading to an AccessError and preventing ticket creation. Although the failure is triggered from Enterprise helpdesk, the actual crash occurs in Odoo (resource.calendar), meaning the fix must be applied there. Fix: Preserve sudo when fetching employee calendars to ensure that scheduling logic does not depend on the access rights of the email sender. A test is added in helpdesk_holidays, as the issue requires both helpdesk (auto-assignment) and hr (employees/resources) to reproduce. The test simulates a portal email flow with a multi-company user linked to multiple employees and ensures ticket creation succeeds. Steps to Reproduce: 1. Install Helpdesk, Employees, and enable multi-company 2. Create two companies (e.g., Company A and Company B) 3. Create one internal user (User X) with access to both companies 4. Create two employees linked to the same user: - Employee 1 in Company A - Employee 2 in Company B Make sure they are set with a start date, but no end date. Needs to be active employee. 5. Create a Helpdesk team in Company A 6. Add Agent X as a team member 7. Enable automatic assignment 8. Configure an email alias for the helpdesk team 9. Create a portal user 10. Send an email from the portal user to the alias Related Ticket: opw-6035099 Forward-Port-Of: odoo/odoo#257720
This update resolves an issue where portal email auto-assignment caused ticket creation failures due to permission errors accessing employee calendars across companies. The fix ensures that calendar access is handled correctly, allowing ticket assignments to proceed smoothly. This improves the reliability of the helpdesk system for portal users.
Original PR description
Problem: Portal email with auto-assignment crashes ticket creation due to calendar access. When a helpdesk ticket is created via email from a portal user and automatic assignment is enabled, the…
Problem: Portal email with auto-assignment crashes ticket creation due to calendar access. When a helpdesk ticket is created via email from a portal user and automatic assignment is enabled, the system computes working intervals for users to determine assignment. This computation goes into resource logic, where resource.calendar fields (flexible_hours) are read. If the assigned user is linked to multiple employees across companies, multiple resource.resource records are evaluated. The helpdesk email flow starts in sudo, but the employee calendar lookup explicitly drops sudo before returning the calendar. Then, the calendar is accessed in the portal context, which does not have permission to read the other company's resource.calendar, leading to an AccessError and preventing ticket creation. Although the failure is triggered from Enterprise helpdesk, the actual crash occurs in Odoo (resource.calendar), meaning the fix must be applied there. Fix: Preserve sudo when fetching employee calendars to ensure that scheduling logic does not depend on the access rights of the email sender. A test is added in helpdesk_holidays, as the issue requires both helpdesk (auto-assignment) and hr (employees/resources) to reproduce. The test simulates a portal email flow with a multi-company user linked to multiple employees and ensures ticket creation succeeds. Steps to Reproduce: 1. Install Helpdesk, Employees, and enable multi-company 2. Create two companies (e.g., Company A and Company B) 3. Create one internal user (User X) with access to both companies 4. Create two employees linked to the same user: - Employee 1 in Company A - Employee 2 in Company B Make sure they are set with a start date, but no end date. Needs to be active employee. 5. Create a Helpdesk team in Company A 6. Add Agent X as a team member 7. Enable automatic assignment 8. Configure an email alias for the helpdesk team 9. Create a portal user 10. Send an email from the portal user to the alias Related Ticket: opw-6035099 Forward-Port-Of: odoo/enterprise#113047
This update enhances the reliability of our IoT device monitoring by ensuring a callback is always triggered when a polling listener fails to connect. Previously, failures were silent, now the system reports an 'unreachable' status for each device, providing better visibility and alerting capabilities. This improves the overall stability of the IoT integration.
Original PR description
Enterprise PR: https://github.com/odoo/enterprise/pull/114779
Before this commit, if a longpolling listener failed to send the polling request to the IoT box, it would silently fail without calling any listener callback.
After this commit, the callback is called with `{ status: "unreachable" }` for each device listener associated with the IoT box.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#260931This update fixes a confusing issue for Mexican employees receiving payslips. Previously, an unstamped payslip was emailed immediately after confirmation, followed by a second email with the stamped version. Now, the email is only sent after the CFDI (tax document) is generated, ensuring employees receive the correct, fully stamped payslip.
Original PR description
Currently, when a user confirms a payslip batch (hr.payslip.run), the base payroll module queues the PDF generation and sends an email to the employee with their payslip immediately. For Mexican payslips, this means the employee receives the email with an unstamped payslip (without CFDI UUID). Later, when the CFDI is generated, a second email is sent with the stamped version, confusing the employee. This commit prevents the email from being sent for Mexican payslips if the CFDI has not been generated yet, ensuring only the stamped payslip is emailed. Forward-Port-Of: odoo/enterprise#114731
This update fixes a bug where VAT reports were incorrectly using the company's VAT number instead of the fiscal position's foreign VAT ID. The change ensures that VAT reports accurately reflect the correct tax identification number for each customer's location, improving tax reporting accuracy. This resolves an issue impacting VAT compliance for international customers.
Original PR description
### Issue: When a fiscal position defines a `foreign_vat`, tax reports generated for that country still use the company's VAT number instead For example, with a Belgian fiscal position using…
### Issue: When a fiscal position defines a `foreign_vat`, tax reports generated for that country still use the company's VAT number instead For example, with a Belgian fiscal position using `BE010203040`, the generated BE VAT report uses the company VAT instead of `010203040` ### Cause: The report generation did not check whether there is a fiscal position with a `foreign_vat` matching the country of the report ### Note: The example above uses a Belgian fiscal position to reproduce the issue Starting from 19.0, this specific flow is blocked because Intervat is enabled in production mode by default A related fix makes the Intervat settings available in that case Until then, the issue can be reproduced by temporarily commenting out: https://github.com/odoo/enterprise/blob/5fb58a9b7b3ae89f87e84d4ba1fa3a16237d80ac/l10n_be_intervat/models/account_return.py#L15 ### Steps to reproduce: - Disable demo data and install `accountant` - Create a Fiscal Position "Belgium" (Country: Belgium, Foreign Tax ID: BE010203040) - Click the alert to install the Belgian taxes - Create and confirm an invoice for a Belgian customer: - Fiscal Position: Belgium - Any product with a Belgian tax - Invoice Date: 01/01/2026 - Open the Tax Report and select `VAT Return (BE)` for January - Click `Returns` and select the full year - Mark the December return as Completed from the three-dot menu - Review January and fill the missing company data (TIN: 1111111, phone and email) - Click `Validate -> Lock -> Submit` ### Before the fix: The generated XML uses the company VAT number (`1111111`) instead of the fiscal position foreign VAT (`010203040`). opw-6076540 Forward-Port-Of: odoo/enterprise#112610
This update addresses a critical issue where Odoo Enterprise experienced a crash when attempting to download a URL document alongside a spreadsheet. The fix ensures stable and reliable downloads of combined documents, improving user workflow and preventing data loss. This resolves a previously reported instability.
Original PR description
Try to download a url document along with a spreadsheet. `onDownload` crash when trying to download a url document. Task: 5485662 Forward-Port-Of: odoo/enterprise#113645 Forward-Port-Of: odoo/enterprise#112513
This update fixes a display issue in reports related to invoices and purchase orders when users are in time zones ahead of UTC. The change ensures that reports accurately reflect the order date in the user's local time, preventing missed invoices or orders. This improves data accuracy for financial reporting.
Original PR description
Why this commit: When loading the 'bills to receive' or 'Invoices to be Issued' The time zones ahead of UTC will face the discrepancy in the view. e.g. etc/GMT-12 timezone is 12 hours ahead of UTC,…
Why this commit: When loading the 'bills to receive' or 'Invoices to be Issued' The time zones ahead of UTC will face the discrepancy in the view. e.g. etc/GMT-12 timezone is 12 hours ahead of UTC, So 12 AM UTC is 12 PM etc/GMT-12. So report view will not include the invoices/bill with order_date of current day till its 12 AM[next day] IN UTC, Meaning etc/GMT-12 will be seeing today's bills/invoices after 12 PM. After this commit: To resolve this discrepancy we use the context_today date to get the user local date. Which is required by the [domain sanitizer](https://github.com/odoo/odoo/blob/8bff78853f6ab8dc2cc951c03bb30181c0745834/odoo/orm/domains.py#L1572-L1574) too. Steps to reproduce (Possible in runbot) : 1. Select etc/GMT-12 timezone in preferences [when UTC is between 13:00-24:00 ~ 1:00-12:00 GMT-12(of next day)] 2. Create a PO and Validate the quantity received. 3. Go to accounting>review>bills to receive. 4. the newly created PO won't be listed here. OPW: 6083526 Forward-Port-Of: odoo/enterprise#114763
This update resolves a bug where deleting a button within the HTML editor would unexpectedly remove the entire editor. The fix ensures that after deleting a button, the editor correctly removes the button and adds a line break, preventing the editor from being cleared. This improves the user experience and stability of the HTML editor.
Original PR description
### Steps to reproduce: - Create a button, set its URL to #, and click Apply. - Place the cursor right after the link. - Press Backspace until the button/link is removed. - Entire editor also gets…
### Steps to reproduce: - Create a button, set its URL to #, and click Apply. - Place the cursor right after the link. - Press Backspace until the button/link is removed. - Entire editor also gets removed. ### In previous version: - Issue is due to [1](https://github.com/odoo/odoo/commit/7685e562b1036d08724ba91ed5064b6fe20c2ce2 ) change in `isEmptyBlock` (because of `isButton`). - When we had `<a>#[]</a>` and pressed backspace, `deleteRange` was called. - Then `fillShrunkBlocks` ran and `isEmptyBlock` returned true (no isButton). - So `<br>` was added and block never became fully empty. ### In current version: - Because of `isButton` condition, some empty blocks are not treated as empty. - So `<br>` is not added & block stays actually empty. Then `removeFEFF` runs & `nodeSize` becomes false & `cleanEmptyAncestors` removes parent even editable. ### After this PR: - Case like `<a>[]</a>`, backspace is handled by override which directly removes `<a>`. Since this skips `deleteRange`, manually call `fillShrunkBlocks` there. - Now after removing `<a>`, block is properly detected as empty and `<br>` gets added. task-6109145 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#259055
This update fixes an issue where increasing the quantity of a service order led to an incorrect purchase order quantity being generated. The fix ensures the quantity is consistently calculated in the sales order line's UoM, resolving a discrepancy in the purchase order creation process. This ensures accurate order fulfillment for service products.
Original PR description
Steps to reproduce the bug: - Create a service product "P1": - In the Purchase tab: - Vendor: Azure Interior - Subcontract Service: True - UoM: dozen - Purchase UoM: unit - Create a sales order with…
Steps to reproduce the bug:
- Create a service product "P1":
- In the Purchase tab:
- Vendor: Azure Interior
- Subcontract Service: True
- UoM: dozen
- Purchase UoM: unit
- Create a sales order with 1 dozen of P1
- Confirm -> a purchase order with 12 units of P1 is generated
- Confirm the purchase order
- Go back to the sales order:
- Update the quantity from 1 to 2 dozen
Problem:
A new purchase order is generated, but with 144 units instead of 12
units. The quantity difference between the old SO quantity and the new
one is computed twice in the purchase order line UoM, in both
`_purchase_increase_ordered_qty` and `_purchase_service_prepare_line_values`:
https://github.com/odoo/odoo/blob/17.0/addons/sale_purchase/models/sale_order_line.py#L186
Solution:
The `quantity` parameter must be expressed in the SO line UoM, as
described in the documentation of the function `_purchase_service_prepare_line_values`.
https://github.com/odoo/odoo/blob/17.0/addons/sale_purchase/models/sale_order_line.py#L178
opw-6049106
Forward-Port-Of: odoo/odoo#258002
Forward-Port-Of: odoo/odoo#255478A recent update caused crashes in the Self-Ordering and Mobile Menu (QR ordering) interfaces. This fix corrects a naming discrepancy in the POS data loading process, ensuring these key features now function correctly. The change updates a field name to align with the current SaaS-19.1 version.
Original PR description
## Overview Accessing the Mobile Menu (QR ordering) or Self-Ordering interface crashes after installing `pos_blackbox_be` on SaaS-19.1. The interface fails to load due to a missing field in the POS…
## Overview Accessing the Mobile Menu (QR ordering) or Self-Ordering interface crashes after installing `pos_blackbox_be` on SaaS-19.1. The interface fails to load due to a missing field in the POS data loading flow. ## Steps to Reproduce 1. Install `pos_blackbox_be` 2. Open POS 3. Access the Mobile Menu (QR code) or Self-Ordering page ## Current Behavior - A traceback is raised - The interface does not load ## Root Cause The method `_load_pos_self_data_fields` returns the field: iface_fiscal_data_module However, from SaaS-19.1 this field was renamed to: iot_fdm_be_id This mismatch causes the POS self-ordering data loading to fail. ## Fix Updated the returned field to match the new field name. # Before return fields + ['iface_fiscal_data_module'] # After return fields + ['iot_fdm_be_id'] ## Impact - Restores proper loading of Mobile Menu (QR ordering) - Fixes Self-Ordering interface crash opw-6044883 ## Reproduction Video https://drive.google.com/file/d/1R5TYVIXvtts12YLF5iB4GKZirMZePoGr/view?usp=sharing
This fix resolves an issue where updating a product in a Bill of Materials (BoM) after a manufacturing order is created would cause an error. The change prevents users from modifying BoMs linked to confirmed manufacturing orders, ensuring data consistency and preventing potential production disruptions. This ensures accurate inventory management.
Original PR description
Currently, an error occurs if a user changes the product in a BoM, updates the manufacturing order (MO) based on that BoM, and then attempts to unbuild the order. ## Steps to replicate: - Install…
Currently, an error occurs if a user changes the product in a BoM, updates the manufacturing order (MO) based on that BoM, and then attempts to unbuild the order.
## Steps to replicate:
- Install Manufacturing without demo data
- Settings > Enable Variants
- Create the following products:
- Car with (Red and Blue Color attributes)
- Red Paint
- Create a BoM for Car and product variant set to Red Car and have Red paint as the component.
- Create and Confirm manufacturing order for Red Car
- Click on Bill of Material > Set Paint required to 2 > Save
- Set product variant in BoM to Blue and save again.
- Go back to MO > Update BoM > Produce All
- Unbuild qty 1 > Confirm
## Observed Behavior:
ZeroDivisionError: float division by zero
## Root cause:
This issue occurs because the Update BoM button remains visible on the Manufacturing Order (MO) even after the product has been changed.
The problem starts when a user initially updates the required paint quantity from 1 to 2. At that point, the function [1] marks the BoM as outdated for all linked MOs, which makes the Update BoM button appear. However, if the user later changes the product template or variant, the BoM is still considered outdated. This incorrectly allows the user to update the MO using a BoM that no longer matches the selected product.
**Why this causes a traceback when unbuilding?**
When the user clicks Update BoM, it triggers the `action_update_bom function` [2], which calls `_link_bom`. This process recomputes several fields to align the MO with the updated BoM. One of the methods triggered during this recomputation is `_compute_move_finished_ids` [3]. Since the production is already confirmed, the logic skips adding the production to `production_with_move_finished_ids_to_unlink_ids`, meaning no new finished moves are created for that updated product.
As a result, although the MO is updated, its finished product (`move_finished_ids.product_id`) still refers to the original product (for example, Red Car), instead of the newly selected one.
Later, when the user attempts to unbuild the product, the `action_unbuild` function [4] is executed, which calls `_generate_consume_moves` [5] During this step, the system tries to compute a factor that depends on `unbuild.mo_id.quantity_produced`.
However, because the finished moves still reference the old product and do not match the MO’s current product, the computed total becomes zero at [6] This leads to a division by zero error at [5], which ultimately causes the traceback.
[1]:
https://github.com/odoo/odoo/blob/444de5354dcd70e9160a985486317a8912d53a84/addons/mrp/models/mrp_bom.py#L432-L447 [2]:
https://github.com/odoo/odoo/blob/444de5354dcd70e9160a985486317a8912d53a84/addons/mrp/models/mrp_production.py#L1044-L1048 [3]:
https://github.com/odoo/odoo/blob/444de5354dcd70e9160a985486317a8912d53a84/addons/mrp/models/mrp_production.py#L771-L800
[4]:
https://github.com/odoo/odoo/blob/97b60952d59a57aba12b048cb4da4f41d85d2ea2/addons/mrp/models/mrp_unbuild.py#L153-L164
[5]:
https://github.com/odoo/odoo/blob/444de5354dcd70e9160a985486317a8912d53a84/addons/mrp/models/mrp_unbuild.py#L225-L232 [6]:
https://github.com/odoo/odoo/blob/444de5354dcd70e9160a985486317a8912d53a84/addons/mrp/models/mrp_production.py#L641-L647
## Solution:
This change prevents users from updating a Bill of Materials (BoM) after the main product or its template has been modified, by ensuring the BoM is not marked as outdated.
This approach make sense because, once a manufacturing order (MO) is confirmed, all raw materials are physically reserved before production begins. While it makes sense to update BoM components in response to an Engineering Change Order (ECO) or last-minute specification changes, it does not make sense to allow changes to the final product itself on existing confirmed MOs. Doing so could lead to operational errors, since materials have already been procured and reserved for a specific product.
This fix ensures that if the product variant or product template is updated in the BoM, users cannot update the MO based on that BoM. This also prevents potential divide-by-zero errors when attempting to unbuild the product in the MO.
Reference commit which also suggests this behavior for the `Update BoM` button: [commit](https://github.com/odoo/odoo/commit/d7392829c769ef50456a7bc93d4482072b329463#:~:text=An%20exception%20however%3A%20if%20the%20MO%20is%20confirmed%20and%20the%20BoM%27s%20product%20was%0Achanged%2C%20the%20MO%20shouldn%27t%20have%20the%20%22Update%20BoM%22%20button%20displayed.%0AOtherwise%2C%20it%20would%20change%20the%20finished%20product%20of%20a%20confirmed%20MO.)
opw-6044754
Forward-Port-Of: odoo/odoo#260639
Forward-Port-Of: odoo/odoo#255981This update fixes an error in how the basic salary is calculated for Mexican employees, ensuring it accurately reflects the total calendar days worked, including unpaid leave. Previously, unpaid leave wasn't properly accounted for, leading to incorrect salary amounts. This change ensures accurate payroll processing for Mexican employees.
Original PR description
In Mexico, the basic salary must be calculated based on the total calendar days of the period. This ensures that both worked days and non-working days (e.g. Sundays) contribute equally to the total…
In Mexico, the basic salary must be calculated based on the total calendar days of the period. This ensures that both worked days and non-working days (e.g. Sundays) contribute equally to the total payment. This calculation also applies to the daily schedule, as the proportional daily wage must be divided equivalently across the hours of the day. Current behavior: When an employee has an unpaid leave, the basic salary is incorrectly prorated using only the registered days/hours. Example: For a monthly wage of 30,000 MXN in a month with 22 scheduled days (21 attendances + 1 unpaid leave), the implicit daily rate becomes 1,363.63 (30,000 / 22). This leads to an incorrect basic salary of 28,636.36 MXN for the days worked. This also happens with unpaid leave for x hours, e.g., for 2 hours, the unpaid leave is calculated as 2 hours * (30,000 / (22 days * 8 hours)) = 340.90 MXN, which is incorrect. Expected behavior: The basic salary should be derived from the full period (e.g., 30 days for a month, 15 for a bi-weekly period). Example: For a 30,000 MXN wage, the daily rate should be 1,000 MXN (30,000 / 30 days). If there is 1 unpaid leave, the basic salary should be 29,000 MXN (29 days * 1,000 MXN), regardless of the number of scheduled working days in the calendar. For unpaid leaves by hours, e.g., for 2 hours, the unpaid leave should be calculated as 2 hours * (30,000 / (30 days * 8 hours)) = 250.00 MXN. To achieve this, the calculation of the days in the `_get_worked_day_lines` is: * Adjust worked days/hours for out-of-contract entries where necessary, ensuring that rest days(Sundays) are included in the count. * Get all worked hours in the lines. * Calculate the number of days to pay based on the total hours and the hours per day. ### Case: payslip does not cover the complete pay period Current behavior: If a payslip is created for a partial period, the total amount is the full period wage. Expected behavior: The total amount should be pro-rated based on the days of the period. For example, if a payslip is created for 25 days(with a monthly schedule pay), the total amount should be the daily salary multiplied by 25 days. To achieve this, `_compute_amount` is updated to calculate the wage based on the `l10n_mx_daily_salary`. Changes on tests: * Add: * `test_monthly_payslip_with_partial_leave`, `test_partial_payslip`, `test_partial_payslip_new_hire_month_31_days` and `test_partial_payslip_new_hire_month_28_days`. * `test_hourly_payslip_by_attendance` to validate when `Work Entry Source` is set to "attendance". * Update: * `test_hourly_payslip`, `test_monthly_payslip` and `test_partial_payslip_new_hire` to align with the new calculation. * Adjust payslips dates to match the `schedule_pay` in `test_regular_payslip_subsidy` and `test_weekly_schedule_pay_no_code` * Fix a one-day difference in `TestMxEdiHrPayrollCommon`(16 days instead of 15 days for a bi-weekly schedule), and update the corresponding CFDI values. * Refactor tests and add new helpers. ### Error on [warning issues generation][1] and [`_compute_is_wrong_duration`][2] The warning: `"The duration of the payslip is not accurate according to the structure type."` appears with these custom periods for Mexican Payroll, although the period is correct: * `10_days` * `14_days` * `bi-weekly` Steps to replicate: * Install `l10n_mx_hr_payroll` module. * Switch to "INNOVACION VALOR Y DESARROLLO SA SA" company. * Go to Employees and open "Cesar Osbaldo Cruz Solorzano". * Click on "Payroll" tab, change the "Pay Schedule" to any option listed above, for example "Bi-weekly". * Go to Payroll > Payslips > Payslips and create a new pay run. * Select Salary Structure 'Mexico: Regular Pay', Pay Schedule 'Bi-weekly' and the Period '01/01/2026 -> 01/15/2026'. * Click on "Continue", select Cesar and click on "Select". * It appears the warning issue. Problem: The warning is raised because of `slip.date_from + slip._get_schedule_timedelta() != slip.date_to` condition, because `_get_schedule_timedelta` function calls [`self._schedule_timedelta(schedule, self.date_from)`][3] without the `country_code` argument. In the Mexican Payroll [_schedule_timedelta is overriden][4] but it is necessary to call it with the country code to use the custom periods; similar to how the [`date_end` is computed][5]. Solution: Call `_get_schedule_timedelta` passing the `country_code` [1]: https://github.com/odoo/enterprise/blob/1666ac87b6cb40e904210fecd159df3ac5b6b33a/hr_payroll/models/hr_payslip.py#L1367 [2]: https://github.com/odoo/enterprise/blob/1666ac87b6cb40e904210fecd159df3ac5b6b33a/hr_payroll/models/hr_payslip.py#L1454 [3]: https://github.com/odoo/enterprise/blob/1666ac87b6cb40e904210fecd159df3ac5b6b33a/hr_payroll/models/hr_payslip.py#L275 [4]: https://github.com/odoo/enterprise/blob/1666ac87b6cb40e904210fecd159df3ac5b6b33a/l10n_mx_hr_payroll/models/hr_payslip.py#L58 [5]: https://github.com/odoo/enterprise/blob/1666ac87b6cb40e904210fecd159df3ac5b6b33a/hr_payroll/models/hr_payslip_run.py#L211 target: 19.0 task-6073601 Forward-Port-Of: odoo/enterprise#112524
This update resolves an issue where the original product name was incorrectly prepended to product descriptions on invoices and RFQs when editing. The fix ensures that only translated names and descriptions are displayed, improving invoice clarity and accuracy for users working with multiple languages. This change impacts invoicing and purchasing workflows.
Original PR description
Steps to reproduce: 1- Install invoicing app 2- Add French language in the settings 3- Create a customer with language set as French 4- Create a product and define french translations of the name and…
Steps to reproduce: 1- Install invoicing app 2- Add French language in the settings 3- Create a customer with language set as French 4- Create a product and define french translations of the name and the description in Sales tab 5- Create an invoice for that customer and choose the product you created 6- You will find the translated product name and description under the product name 7- Edit the description, save and preview the invoice 8- The invoice line will contain [Product Name EN] [Product Name FR] [Product Description FR] Description of the issue: When creating an invoice for a customer whose language differs from the user's account language, manually editing the product description on an invoice line causes the original product name to be prepended to the description. The same issue happens in a RFQ in Purchase. Expected behaviour: User can edit the product description in the invoice line and the output in the invoice should only be the translated name and description, without the original product name. Why this happens? 1- When the product is selected in the invoice line, the label is loaded from _compute_name method in account_move_line, which holds the translated name and description. 2- After editing the description and escaping the field (clicking outside it), the parseLabel method is called, which prepends the original name to the label, making the invoice output as [original name] [translated name] [translated desc.] Fix: Use the product name returned in the label for trimming and concatenation to handle both original/translated text scenarios. References: original PR: #248401 partial revert: #254158 opw-5480494 Forward-Port-Of: odoo/odoo#256837
This update fixes inaccuracies in tax calculations for Argentina (l10n_ar) by modernizing the underlying tax calculation methods. The changes ensure accurate VAT and tax amount reporting, aligning with Argentina's specific tax regulations. This improves the reliability of financial reporting for Argentinian businesses.
Original PR description
This commit refactors the tax amount calculations on the `_get_vat` and `_l10n_ar_get_amounts` method so that it uses the tax computation engine helpers properly, to prepare for any future fixes done on how Argentina tax calculations differs from all other localizations. This replaces all move line queries with the proper `base_line` calculation, with the proper aggregating methods. related-enterprise-PR: https://github.com/odoo/enterprise/pull/92639 task-4891206 Forward-Port-Of: odoo/odoo#259976 Forward-Port-Of: odoo/odoo#223393
This update fixes inaccuracies in how tax amounts are calculated for Arabic VAT (l10n_ar_edi) within Odoo. By utilizing the new tax computation engine, the system now accurately processes and formats tax figures, ensuring correct financial reporting. This improves the reliability of VAT calculations for Arabic-speaking customers.
Original PR description
- Rewrite all tax amounts calculations on `_get_tributes` and `_get_line_details` to properly use the tax computation engine helpers (`base_line`, and aggregating methods) - Ensure that all final amounts from the calculation are formatted with `float_repr` with appropriate precision. related-community-PR: https://github.com/odoo/odoo/pull/223393 task-4891206 Forward-Port-Of: odoo/enterprise#114251 Forward-Port-Of: odoo/enterprise#92639
This update resolves an error that prevented users from grouping depreciation schedules by analytic plans. The issue stemmed from an incorrect formatting of data within the database query, specifically surrounding analytic account IDs. Removing unnecessary parentheses corrected the query and allows users to properly group schedules by analytic plans.
Original PR description
Currently, an error occurs when user tries to group by an analytic plan on depreciation schedule. Steps to replicate: - Install `accountant` with demo, turn on `Analytic Accounting` from settings. -…
Currently, an error occurs when user tries to group by an analytic plan on depreciation schedule.
Steps to replicate:
- Install `accountant` with demo, turn on `Analytic Accounting` from settings.
- Open `Accounting > Review > Depreciation Schedule`.
- Click `Analytic` > Add a `Plan`.
Error:
```
psycopg2.errors.UndefinedFunction: operator does not exist: text = record
LINE 12: WHERE key IN (('7', '16', '8', '15', '6', '9', '...
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
```
Cause:
- At line [1], we pass a tuple of `analytic_account_ids`, which is then wrapped again in parentheses at line [2].
- This results in the IDs being enclosed in double parentheses, e.g. `(('7', '16', '8', '15', '6'))`.
- When the `IN` clause is used with this double-parenthesized tuple, PostgreSQL treats it as a list containing a single record. It then tries to compare `key` (a text value) with that record `('7', '16', '8', '15', '6')`, effectively resulting in a `text = record` comparison, which is invalid and causes error.
Solution:
- Removed the extra parentheses from the query.
[1]: https://github.com/odoo/enterprise/blob/847b5be291adbfa315f8f74ab7a6dd3d1fb7d518/account_asset/models/account_assets_report.py#L124
[2]: https://github.com/odoo/enterprise/blob/847b5be291adbfa315f8f74ab7a6dd3d1fb7d518/account_asset/models/account_assets_report.py#L121
sentry-7423121981This update fixes a potential error in bank statement reconciliation. Previously, the system incorrectly matched bank transactions with payments from different companies using the same UUID. This could lead to foreign tax lines being added to the wrong company's accounting records. The fix ensures both the bank statement and payment share the same company hierarchy for accurate reconciliation.
Original PR description
ticket-5992100 When auto-reconciling bank statement lines, the end-to-end UUID lookup correctly checked that matched AMLs and their payment belong to the same company hierarchy, but missed checking that the payment also belongs to the same company hierarchy as the bank statement line itself. This allowed a payment from an unrelated company (sharing the same end-to-end UUID from an inter-company bank transfer) to be matched against another company's bank transaction, pulling foreign tax lines into the wrong company's journal entry. Fix by adding the same parent-path company check between the bank statement line and the payment. Forward-Port-Of: odoo/enterprise#113279
This update fixes an issue where invoices incorrectly showed as 'Paid' after a check was voided. The change ensures that the invoice payment state reverts to 'not_paid' when a check is voided, preventing incorrect payment reporting. This improves the accuracy of financial records.
Original PR description
Steps to reproduce: 1- Install l10n_ar and l10n_latam_check modules 2- Switch company to (AR) Responsable Inscripto 3- Go to [Accounting -> Configuration -> Journals -> Bank] and make sure 'outstanding payments account' is set in outgoing payments for own checks and manual payment 4- Go to [Accounting -> Vendors -> Bills] and create a new bill 5- Create an own check payment for the full amount 6- Go to the check and void it Description of issue: When you view the invoice after voiding the check, it's payment state will be shown as 'Paid' Expected behavior: Invoice payment state should go back to 'not_paid' Why this happens: When the check is voided, it's `amount_residual` attribute becomes 0. This causes `pay.is_matched` to be True, and as a result `all_payments_matched` attribute is also True. This turns the invoice payment state to paid. opw-6016394 Forward-Port-Of: odoo/odoo#256118
This update fixes an issue where stock quantities weren't always correctly reflected in purchase and sales orders. It now ensures quantities match the original order unit of measure and creates pickings for partial orders, improving order fulfillment accuracy. Additionally, it corrects a demo stock imbalance to prevent negative stock levels in demo data, ensuring consistent reporting.
Original PR description
Make sure that the quantity received is in the unit of measure of the purchase order line. Also, when installing stock, create pickings for partial and empty sale/purchase orders. Finally, since creating we're creating more pickings, we need to raise the demo stock of product_product_12 to not be in negative stock for other modules demo data. task 5431550 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This update fixes a confusing error message that appeared when employees changed their work schedules while existing holiday periods were in place. The fix now includes the original error traceback, making it easier for support teams to diagnose and resolve the issue quickly. This improves the overall user experience and reduces troubleshooting time.
Original PR description
A validation error is raised if changing employee's contract with a new working schedule on a period with leaves and the new working schedule changes the duration of these leaves in such a way that the employee no longer has the required allocation for them. This adds to the error message the original error traceback for debuggig purposes. Task: 6105516 Forward-Port-Of: odoo/odoo#258280
This update corrects a bug in the self-ordering point-of-sale system that caused incorrect pricing when customers ordered multiple units of combo products. The fix ensures that free item quantities are properly scaled with the parent order, preventing miscalculations and ensuring accurate pricing for larger purchases. This improves the reliability of combo pricing.
Original PR description
When buying more than one unit of a combo product, the free-item quota (qty_free) was not scaled by the parent quantity, causing child lines with qty > 1 to be partially mis-classified as extra. This meant the same line was processed by both the free and extra loops, with the extra loop overwriting the correct price. Additionally, the proportional price_unit for free child lines used the unscaled original_total (which already includes the parent qty factor) against a per-unit parent_lst_price, resulting in a price that was too low by exactly the parent qty factor. opw-6045562 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#258753
This update resolves a performance issue that caused slowdowns and crashes when working with many2many fields containing a large number of records. The change replaces a slow search method with a faster one, ensuring smoother operation and preventing UI freezes when handling large datasets.
Original PR description
### Issue before this commit: When handling many2many fields with a large number of records (e.g., 20k+), the client-side performance degraded significantly. In extreme cases, the browser became…
### Issue before this commit: When handling many2many fields with a large number of records (e.g., 20k+), the client-side performance degraded significantly. In extreme cases, the browser became unresponsive or crashed when triggering onchange or compute logic. ### Steps to Reproduce: Create a computed many2many field. Add it to a form view (can be invisible). Populate the related model with a large dataset (20k+ records). Trigger an onchange that recomputes the field. ### Cause of the Issue: In _applyCommands (LINK case), the system checks for existing record IDs using Array.includes(), which has O(n) time complexity. When handling thousands of records, repeated ID lookups using includes() result in O(n²) complexity. ### With This Commit: Replaced Array.includes() with a Set (Set.has()), reducing lookup time to O(1). The set is updated incrementally as new IDs are added, improving overall complexity to O(n) and preventing UI freezes for large datasets. opw-6122024 Forward-Port-Of: odoo/odoo#260993
This update fixes an error in how holiday leave time off is calculated. Previously, public holidays were incorrectly included in the time off duration, leading to inaccurate 'Approved Time Off' values. The fix ensures that time off is calculated correctly, aligning with the 'Ignore Public Holidays' option.
Original PR description
# Setup You'll need a User with : - An active contract (for easiness of testing, a contract that started long ago with 8hrs/day) # How to reproduce - Create a new Time Off type with "Ignore Public…
# Setup
You'll need a User with :
- An active contract (for easiness of testing, a contract that started long ago with 8hrs/day)
# How to reproduce
- Create a new Time Off type with "Ignore Public Holidays" enabled
- Create a Public Holiday for Period X
- Create A Time Off request for the User for a Period Y that contains Period X
- Go to the Time Off Ledger
- Remove the Missing Hours filter and search for the dates in Period Y
Exemple of periods :
- Period X => Feb 10 2026 - Feb 10 2026
- Period Y => Feb 9 2026 - Feb 11 2026
# The problem
The "Approved Time Off" and "Difference" values are wrong.
With the given exemples, we'll see Feb 9 and Feb 11 with "Approved Time Off" values of 12hrs, which is wrong since the employee is supposed to work 8hrs a day, so he should have a time off of also 8hrs.
# Cause
The calculation for "Approved Time Off" is the following :
Divide the `number_of_hours` of a hr_leave
By the number of working days during the period of the leave
Using our exemple, we get :
`number_of_hours` = 24hrs
number of working days = 2
Approved Time Off = 24hrs / 2 => 12hrs, but we expect 8hrs
The `number_of_hours` is correct since we checked "Ignore Public Holidays" (which actually means : include the public holidays in the number of hours of a leave)
The problem is that the aggregation for the number of working days excludes automatically
public holidays, without paying attention to the value of "Ignore Public Holidays" :
https://github.com/odoo/odoo/blob/5e623af55fba64e812db6bcaf06d8f7c5d08f055/addons/hr_holidays_attendance/report/hr_leave_attendance_report.py#L193-L202
Explanation for this part of the query : we only keep days where there is no record in
resource_calendar_leaves (`WHERE rcl2.id IS NULL`) that contains that day
and that are considered public (`AND rcl2.resource_id IS NULL`)
# Proposed Solution
We add a `JOIN hr_leave_type` to be able to get the value for
`include_public_holidays_in_duration` ("Ignore Public Holidays").
Then, we make it so we exclude the Public Holidays only if that value is
false (`AND NOT lvt.include_public_holidays_in_duration`)
opw-6082422
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#258963This update resolves an issue where attachment downloads from the Odoo chatter were failing in Odoo 18.4+. The fix ensures that absolute URLs for attachments are correctly handled by the mobile apps, preventing errors and restoring reliable download functionality. This change was made on the JavaScript side to avoid requiring an update to the Android app.
Original PR description
In Odoo 18.4+, downloading attachments from the chatter is broken.
See: https://github.com/odoo/odoo/pull/200099
The `onClickDownload` function now passes an absolute URL to `downloadFile`.
The download function is implemented natively in the mobile apps.
The Android implementation always prefixes the provided URL with the
database origin (i.e.: `https://example.odoo.com`).
`download({url: "https://example.odoo.com/web/content"})` will try to
download `https://example.odoo.comhttps://example.odoo.com/web/content`.
This results in an UnknownHostException.
We can remove the origin from the url before calling the native method.
By doing this on the JS side, there is no need to update the Android app.
opw-6033150
Forward-Port-Of: odoo/enterprise#114832This update ensures that the AI chat window now opens in full-screen mode, regardless of how it's initiated – through the system tray or command palette. Previously, the chat would open in a background window, which has now been resolved for a smoother and more convenient user experience.
Original PR description
Prior to this commit, when opening the chat with an agent from the systray button, the chat window was opened in the background. This commit fixes the issue by adding a call to `channel.open` which opens the chat when in full-screen mode. This commit also fixes an issue where the chat window wasn't properly opened when done from the command palette. task-5172978 Forward-Port-Of: odoo/enterprise#114598
This update fixes an issue where MTSO procurements incorrectly calculated available stock quantities, leading to inaccurate purchase order quantities. The change ensures that stock availability is accurately considered across multiple BOM levels during procurement creation, preventing overestimation of required components.
Original PR description
When creating a procurement through mtso, if the product has a muti level bom with the same component at multiple levels, it will consider the available quantity multiple times. Steps to reproduce:…
When creating a procurement through mtso, if the product has a muti level bom with the same component at multiple levels, it will consider the available quantity multiple times. Steps to reproduce: ------------------- * Enable MTO and change supply method to: "Take From Stock, if unavailable, Trigger Another Rule" * Create three products : final, semi, component - final: mtso, manufacture - semi: mtso, manufacture - component: mtso, buy, on hand quantity to 4 * Create a bom for final: - 10 components - 1 semi * Create a bom for semi: - 10 components * Create and confirm a MO for 1 "final" -> Issue the purchase order is only for 12 components and not 16. Observation: ------------- When confirming our MO, it will create a manufacture procurement for the products. The procurement will recursively create procurements and stock moves for each of its components. https://github.com/odoo/odoo/blob/b0b8a102153eaa9231321524ec5140cc6d754502/addons/stock/models/stock_move.py#L1563-L1571 Since its a MTSO, it will first check the products if there is available products in stock (free_qty) and create the procurement for the missing quantity: https://github.com/odoo/odoo/blob/b0b8a102153eaa9231321524ec5140cc6d754502/addons/stock/models/stock_move.py#L1646-L1647 https://github.com/odoo/odoo/blob/b0b8a102153eaa9231321524ec5140cc6d754502/addons/stock/models/stock_move.py#L1657-L1663 And each procurement, if it is of the manufacture type, will create corresponding procurements for their components. Once all the procurements and stock moves have been created, the stock move will confirmed and assigned. https://github.com/odoo/odoo/blob/942cbbbf243ff28f84fdaa40ed73b6572e0032a6/addons/stock/models/stock_move.py#L1627-L1629 -> The issue arise because the free_qty will only be updated when the stock moves are assigned which happen after all the procurement quantity are calculated for all the levels. opw-5514788 Forward-Port-Of: odoo/odoo#253958
This update fixes an issue where IoT events were missed due to a failure in the longpolling fallback mechanism. Now, if longpolling requests fail, the system automatically switches to using the more reliable WebSocket connection, preventing disruptions like failed Worldline payments. This ensures consistent event delivery.
Original PR description
Community PR: https://github.com/odoo/odoo/pull/260931 Before this commit, if `onMessage` in `iot_http_service` was called directly, it would fail to fallback to websocket if the longpolling request failed, causing events to be missed. One symptom of this is Worldline payments failing to confirm when using websocket. After this commit, the `_longpolling` method will now throw an error in this case, causing the fallback mechanism to attempt websocket instead. Forward-Port-Of: odoo/enterprise#114779
This fix ensures that the default website correctly updates when the sequence order is changed, particularly in incognito browsing sessions. Previously, website defaults weren't refreshed after reordering, leading to incorrect website selection. The update restores a cache clearing mechanism to maintain accurate website defaults.
Original PR description
**Problem:** Changing the order of websites does not update which website is shown as the default when visiting from an incognito window (no domain match). **Steps to reproduce:** 1. Create two…
**Problem:** Changing the order of websites does not update which website is shown as the default when visiting from an incognito window (no domain match). **Steps to reproduce:** 1. Create two websites with no domain set 2. Change their sequence order via the handle widget in the backend 3. Open an incognito window 4. The default website shown is still the old one **Current behavior:** The default website does not change after reordering. **Expected behavior:** The website with the lowest sequence should be served as the default. **Cause of the issue:** Commit d6f4af2790a0 replaced `models.Model` with `models.CachedModel` and removed the blanket `self.env.registry.clear_cache()` from the top of `write()`. CachedModel only auto-clears caches for fields listed in `_cached_data_fields`, but `sequence` is not in that list. As a result, `_get_current_website_id` (decorated with `@tools.ormcache`) keeps returning the stale cached website ID after a sequence change. https://github.com/odoo/odoo/commit/d6f4af2790a0abacba6e616b00d999eddc30edc9#diff-5e92e473fa4d3da6db7ef727fb217dad51ef6c2383913edca73fe040a23e82c2L339-L341 **Fix:** Restoring `clear_cache()` scoped to the existing sequence/company_id check ensures the ormcache is invalidated only when relevant fields change, rather than on every write as before. opw-6102426
This update ensures that all Brazilian tax documents generated through EDI comply with local law. It incorporates approximate tax values provided by Avalara, which are now included in the EDI payload regardless of whether actual tax information is available. This ensures accurate and compliant reporting for Brazilian businesses.
Original PR description
All fiscal documents are required by Brazil law to include the approximate value of fed, state, and city taxes that affect it. Avalara already provides back these values in their tax calculation response, we just missed sending it to the EDI. This commit takes the information from that response and adds it to the EDI payload to make sure that it is generated properly into the generated documents. We are required to always show this even if there are no informative taxes as such we combine it with the T&C sent already. task-5478059 Forward-Port-Of: odoo/enterprise#113732
This update resolves an issue where creating a filter with invalid data in Odoo views would cause the application to crash. Now, the system gracefully handles these errors without a crash, although the filter itself isn't active. This improves stability and prevents disruptions to users creating and editing filters.
Original PR description
On some view, create and edit a filter, but put something unparseable by JS in the `context` field eg: `{123}`.
Go back to the view.
Before this commit there was a crash, because the python parser in JS crashed.
After this commit, there is no crash, the filter is visible but not activable.
Description of the issue/feature this PR addresses:
Current behavior before PR:
Desired behavior after PR is merged:
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#260612This update resolves an issue where the minimum IS (Insurance Savings) amount was incorrectly calculated in the Swiss payroll module. The fix ensures accurate IS calculations, aligning with Swiss tax regulations and improving payroll accuracy. This impacts the correct processing of employee insurance contributions.
Original PR description
Forward-Port-Of: odoo/enterprise#114463
This update resolves an issue where deleting a project stage incorrectly navigated users to a different view and displayed archived tasks. The fix ensures the original view remains active, the stage is properly deleted, and the user's intended task list is displayed correctly. This improves the user experience when managing project stages.
Original PR description
# Steps to reproduce 1. Create a project 2. Create a stage 4. Remove the stage # Current behavior Instead of remaining in the project tasks view, it switches to the tasks view filtered with the current project. Additionally, it displays archived tasks because no filter is selected, thereby discarding original ones. This also applies to stage deletion in other views (e.g., My Tasks), where the search filters are completely discarded. # Expected behavior The dialog should be closed, the stage should be deleted, and the original view should remain active. This is done through a soft-reload of the page, ensuring the original view is kept, together with original breadcrumbs, and the stage is visually disappearing. task-5498274 Forward-Port-Of: odoo/odoo#260213 Forward-Port-Of: odoo/odoo#246935
This update corrects a bug where the DIAN web service was incorrectly overwriting customer contact information (names and emails) with fiscal data, leading to data loss and incorrect invoice delivery. The fix now intelligently handles email differences, creating a new contact if needed and giving users control over their data.
Original PR description
The DIAN web service was overwriting partner names and emails with fiscal data, causing data loss for CRM contacts. The fiscal email often differs from the commercial one, and the overwrite broke the sales flow by sending invoices to the wrong address. Users had no standard workaround short of manually re-entering emails after every invoice generation. Instead of blindly overwriting, only update empty fields and create a child invoicing contact when the DIAN email differs from the existing one. Also remove the automatic onchange and periodic re-fetch triggers to leave existing data under user control. task-5912005 Forward-Port-Of: odoo/enterprise#114017