Daily updates from Odoo
Navigate
Branch
Wednesday, August 19, 2026
285 changes
28 changes
Enhancements to existing features
Cached website generation requests now schedule a quick follow-up trigger instead of waiting or calling webhooks too early. This helps related notifications run reliably without tying up processing workers.
Original PR description
When a request is cached a webhook won't be called because if we did it immediatly it might arrive before the generator commited. And we don't want to delay the webhook call to not stall a worker just for that. So we simply scheldule a trigger if a request was cached. Forward-Port-Of: odoo/enterprise#128206
The Dutch payroll localization now includes the 2026 resident income tax rate values. This helps payroll calculations stay aligned with the latest tax rules for employees in the Netherlands.
Original PR description
Added 2026 values for the residents' income tax rates rule parameter. task-6462877 Forward-Port-Of: odoo/enterprise#127556
Users can now move through return items in the returns kanban view using the up and down arrow keys without triggering an error. This improves day-to-day navigation and prevents interruptions when reviewing or selecting returns.
Original PR description
In returns kanban view, a traceback occurs when pressing down. Fix this by adding the support for up/down keyboard navigation for returns selection. task-6281033 Forward-Port-Of: odoo/enterprise#128308 Forward-Port-Of: odoo/enterprise#125164
Resolved issues and error corrections
The Project task Gantt view now avoids showing misleading progress bars on individual task rows when grouped by assignee in sparse mode. Instead, users see the task's allocated hours, making planning information clearer and less confusing.
Original PR description
## Current behavior: In the task gantt view grouped by assignees and displayed in sparse mode, task leaf rows reuse the row progress bar and display a misleading visual indicator on each task. ## Expected behavior: It would make more sense to not display anything for the progress bar of each task, rather display the allocated_hours per task ## Steps to reproduce: 1. Open Project tasks in Gantt view 2. Group by Assignees 3. Enable sparse display so each task appears on its own row 4. Notice that task rows show a progress bar that is not meaningful for the task itself ## Cause of the issue: Each task rows currently has no meaningful progress-bar semantics, but they still inherit the generic row progress-bar rendering ## Fix: Override the task gantt renderer to hide progress bars, and only show `allocated_hours` for each task opw-6352260
This fixes an issue that could cause Colombian point-of-sale order reports to crash when users grouped records by state. The problem came from an outdated internal name left after two localization modules were merged, and the update keeps reporting behavior stable.
Original PR description
l10n_co_edi and l10n_co_dian were two modules merged together, when that happened some of the dian models and systems were renamed to the standard `l10n_co_edi.` system. The sql compute for the state field on pos.order was missed/forward ported up to a version that didn't have that name and as such would crash if you try to group. task-none
Fixed an issue where Dominican Republic electronic consumer invoices could show the company name twice when company address details were not configured. Invoice headers now display the company name only once, making printed and previewed e-CF invoices cleaner and more professional.
Original PR description
Steps to Reproduce: 1. Install l10n_do_edi on a company configured with Dominican Republic localization. 2. Leave the company's address / Document Layout details unconfigured (Settings > Companies >…
Steps to Reproduce:
1. Install l10n_do_edi on a company configured with Dominican Republic localization.
2. Leave the company's address / Document Layout details unconfigured (Settings > Companies > Configure Document Layout).
3. Enable 'Use Documents' on the sales journal and create a Customer Invoice with Document Type = Electronic Consumer Invoice (e-CF).
4. Confirm/post the invoice and print/preview the PDF.
5. Observe the company name is printed twice in the header.
Root Cause:
l10n_do_edi.custom_header explicitly renders the company name via:
[`<span t-field='o.company_id.partner_id.name'/>`](https://github.com/odoo-dev/enterprise/blob/c23d526f53402471dccbbf9460cfd04ce377b5d0/l10n_do_edi/views/report_invoice.xml#L10)
immediately followed by a call to the core template web.company_address_list:
[` <t t-call='web.company_address_list'/>`](https://github.com/odoo-dev/enterprise/blob/c23d526f53402471dccbbf9460cfd04ce377b5d0/l10n_do_edi/views/report_invoice.xml#L11)
When the company's address/Document Layout is empty, `company.is_company_details_empty` evaluates True, causing `web.company_address_list` to fall back to a contact widget with `fields=['address', 'name']` — which renders the company name a second time. custom_header assumed `company_address_list` would never render the name itself, which does not hold in this empty-address state, resulting in the duplicate. This only surfaces on e-CF document types, since custom_header is only set via l10n_do_edi.report_invoice_document's routing (_get_name_invoice_report()), which standard invoices never hit.
Solution:
Made the explicit name span in `l10n_do_edi.custom_header` conditional on whether the company's details are configured. `web.company_address_list` (core, shared across other reports, left untouched) already renders the company name via its contact widget fallback when the address is empty — so the explicit span now only prints the name when `company_details` is populated, avoiding both sources printing it at once.
Result:
Company name now renders exactly once on e-CF invoices, regardless of whether the company's address/Document Layout is configured.
opw-6430918
Forward-Port-Of: odoo/enterprise#127542Fixes an issue where clearing a spreadsheet filter could also remove its default value from the edit panel. This keeps the value currently applied to a spreadsheet separate from the default value shown when editing the filter, making filter setup more reliable for users.
Original PR description
Current behavior before PR: - In [da282d4](https://github.com/odoo-dev/enterprise/commit/da282d4a031fa1c1377a6836acf662100c68bde0), date filter values were cleared when selecting 'All time'. - Based on this, we handled the crash by relying on the active filter value, but forgot that the same component is also used in the edit panel. - As a result, clearing a filter could make its default value disappear from the edit panel. Desired behavior after PR is merged: - Always use the `globalFilterValue` passed to `GlobalFilterInput`. - Keep the current filter value independent from the filter's default value. - This makes the component independent of the active filter state. Task: [6388147](https://www.odoo.com/odoo/project/2328/tasks/6388147) Forward-Port-Of: odoo/enterprise#125153
The salary simulation for Indian regular pay structures no longer clears newly entered values when the popup opens. This prevents misleading missing-field errors and lets HR users complete simulations reliably.
Original PR description
Steps :- - On opening the Simulation when India: Regular pay structure is selected, throws "Missing required fields" when fields are changes on form view. Fix:- - For Indian company, the TDS calculation ran in the background while opening the popup, and it was clearing the values just entered. This calculation isn't needed for a simulation, so it is now skipped. task-6392171 Forward-Port-Of: odoo/enterprise#126483
Weekly rentals that start and end on the same weekday are now counted as one week instead of being rounded up to two. This prevents customers from seeing an inflated rental duration and helps ensure clearer pricing on the website.
Original PR description
A product with a weekly rental periodicity is booked for 2 weeks when we actually book it for a single week Steps to reproduce: 1. Install Rental and eCommerce 2. Go to Rental > Products and create a new product 'test', in the Sales tab, set the rental periodicity to 'Weeks' 3. Click on the smart button 'Go to Website' 4. Change the rental period so that it exactly covers a week (e.g. from Monday to Monday) 5. The website shows that you're booking for 2 weeks Issue: The default pickup time is 9h and the default return time is 18h. When we select exactly one week for the rental duration, the true duration of the rental is greater than 1 week (because of the pickup and return time) so it is rounded as 2 weeks. Solution: Also swap `pickup_time` and `return_time` when swapping from weeks periodicity. opw-6397786 Forward-Port-Of: odoo/enterprise#126395
The document app’s automated tests were updated to reflect improved GIF thumbnail processing. This helps ensure resized GIF thumbnails are validated correctly without failing on outdated byte-by-byte comparisons.
Original PR description
Previously, test_document_thumbnail_status asserted that document thumbnails were byte-for-byte identical to the raw GIF content. Following improvements to GIF handling in image_process(), thumbnails are now correctly resized, causing the raw byte assertion to fail. Update the test to assert the presence and status of the thumbnail rather than matching exact raw unresized payload bytes. [odoo/odoo#281883](https://github.com/odoo/odoo/pull/281883) opw-6232841 Forward-Port-Of: odoo/enterprise#128150 Forward-Port-Of: odoo/enterprise#128032
Fixed an issue where switching to a pivot report through the AI assistant could sometimes crash the view or open it with no active measures. The change ensures the report finishes loading before AI adjustments are applied and keeps default measures when the AI request does not specify them.
Original PR description
When the AI agent switched from another view to a pivot view, the pivot view could crash or open without any active measures. The AI controller patch applies the agent's adjustments upon receiving…
When the AI agent switched from another view to a pivot view, the pivot view could crash or open without any active measures. The AI controller patch applies the agent's adjustments upon receiving the `APPLY_AI_ADJUST_MODEL` bus event. However, the event could be processed while the pivot model was still executing `_loadData()`. In that case, the following sequence occurred: * `_loadData()` started and awaited. * The controller patch was executed. * The patch called `toggleMeasures()`. * `toggleMeasures()` waited for `_loadData()` to complete. * `_loadData()` finished and updated the metadata with the available measures. * `toggleMeasures()` resumed and wrote back the metadata snapshot it had taken before waiting. Since `toggleMeasures()` operates on a snapshot of the metadata, the measures populated by `_loadData()` were lost when the snapshot replaced the current metadata, leaving the pivot model without its `measures` metadata and causing the view to crash. Prevent this race condition by waiting for the pivot model initialization to complete before applying the AI adjustments. Also preserve the default active measures when the AI agent does not explicitly request any measures instead of clearing them and opening an empty pivot view. task-6384368 Forward-Port-Of: odoo/enterprise#125897
Enabling Billing in Planning now automatically selects the expected default project after installation. This prevents setup confusion for teams using Planning with Field Service and sales timesheets.
Original PR description
Steps to reproduce: - 1. Create a new database and install Planning and Field Service. 2. Open the Planning settings and enable Billing. Issue: - Billing is enabled but no default project is selected. Cause: - The default of `res.company.planning_project_id` references the module's own `fsm_project` xmlid, but it is evaluated before the module's data files are loaded. The ref resolves to nothing and no value is written. Fix: - Set the project in the post-init hook, once the data files are loaded. task-6460106
Users can now see and select WhatsApp signing templates even before they are approved. This makes it clearer that templates must be submitted for approval, while keeping the related signing flow inactive until approval is complete.
Original PR description
Previously, users couldn’t see or select unapproved WhatsApp templates in Settings, which made it unclear that they needed to submit templates for approval first. Now, users can select unapproved templates, making the process clearer. The flow will still remain disabled until the template is approved. task-6306091 Forward-Port-Of: odoo/enterprise#127910
A leftover employee appraisal action that no longer worked has been removed. Users should use the existing Launch Campaign option to request appraisals for multiple employees, avoiding an error when starting appraisal requests.
Original PR description
#### Description of the issue/feature this PR addresses: The "Request Appraisals" server action on hr.employee calls model._create_multi_appraisals(), a method that no longer exists. Running it…
#### Description of the issue/feature this PR addresses: The "Request Appraisals" server action on hr.employee calls model._create_multi_appraisals(), a method that no longer exists. Running it raises AttributeError: 'hr.employee' object has no attribute '_create_multi_appraisals'. #### Current behavior before PR: Commit 8845eb2ac29 replaced the multi-appraisal flow with hr.appraisal.campaign.wizard: it deleted _create_multi_appraisals and repointed the employee list header button to action_open_appraisal_campaign_wizard, but left the action_create_multi_appraisals record in hr_appraisal/views/hr_employee_views.xml. Its code is now the only reference to the deleted method, so the action crashes whenever it is run. #### Desired behavior after PR is merged: The dangling action is gone. Requesting appraisals for several employees at once is done with the "Launch Campaign" button already present in the Employees list view; action_open_appraisal_campaign_wizard reads active_ids when active_model is hr.employee and pre-fills the selected employees. Nothing references the removed xml id, and the record is not noupdate, so _process_end removes it from existing databases on update; no migration script is required. Verified on a 19.0 database: with the orphan record loaded, updating hr_appraisal with this change deletes it. opw-6408609 Forward-Port-Of: odoo/enterprise#127983 Forward-Port-Of: odoo/enterprise#125630
This update prevents errors when payroll salary attachment names are generated for several employees at once. It helps payroll users view and process multiple salary attachments reliably without interruptions.
Original PR description
Currently, an error occurs when the display name is computed for multiple salary attachments. `ValueError: Expected singleton: hr.employee(58, 56)` After [recent commit], when computing the display name, the employee's display name is accessed through multiple attachment records at once. This results in accessing the display name of multiple employees simultaneously, which raises a singleton error. This commit ensures that the employee is accessed from each individual attachment record when computing the display name. [recent commit]: https://github.com/odoo/enterprise/commit/d9648ef695903113d6ed2f40cd4fcfdd68221fa8 [1]- https://github.com/odoo/enterprise/blob/d913de3097d06e723a08d24907024f83769b1fb8/hr_payroll/models/hr_salary_attachment.py#L150-L153 sentry-7665634830 Forward-Port-Of: odoo/enterprise#127765
The Belgian payroll warning for missing transport benefits will no longer appear for company executives. This prevents irrelevant alerts on executive contracts and keeps the warning focused on regular employees who may need transport benefits reviewed.
Original PR description
The transport benefit warning incorrectly applies to company executives (Joint Committee = 999). This warning is intended solely for regular employees. Steps to reproduce: 1. Go to an employee's contract and set the Joint Committee to 999. 2. Set a monthly wage that results in an annual salary below 34,654€. 3. Ensure no transport benefits are selected. 4. Save the contract; the missing transport benefit warning is incorrectly displayed. This change skips the validation for Joint Committee 999 to ensure the warning only triggers when applicable. task-6442880
This fix ensures confirmed manufacturing orders correctly reflect changes made to their bill of materials. When operations are removed or adjusted on a bill of materials, using Update BoM now removes obsolete steps and applies relevant updates, helping production teams avoid outdated work instructions.
Original PR description
Steps to reproduce: - Create a product with a bom and 2 operations - Create an MO for 1 unit of that product - Confirm the MO - On the bom, delete the second operation and modify the first operation…
Steps to reproduce: - Create a product with a bom and 2 operations - Create an MO for 1 unit of that product - Confirm the MO - On the bom, delete the second operation and modify the first operation on anything else than the company, name or workcenter - Go back to the MO, click the "Update Bom" button > The second operation is not unlinked and the first operation is not updated Cause of the issue: The `action_update_bom` updates the move raws and operations of the MO via the `_link_bom`: https://github.com/odoo/odoo/blob/f66614193cce18f5a3298d03ce7e5f29d54f07e9/addons/mrp/models/mrp_production.py#L1214-L1218 For draft MO's all the work of these updates is done via the compute methods and by deleting all the records unrelevant to the new bom: https://github.com/odoo/odoo/blob/f66614193cce18f5a3298d03ce7e5f29d54f07e9/addons/mrp/models/mrp_production.py#L2603-L2626 And, in that case all the workorders that are not linked to an operation of the bom are expected to be deleted. However, when the MO is not in draft, the update of operations is expected to be performed here: https://github.com/odoo/odoo/blob/f66614193cce18f5a3298d03ce7e5f29d54f07e9/addons/mrp/models/mrp_production.py#L2647-L2664 However, since the operation of the bom has been deleted, the workorder that is expected to be deleted is not linked to any operation and hence does not satisfy the condition to be deleted: https://github.com/odoo/odoo/blob/f66614193cce18f5a3298d03ce7e5f29d54f07e9/addons/mrp/models/mrp_production.py#L2663-L2664 Concerning the non update of operations, it happens because the MO's operation are only updated on the three fields: `company_id`, `workcenter_id`, `name`: https://github.com/odoo/odoo/blob/31df5033e31c193b4576ef37dfbc5fc683817bc5/addons/mrp/models/mrp_production.py#L2647-L2664 https://github.com/odoo/odoo/blob/31df5033e31c193b4576ef37dfbc5fc683817bc5/addons/mrp/models/mrp_production.py#L2628-L2629 However, many other cahnges can and are actually relevant. Note: Prior to commit 80e6ed658fb43584bc2fad673ca40d9af6cf0ab6 operations were archived on boms rather than deleted: https://github.com/odoo/odoo/blob/4a5270218fe6fd7d30edb6d684b3340dc7423bab/addons/mrp/views/mrp_routing_views.xml#L53-L55 As such they would still be linked to an operation (but unrelated to the present values of the bom) and hence would fall into the condition of being unlinked from the MO. Since the bom operations are no longer archived there is no way to determine if an operation used to be linked to a bom and we therefore need to chose between deleting all operations unrelated to the present bom or to keep them all (when the MO has been confirmed). Community: https://github.com/odoo/odoo/pull/269747 opw-6285878 opw-6261738 Forward-Port-Of: odoo/enterprise#128120 Forward-Port-Of: odoo/enterprise#120709
The salary configurator now correctly shows included optional benefits as selected when an employee opens a contract offer. This prevents confusion during offer review by ensuring Yes/No benefit choices reflect what is already part of the offer.
Original PR description
Issue: When an employee opens the salary configurator for a contract offer, optional benefits configured with Yes/No radio choices (such as Medical Insurance) fail to pre-select 'Yes' even when the benefit is already included in the offer. Steps to Reproduce: 1. Go to Salary Configurator and open a contract offer that has an active benefit 2. Observe that 'Yes' is not selected for the benefit on initial load. Fix: Ensure that when a contract offer includes a benefit, the salary configurator automatically defaults the radio selection to `Yes`. task-6392064 Forward-Port-Of: odoo/enterprise#127544
Sendcloud return labels now avoid printing the customer's house number twice in the origin address. This improves label accuracy and helps prevent confusion during return shipments.
Original PR description
Issue ----- On return labels, the house number of the origin address (so the customer) is printed twice. Steps to reproduce ----- - Setup sendcloud - Select a return service - Enable "Generate Return Label" - Create a delivery using sendcloud - Validate the delviery > The return label has the house number printed twice Cause ----- For the origin address shown on labels, Sendcloud prints both the address line and the house number. There doesn't seem to be any parsing made on the address line to extract the house number. For the WH -> Customer label, the "from" address is taken directly from the Sendcloud account's configuration. For the Customer -> WH return, we provide it in the `from_` fields of the request. Note that, when including the house number on the address line in Sendcloud, the issue is also present. ----- Ticket: opw-6405054 Forward-Port-Of: odoo/enterprise#127855 Forward-Port-Of: odoo/enterprise#126250
Studio approval rules now handle empty rule conditions consistently, so they apply to the intended records instead of being interpreted ambiguously. This reduces the risk of approvals being skipped or applied incorrectly when no specific condition is set.
Original PR description
Before this commit, there was an ambiguity with the usage of filtered_domain ie ``` self.assertTrue(record.filtered_domain(False)) self.assertFalse(record.filtered_domain(Domain(False))) ``` This is because in that case the API of filtered_domain was not respected After this commit, there is no ambiguity as we cast to a Domain the value we obtain from the rule: - False or None: all records should be impacted by the rule => Domain(True) - otherwise, let the domain do its job opw-6431607 Forward-Port-Of: odoo/enterprise#128160 Forward-Port-Of: odoo/enterprise#127676
The Belgian salary configurator now calculates wages consistently when employees use mobility budgets and extra-legal leave. This prevents small mismatches between the target employer cost and the gross salary shown to users.
Original PR description
Forward-Port-Of: odoo/enterprise#112723
Project Forecast no longer shows the Time Management section in project settings unless the Timesheets app is installed. This avoids displaying irrelevant settings and keeps project configuration clearer for users who do not use timesheets.
Original PR description
**Steps to reproduce:** - Install the project_forecast module. - Go to Projects -> Open the settings of any project (create one if none exist) -> Settings. - You will see the Time Management section. **Issue:** The project_forecast module was forcefully setting the invisible attribute of group_time_managment to 0. This caused the group to remain visible at all times, even when the Timesheets app was not installed. **Fix:** Remove the forced attribute setting from the project_project_view. The visibility is already properly managed by the hr_timesheet module, and project_forecast does not depend on timesheet_grid or hr_timesheet. task-6195716 Forward-Port-Of: odoo/enterprise#128159 Forward-Port-Of: odoo/enterprise#121454
This fix prevents an automated field service planning test from failing because of a conflict with another planning module. It keeps validation focused on the intended behavior, improving build reliability without changing user-facing functionality.
Original PR description
On runbot, the `test_onchange_break_time_after_removing_dates` test was failing during the "all" build due to the `planning_slot_check_datetimes_set_or_plannable_slot` SQL constraint introduced by the sale_planning module. The test previously used `odoo.tests.Form` as a context manager, which implicitly triggered a database save and flushed the dateless test shift to PostgreSQL. Can resolved this by instantiating the Form in memory to validate the frontend `@api.depends` logic without triggering the cross-module database constraint. runbot-6463625 Forward-Port-Of: odoo/enterprise#127859
Closing days now appear immediately after being added from the appointment schedule views, keeping teams’ availability information up to date. The add button is limited to appointment screens and now only offers closing day types that match the current scheduling setup, reducing confusion and data entry mistakes.
Original PR description
Fix some issues with the closing day feature rendering: - The closing day is not appearing in the gantt view after being created using the gantt "Add closing day" button. Re-fetching the gantt data after the closing day record creation to make sure the view is up-to-date. - The "Add closing day" button is visible from the calendar app but it should only be visible from appointment. As the calendar controller view is inherited in extension, the button was visible both from calendar and from appointment. Only displaying the button if we're in the appointment views. - In the appointment gantt, calendar and list views, making sure the "Add closing day" button only allows creating a leave of the same type as the currently opened views. In other word, hide the leave type 'resources' in the 'users' based views and the other way around. Task-6426018
The timesheet assistant now handles inactive periods more reliably when building work activity suggestions. This prevents breaks from being missed or overwritten, helping users and managers see a more accurate view of recorded time.
Original PR description
## Previous Behavior Before this Commit 1. When key and non‑key events were merged to build the final suggestion timeline, key events always took priority over AFK events, even when the key event was…
## Previous Behavior Before this Commit 1. When key and non‑key events were merged to build the final suggestion timeline, key events always took priority over AFK events, even when the key event was not “always active.” This caused AFK events to be incorrectly overridden. 2. During event normalization, certain events were lost entirely, resulting in important events not being counted. 3. When merging two event timelines, zero‑duration gaps were treated as valid, preventing proper merging of surrounding events. 4. ActivityWatch sometimes produced empty gaps instead of AFK events, causing breaks to go unrecorded. ## New Expected Behavior After this Commit 1. Events now follow the updated priority system: a. Always‑active key events b. Always‑active non‑key events c. Non‑key AFK events d. Other key events e. Other non‑key events 2. Events are now shortened or split so that the latest event always has priority, while minimizing unnecessary event removal. 3. Zero‑duration gaps are skipped when merging event lists. 4. Any gap larger than 3 minutes, between the first and last event and containing no events is automatically filled with an AFK event. ## Additional Notes Because point 4 introduces additional AFK events, several tests were updated to reflect the new behavior. task-[6455412](https://www.odoo.com/odoo/project/4105/tasks/6455412) Forward-Port-Of: odoo/enterprise#127811
This update ensures all eligible contract salary benefit fields can be selected, including country-specific fields that were previously excluded. It also fixes an error that could occur when saving the public field setting, improving reliability for HR salary package configuration.
Original PR description
1- The benefit fields related to the hr.version have a domain that limits them to the whitelisted fields used to copy values from a template, which does not always include benefit fields. The…
1- The benefit fields related to the hr.version have a domain that limits them to the whitelisted fields used to copy values from a template, which does not always include benefit fields. The advantage of the whitelist is that it factored in for the allowed countries, so instead of duplicating this logic to benefit fields and implementing it in every l10n, we can check which module the field comes from.
example:
The field [`company_car_total_depreciated_cost`](https://github.com/odoo/enterprise/blob/ce691cd6aaacfb86cd866698d2fcc3fe930912cb/l10n_be_hr_payroll_fleet/models/hr_version.py#L62) cannot be selected as `res_field_id` when it should be possible as we see in the [data](https://github.com/odoo/enterprise/blob/ce691cd6aaacfb86cd866698d2fcc3fe930912cb/l10n_be_hr_contract_salary/data/hr_contract_salary_benefit_data.xml#L6), it is not whitelisted because we dont want to copy its value from a template.
2- Another fix is the inverse of the public field, there's a traceback because the selection field is always converted to a string and cannot be used to browse as is.
```py
File "/data/build/enterprise/hr_contract_salary/models/hr_contract_salary_benefit.py", line 238, in _inverse_res_field_public
record.res_field_id = self.sudo().env['ir.model.fields'].browse(record.res_field_public)
^^^^^^^^^^^^^^^^^^^
File "/data/build/odoo/odoo/orm/fields.py", line 1890, in __set__
write_value = self.convert_to_write(value, records)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/build/odoo/odoo/orm/fields_relational.py", line 387, in convert_to_write
return value.id
^^^^^^^^
File "/data/build/odoo/odoo/orm/fields_misc.py", line 115, in __get__
raise ValueError("Expected singleton: %s" % record) from None
ValueError: Expected singleton: ir.model.fields('1', '7', '3', '8', '4')
```
Forward-Port-Of: odoo/enterprise#128275
Forward-Port-Of: odoo/enterprise#127743A test setup for accounting reports now uses the correct PDF generation mock, avoiding crashes during automated validation. This helps keep reporting workflows reliably tested without needing a real PDF engine in the test environment.
Original PR description
Description of the issue this commit addresses: Commit d3b4294a56a5ead3f5ea60714eccdba586f72ef0 changed `_run_wkhtmltopdf` into a subprocess wrapper. A later FW, 64f46c270e062df94d4ce37e3065b3e0a0ba68db, did not account for that change and continued mocking the method with PDF bytes. When the wkhtmltopdf path is reached, reading from those bytes crashes. --- Desired behavior after this commit is merged: This commit mocks `_run_pdf_engine_without_processing`, whose contract is to return PDF bytes, so the tour can validate without invoking a real PDF engine. --- runbot-[944189](https://runbot.odoo.com/odoo/error/944189)
Vendor payment files now include the state or province and second address line when generating ISO 20022 bank transfer files. This helps prevent banks, especially in North America, from rejecting payments because beneficiary address details are incomplete.
Original PR description
The PstlAdr block written into pain.001 files never contains the partner's state/province nor the second street line, even when they are set on the record: _get_all_addr() now returns them, but…
The PstlAdr block written into pain.001 files never contains the partner's state/province nor the second street line, even when they are set on the record: _get_all_addr() now returns them, but _get_PstlAdr() also needs to write them out. Some North American banks reject wire transfers whose beneficiary address lacks the state/province, so those payments fail regardless of how complete the vendor record is. Emit CtrySubDvsn when the address has a state, before Ctry as required by the element order of the PostalAddress schema, and append street2 to the street address line. Steps to reproduce: - Install Accounting and enable a generic ISO 20022 payment method on a bank journal - Create a vendor located in the US or Canada with a complete address, including the state and a second street line - Register a vendor payment, add it to a batch and generate the pain.001 file - The creditor PstlAdr has no state/province, and its street line only carries the first street field: the street2 part is dropped Requires odoo/odoo#282518, which makes _get_all_addr() return the state and street2. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/enterprise#128021 Forward-Port-Of: odoo/enterprise#127958
14 changes
Security fixes and vulnerability patches
Payroll-related updates, such as wage or yearly cost changes, are now hidden from employee chatter for users who do not have payroll access. This helps prevent sensitive compensation information from being exposed to people who should not see it.
Original PR description
[IMP] hr_payroll: restrict access on version change messages Backport of https://github.com/odoo-dev/enterprise/commit/081177657696826be69e8f4756ae7af51994f2b8 https://github.com/odoo/enterprise/pull/119350 Tracking messages on hr.employee (e.g. wage/yearly cost changes) were visible in the chatter to any user with access to the employee, even without payroll rights. Payroll-sensitive tracking values are now posted under a dedicated subtype, hidden from the chatter for users outside hr_payroll.group_hr_payroll_user. Task-6460201
Enhancements to existing features
The Dutch payroll module now includes the 2026 income tax rate values for residents. This helps ensure payroll calculations stay aligned with the latest statutory requirements for Dutch employees.
Original PR description
Added 2026 values for the residents' income tax rates rule parameter. task-6462877 Forward-Port-Of: odoo/enterprise#127556
Resolved issues and error corrections
A failing automated test was adjusted so it validates the intended planning behavior without accidentally saving incomplete test data. This helps keep build checks reliable and reduces false failures during quality validation.
Original PR description
On runbot, the `test_onchange_break_time_after_removing_dates` test was failing during the "all" build due to the `planning_slot_check_datetimes_set_or_plannable_slot` SQL constraint introduced by the sale_planning module. The test previously used `odoo.tests.Form` as a context manager, which implicitly triggered a database save and flushed the dateless test shift to PostgreSQL. Can resolved this by instantiating the Form in memory to validate the frontend `@api.depends` logic without triggering the cross-module database constraint. runbot-6463625
Project settings no longer show the Time Management section when the Timesheets app is not installed. This avoids confusing users with options that are not available and keeps the project setup screen aligned with installed apps.
Original PR description
**Steps to reproduce:** - Install the project_forecast module. - Go to Projects -> Open the settings of any project (create one if none exist) -> Settings. - You will see the Time Management section. **Issue:** The project_forecast module was forcefully setting the invisible attribute of group_time_managment to 0. This caused the group to remain visible at all times, even when the Timesheets app was not installed. **Fix:** Remove the forced attribute setting from the project_project_view. The visibility is already properly managed by the hr_timesheet module, and project_forecast does not depend on timesheet_grid or hr_timesheet. task-6195716 Forward-Port-Of: odoo/enterprise#121454
A leftover “Request Appraisals” action that caused an error has been removed. Employees can still request appraisals in bulk through the existing “Launch Campaign” option, which correctly handles selected employees.
Original PR description
#### Description of the issue/feature this PR addresses: The "Request Appraisals" server action on hr.employee calls model._create_multi_appraisals(), a method that no longer exists. Running it…
#### Description of the issue/feature this PR addresses: The "Request Appraisals" server action on hr.employee calls model._create_multi_appraisals(), a method that no longer exists. Running it raises AttributeError: 'hr.employee' object has no attribute '_create_multi_appraisals'. #### Current behavior before PR: Commit 8845eb2ac29 replaced the multi-appraisal flow with hr.appraisal.campaign.wizard: it deleted _create_multi_appraisals and repointed the employee list header button to action_open_appraisal_campaign_wizard, but left the action_create_multi_appraisals record in hr_appraisal/views/hr_employee_views.xml. Its code is now the only reference to the deleted method, so the action crashes whenever it is run. #### Desired behavior after PR is merged: The dangling action is gone. Requesting appraisals for several employees at once is done with the "Launch Campaign" button already present in the Employees list view; action_open_appraisal_campaign_wizard reads active_ids when active_model is hr.employee and pre-fills the selected employees. Nothing references the removed xml id, and the record is not noupdate, so _process_end removes it from existing databases on update; no migration script is required. Verified on a 19.0 database: with the orphan record loaded, updating hr_appraisal with this change deletes it. opw-6408609 Forward-Port-Of: odoo/enterprise#127983 Forward-Port-Of: odoo/enterprise#125630
Fixed an issue where switching to a pivot view through the AI agent could cause the view to crash or open without selected measures. The update waits for the pivot view to finish loading before applying AI changes and keeps default measures when none are requested, improving reliability for users working with AI-assisted reporting.
Original PR description
When the AI agent switched from another view to a pivot view, the pivot view could crash or open without any active measures. The AI controller patch applies the agent's adjustments upon receiving…
When the AI agent switched from another view to a pivot view, the pivot view could crash or open without any active measures. The AI controller patch applies the agent's adjustments upon receiving the `APPLY_AI_ADJUST_MODEL` bus event. However, the event could be processed while the pivot model was still executing `_loadData()`. In that case, the following sequence occurred: * `_loadData()` started and awaited. * The controller patch was executed. * The patch called `toggleMeasures()`. * `toggleMeasures()` waited for `_loadData()` to complete. * `_loadData()` finished and updated the metadata with the available measures. * `toggleMeasures()` resumed and wrote back the metadata snapshot it had taken before waiting. Since `toggleMeasures()` operates on a snapshot of the metadata, the measures populated by `_loadData()` were lost when the snapshot replaced the current metadata, leaving the pivot model without its `measures` metadata and causing the view to crash. Prevent this race condition by waiting for the pivot model initialization to complete before applying the AI adjustments. Also preserve the default active measures when the AI agent does not explicitly request any measures instead of clearing them and opening an empty pivot view. task-6384368 Forward-Port-Of: odoo/enterprise#125897
Return shipping labels generated through Sendcloud now avoid printing the customer's house number twice. This keeps customer address details clearer on return labels and reduces confusion during returns processing.
Original PR description
Issue ----- On return labels, the house number of the origin address (so the customer) is printed twice. Steps to reproduce ----- - Setup sendcloud - Select a return service - Enable "Generate Return Label" - Create a delivery using sendcloud - Validate the delviery > The return label has the house number printed twice Cause ----- For the origin address shown on labels, Sendcloud prints both the address line and the house number. There doesn't seem to be any parsing made on the address line to extract the house number. For the WH -> Customer label, the "from" address is taken directly from the Sendcloud account's configuration. For the Customer -> WH return, we provide it in the `from_` fields of the request. Note that, when including the house number on the address line in Sendcloud, the issue is also present. ----- Ticket: opw-6405054 Forward-Port-Of: odoo/enterprise#127855 Forward-Port-Of: odoo/enterprise#126250
The returns Kanban view now supports using the up and down arrow keys to move through return selections. This prevents an error that could interrupt users when navigating returns with the keyboard, making the workflow smoother and more reliable.
Original PR description
In returns kanban view, a traceback occurs when pressing down. Fix this by adding the support for up/down keyboard navigation for returns selection. task-6281033 Forward-Port-Of: odoo/enterprise#128135 Forward-Port-Of: odoo/enterprise#125164
Salary contract benefits can now use all relevant contract benefit fields, including fields provided by local payroll modules. This prevents missing options and fixes an error that could occur when saving the public field selection.
Original PR description
1- The benefit fields related to the hr.version have a domain that limits them to the whitelisted fields used to copy values from a template, which does not always include benefit fields. The…
1- The benefit fields related to the hr.version have a domain that limits them to the whitelisted fields used to copy values from a template, which does not always include benefit fields. The advantage of the whitelist is that it factored in for the allowed countries, so instead of duplicating this logic to benefit fields and implementing it in every l10n, we can check which module the field comes from.
example:
The field [`company_car_total_depreciated_cost`](https://github.com/odoo/enterprise/blob/ce691cd6aaacfb86cd866698d2fcc3fe930912cb/l10n_be_hr_payroll_fleet/models/hr_version.py#L62) cannot be selected as `res_field_id` when it should be possible as we see in the [data](https://github.com/odoo/enterprise/blob/ce691cd6aaacfb86cd866698d2fcc3fe930912cb/l10n_be_hr_contract_salary/data/hr_contract_salary_benefit_data.xml#L6), it is not whitelisted because we dont want to copy its value from a template.
2- Another fix is the inverse of the public field, there's a traceback because the selection field is always converted to a string and cannot be used to browse as is.
```py
File "/data/build/enterprise/hr_contract_salary/models/hr_contract_salary_benefit.py", line 238, in _inverse_res_field_public
record.res_field_id = self.sudo().env['ir.model.fields'].browse(record.res_field_public)
^^^^^^^^^^^^^^^^^^^
File "/data/build/odoo/odoo/orm/fields.py", line 1890, in __set__
write_value = self.convert_to_write(value, records)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/build/odoo/odoo/orm/fields_relational.py", line 387, in convert_to_write
return value.id
^^^^^^^^
File "/data/build/odoo/odoo/orm/fields_misc.py", line 115, in __get__
raise ValueError("Expected singleton: %s" % record) from None
ValueError: Expected singleton: ir.model.fields('1', '7', '3', '8', '4')
```
Forward-Port-Of: odoo/enterprise#127743Fixed an issue that could prevent customers from opening their cart after a rental product order was changed into a regular sales order. The cart now only shows rental period details when the order still has an active rental period, avoiding a checkout-blocking error.
Original PR description
Currently, an error occurs when a user adds a rental product to the cart, opens the corresponding sales order, removes the rental period, and then opens the cart again. Steps to replicate: - Install…
Currently, an error occurs when a user adds a rental product to the cart, opens the corresponding sales order, removes the rental period, and then opens the cart again. Steps to replicate: - Install `website_sale_renting` with demo. - Open website > shop > add the product named `Projector`. - Click Ecommerce in the menu bar > Orders . - Remove the `Confirmed` filter > Click on the top order (should be containing the projector product.) - Remove the `Rental Period` and go to the cart. Error: ``` QWebError: Error while rendering the template: AttributeError: 'bool' object has no attribute 'time' Template: website_sale.shorter_cart_summary ``` Cause: - When the user removes the rental period (`rental_start_date` and `rental_end_date`), both fields are set to `False`. When the cart is opened again, these values trigger the error in [line]. - Since the rental period has been removed from the order, the order is converted to a regular Sales Order (see [PR] and its [task]). Therefore, the Rental Period should no longer be displayed. Solution: - Use `is_rental_order` to determine whether to render the rental period instead of `has_rentable_lines`, since `has_rentable_lines `only checks whether the product is rentable [1], which is determined by the product's `rental_periodicity` [2]. - `is_rental_order` is a better check here because it indicates whether the rental period is actually defined on the order [3]. [line]: https://github.com/odoo/enterprise/blob/7c80c9ffa9e7812267f2ac285e3a3fc5ca501814/website_sale_renting/views/templates.xml#L207 [task]: https://www.odoo.com/odoo/all-tasks/6003684 [PR]: https://github.com/odoo/enterprise/pull/106381/commits/56ec41d81f7536f047a1586a12ea6f6e8414b844 [1]: https://github.com/odoo/enterprise/blob/f23ef9c604d8ce6be152d5e5bf6f72bd68b31451/sale_renting/models/sale_order.py#L140-L143 [2]: https://github.com/odoo/enterprise/blob/f23ef9c604d8ce6be152d5e5bf6f72bd68b31451/sale_renting/models/sale_order_line.py#L61-L64 [3]: https://github.com/odoo/enterprise/blob/f23ef9c604d8ce6be152d5e5bf6f72bd68b31451/sale_renting/models/sale_order.py#L135-L138 sentry-7663524549
Users now see a helpful message if the Belgian POS blackbox self-order module is missing, including guidance on how to install it. This prevents confusing technical errors when opening the point of sale and helps teams resolve the setup issue faster.
Original PR description
Replace the bare ValidationError with a user-friendly UserError that explains how to install the required 'l10n_be_pos_blackbox_self_order' module. Task-6388185
Fixed an issue where a customer manually assigned to a planning shift could be removed when the worker signed in or completed the shift. This keeps shift customer information stable even when the shift is linked to a sales order.
Original PR description
Before this commit, when `sale_planning` module is installed after `planning_field_service` and the user sets a customer onto a shift, the customer could be removed when the user signs in or complete the shift. This issue is because `sale_planning` module defined `partner_id` field as a related field `related="sale_order_id.partner"` and `planning_field_service` module stores the field and so the field will always follows the partner set on the SO linked even if the user sets a customer on the shift. This commit removes the related attribute to replace it by a compute and a search method to have the exact same behavior but the search method will be short-circuited if the partner_id field is stored. task-5264800 Forward-Port-Of: odoo/enterprise#122034
DIN 5008 business documents now consistently show dates in the expected day.month.year format for Germany, Austria, and Switzerland, regardless of the user's language setting. This prevents confusing or non-compliant date displays on invoices, quotations, purchase orders, follow-ups, and field service documents.
Original PR description
* = din5008_account_followup, din5008_industry_fsm **Steps to reproduce:** * Install the **Germany - Accounting** (`l10n_de`) module (which pulls in `l10n_din5008`) * Set the document layout to **DIN…
* = din5008_account_followup, din5008_industry_fsm
**Steps to reproduce:**
* Install the **Germany - Accounting** (`l10n_de`) module (which pulls in `l10n_din5008`)
* Set the document layout to **DIN 5008** and generate any PDF report (invoice, quotation, purchase order, etc.).
**Observed behavior (date format):**
* All dates in the information block (Invoice Date, Due Date, Delivery Date, Order Date, etc.) are rendered in `yyyy-mm-dd` format instead of the expected `dd.MM.yyyy` format used in DE, AT, and CH.
**Cause (date format):**
* All `t-options="{'widget': 'date'}"` directives across the DIN 5008 template family rely on the active user's language locale for date formatting. If the user language is not `de_DE`, dates render in the locale's default format (e.g. `yyyy-mm-dd` for `en_US`).
**Fix (date format):**
* Add `'format': 'dd.MM.yyyy'` explicitly to all `t-options` date widgets across all DIN 5008 report templates (`l10n_din5008`, `l10n_din5008_sale`, `l10n_din5008_purchase`, `l10n_din5008_sale_subscription`, `l10n_din5008_repair`, `l10n_din5008_account_followup`, `l10n_din5008_industry_fsm`).
* This is correct for all three countries using DIN 5008 (DE, AT, CH), which all follow the `dd.MM.yyyy` convention.
opw-6392649
Forward-Port-Of: odoo/enterprise#128209
Forward-Port-Of: odoo/enterprise#126006ISO 20022 payment files now include the beneficiary's state or province and second address line when available. This helps prevent banks, especially in North America, from rejecting vendor wire transfers because required address details are missing.
Original PR description
The PstlAdr block written into pain.001 files never contains the partner's state/province nor the second street line, even when they are set on the record: _get_all_addr() now returns them, but…
The PstlAdr block written into pain.001 files never contains the partner's state/province nor the second street line, even when they are set on the record: _get_all_addr() now returns them, but _get_PstlAdr() also needs to write them out. Some North American banks reject wire transfers whose beneficiary address lacks the state/province, so those payments fail regardless of how complete the vendor record is. Emit CtrySubDvsn when the address has a state, before Ctry as required by the element order of the PostalAddress schema, and append street2 to the street address line. Steps to reproduce: - Install Accounting and enable a generic ISO 20022 payment method on a bank journal - Create a vendor located in the US or Canada with a complete address, including the state and a second street line - Register a vendor payment, add it to a batch and generate the pain.001 file - The creditor PstlAdr has no state/province, and its street line only carries the first street field: the street2 part is dropped Requires odoo/odoo#282518, which makes _get_all_addr() return the state and street2. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/enterprise#128021 Forward-Port-Of: odoo/enterprise#127958
15 changes
Enhancements to existing features
The Dutch payroll module now includes the 2026 resident income tax rate values. This helps payroll calculations stay aligned with upcoming Netherlands tax requirements.
Original PR description
Added 2026 values for the residents' income tax rates rule parameter. task-6462877 Forward-Port-Of: odoo/enterprise#127556
Resolved issues and error corrections
A leftover “Request Appraisals” action that no longer worked has been removed. Users should use the existing “Launch Campaign” option to request appraisals for multiple employees, avoiding an error when starting the process.
Original PR description
#### Description of the issue/feature this PR addresses: The "Request Appraisals" server action on hr.employee calls model._create_multi_appraisals(), a method that no longer exists. Running it…
#### Description of the issue/feature this PR addresses: The "Request Appraisals" server action on hr.employee calls model._create_multi_appraisals(), a method that no longer exists. Running it raises AttributeError: 'hr.employee' object has no attribute '_create_multi_appraisals'. #### Current behavior before PR: Commit 8845eb2ac29 replaced the multi-appraisal flow with hr.appraisal.campaign.wizard: it deleted _create_multi_appraisals and repointed the employee list header button to action_open_appraisal_campaign_wizard, but left the action_create_multi_appraisals record in hr_appraisal/views/hr_employee_views.xml. Its code is now the only reference to the deleted method, so the action crashes whenever it is run. #### Desired behavior after PR is merged: The dangling action is gone. Requesting appraisals for several employees at once is done with the "Launch Campaign" button already present in the Employees list view; action_open_appraisal_campaign_wizard reads active_ids when active_model is hr.employee and pre-fills the selected employees. Nothing references the removed xml id, and the record is not noupdate, so _process_end removes it from existing databases on update; no migration script is required. Verified on a 19.0 database: with the orphan record loaded, updating hr_appraisal with this change deletes it. opw-6408609 Forward-Port-Of: odoo/enterprise#125630
The returns kanban view now supports using the up and down arrow keys to move through return selections. This prevents an error that previously appeared when users pressed the down arrow, making the workflow smoother and more reliable.
Original PR description
In returns kanban view, a traceback occurs when pressing down. Fix this by adding the support for up/down keyboard navigation for returns selection. task-6281033 Forward-Port-Of: odoo/enterprise#125164
When payroll users include additional unpaid payslips in a payment file, those payslips are now correctly marked as paid after confirming the payment. This prevents mismatches between generated bank payment files and payroll records, reducing manual follow-up and reconciliation errors.
Original PR description
Steps to reproduce: - Open the payment report wizard on a payslip or a pay run - Tick "Include Unpaid" and keep the extra payslips selected - Generate the SEPA file, then click "Mark as Paid" Issue: the extra payslips listed in the file stay in state "validated". Cause: mark_as_paid() paid payslip_ids, while the file is built from unpaid_payslips. Fix: pay the payslips that are actually listed in the file. Task 6428919
Fixed an issue where the customer selected on a field service shift could be removed when an employee signed in or completed the shift. This ensures shift customer information stays accurate even when linked sales orders are involved.
Original PR description
Before this commit, when `sale_planning` module is installed after `planning_field_service` and the user sets a customer onto a shift, the customer could be removed when the user signs in or complete the shift. This issue is because `sale_planning` module defined `partner_id` field as a related field `related="sale_order_id.partner"` and `planning_field_service` module stores the field and so the field will always follows the partner set on the SO linked even if the user sets a customer on the shift. This commit removes the related attribute to replace it by a compute and a search method to have the exact same behavior but the search method will be short-circuited if the partner_id field is stored. task-5264800
Swiss payroll now counts flexible employee absences using the calendar dates selected by the user, avoiding an extra day caused by timezone conversion. This prevents one-day accident leave from being treated as two days, helping keep regular wages and accident salary calculations accurate.
Original PR description
Issue: Swiss payslips count one extra absence day for employees without a working schedule. A one day accident leave can therefore be prorated as two days, reducing the regular wage and overstating…
Issue: Swiss payslips count one extra absence day for employees without a working schedule. A one day accident leave can therefore be prorated as two days, reducing the regular wage and overstating the accident salary. Steps to reproduce: * Install Swiss Payroll. * Configure a monthly employee without a working schedule. * Assign one day of accident time off. * Generate the payslip for that month. Cause: The Swiss wage computation derives absence boundaries from the date portion of the leave's UTC datetimes: https://github.com/odoo/enterprise/blob/16c29e1bab34b5bcb2b001477d928ec5eb294a97/l10n_ch_hr_payroll/models/hr_payslip.py#L313-L330 A fully flexible employee's full day leave starts at local midnight. In timezones ahead of UTC, that start is stored on the previous UTC date, so the inclusive calendar day computation adds an extra day. Solution: We need to use the requested time off dates for both payslip range filtering and absence proration. These fields preserve the calendar days selected by the user independently of timezone conversion, while leaving the UTC datetimes and half-day handling unchanged. opw-6435086 Forward-Port-Of: odoo/enterprise#127513
The timesheet timer in the systray now keeps running until midnight in the user's own timezone, instead of stopping when the server reaches UTC midnight. This prevents employees in earlier timezones from losing visibility of an active timer before their workday ends.
Original PR description
**Problem:** The running timesheet timer in the systray stops when the Odoo server clock passes UTC midnight, even though it is still the same day in the user's own timezone. **Steps to reproduce:**…
**Problem:** The running timesheet timer in the systray stops when the Odoo server clock passes UTC midnight, even though it is still the same day in the user's own timezone. **Steps to reproduce:** 1. Set the user's timezone to one behind UTC (e.g. America/Guadeloupe, UTC-4). 2. Start a timesheet timer while it is before local midnight but after the server has passed UTC midnight (e.g. 20:00 local = 00:00 UTC). 3. Look at the running timer in the systray. **Current behavior:** At server (UTC) midnight the running timer disappears and its ongoing count is lost. **Expected behavior:** The timer keeps running until the user's own local midnight, regardless of the server timezone. **Cause of the issue:** The systray controller derives its reference day from `date.today()`, which returns the server's (UTC) local date. The running timer's timesheet is created dated the user's local day (`hr_timesheet` uses `fields.Date.context_today`). Once the server crosses UTC midnight, `date.today()` advances to the next day while the user's local day has not, so `timesheet_systray_user_data` (searching `date == today`) and `get_timer_start_time` (searching `date` within today's bounds) no longer match the running timesheet, and the systray reports no running timer. **Fix:** Deriving the reference day from `fields.Date.context_today` aligns the systray's notion of "today" with the timezone the timesheet was recorded in, so the timer is tied to the user's local day rather than the server's. This keeps recording and retrieval consistent, since the timesheet is already dated with the user-local day on creation. opw-6343442
The product catalog opened from Field Service tasks now gives more space to the unit of measure column. This improves readability and aligns the Enterprise catalog view with the related Community update.
Original PR description
Steps to produce: --- - Install `Field service` module. - Create a task and open it. - From the task open the catalog from smart button. Update the Product Catalog UI to match the Community PR changes. community PR: https://github.com/odoo/odoo/pull/267118 opw-6253382 --- Forward-Port-Of: odoo/enterprise#127996 Forward-Port-Of: odoo/enterprise#121139
Return shipping labels created through Sendcloud now avoid printing the customer's house number twice. This makes labels clearer and helps prevent address confusion during returns.
Original PR description
Issue ----- On return labels, the house number of the origin address (so the customer) is printed twice. Steps to reproduce ----- - Setup sendcloud - Select a return service - Enable "Generate Return Label" - Create a delivery using sendcloud - Validate the delviery > The return label has the house number printed twice Cause ----- For the origin address shown on labels, Sendcloud prints both the address line and the house number. There doesn't seem to be any parsing made on the address line to extract the house number. For the WH -> Customer label, the "from" address is taken directly from the Sendcloud account's configuration. For the Customer -> WH return, we provide it in the `from_` fields of the request. Note that, when including the house number on the address line in Sendcloud, the issue is also present. ----- Ticket: opw-6405054 Forward-Port-Of: odoo/enterprise#127855 Forward-Port-Of: odoo/enterprise#126250
Invoice extraction now compares scanned IBANs with partner bank accounts after removing spaces and punctuation on both sides. This helps the system correctly identify matching bank accounts even when saved IBANs include formatting characters, reducing missed matches during invoice processing.
Original PR description
When looking for a matching IBAN, we were searching on the `acc_number` field, which can contain spaces or special characters (dots, dashes, etc). But the OCR always returns the IBAN in a sanitized format, without any space or special characters, so it should be compared against the sanitized IBAN of the partners. task-none (issue found by chance) Forward-Port-Of: odoo/enterprise#127775
Validation errors on Indian employee contracts now reflect the employee's actual pay schedule instead of always referring to a monthly wage. This reduces confusion when allowances exceed the wage for employees paid on different schedules.
Original PR description
**Steps to reproduce:** - Create an indian employee. - Put total allowance `(basic salary + HRA + standard ALW + Perf bonus + travel ALW) > wage` - We will get validation error in employee stating that allowance sum can't be greater than wage. **Before:** - We were always showing monthly wage in the validation error, which was confusing to the end user. **After:** - We will use field `version.shedule_pay` to show dynamic validation error message. Task: [6449791](https://www.odoo.com/odoo/project/1251/tasks/6449791)
Employee-related Gantt views now apply search filters consistently, so results better match what users selected in the search bar. These views also default to grouping by employee and hide unsupported grouping options, reducing confusing or incomplete displays.
Original PR description
`user_domain` (in the context) is supposed to contain the domain defined by the user (in the search bar). It was not the case for all gantt views, and produced inconsistent results, as that domain is…
`user_domain` (in the context) is supposed to contain the domain defined by the user (in the search bar). It was not the case for all gantt views, and produced inconsistent results, as that domain is used to know when to display the employees without leaves/attendances. The PR fixes that issue by creating the `HrGanttModel` class, that takes care of defining the `user_domain` correctly. This class also disables the *Group By* menu, and defaults to grouping by employees. This was decided for the following reasons: - All gantt views inheriting this class would group by employee - Grouping by other fields would already not work in some cases - It's very difficult to add employees without records if the gantt is grouped by multiple fields at once Affected `_get_gantt_data()` functions have been adapted accordingly The access models (the ones defined in the `access.csv` files) are implied, and thus never passed as a parameter to `get_gantt_data()`, so we need to also manually add them when converting the model to the related field used in `groupby` task-5502544
The salary calculator now correctly treats entered values as a simulation, avoiding unintended updates to an employee's existing draft payslip. This prevents required fields from being cleared and helps payroll users run salary simulations reliably.
Original PR description
Steps:- 1. Navigate to Payroll->Employees menu->Salary Calculator 2. Select Employee who already have a draft payslip. 3. You will see all the fields will get emptied and give "Missing required fields". Root cause:- Opening the salary simulator temporarily writes the simulated values onto the employee's record. If that employee already had a draft payslip, this write also refreshed that payslip behind the scenes, even though the payslip had nothing to do with the simulation. Fix:- Mark the simulation clearly as a simulation so it no longer refreshes the employee's existing payslip. task-6392171 Forward-Port-Of: odoo/enterprise#127223
Contract salary benefits now show all eligible benefit fields, including country-specific fields that were previously hidden. This prevents configuration errors and avoids a crash when saving the public field selection.
Original PR description
1- The benefit fields related to the hr.version have a domain that limits them to the whitelisted fields used to copy values from a template, which does not always include benefit fields. The…
1- The benefit fields related to the hr.version have a domain that limits them to the whitelisted fields used to copy values from a template, which does not always include benefit fields. The advantage of the whitelist is that it factored in for the allowed countries, so instead of duplicating this logic to benefit fields and implementing it in every l10n, we can check which module the field comes from.
example:
The field [`company_car_total_depreciated_cost`](https://github.com/odoo/enterprise/blob/ce691cd6aaacfb86cd866698d2fcc3fe930912cb/l10n_be_hr_payroll_fleet/models/hr_version.py#L62) cannot be selected as `res_field_id` when it should be possible as we see in the [data](https://github.com/odoo/enterprise/blob/ce691cd6aaacfb86cd866698d2fcc3fe930912cb/l10n_be_hr_contract_salary/data/hr_contract_salary_benefit_data.xml#L6), it is not whitelisted because we dont want to copy its value from a template.
2- Another fix is the inverse of the public field, there's a traceback because the selection field is always converted to a string and cannot be used to browse as is.
```py
File "/data/build/enterprise/hr_contract_salary/models/hr_contract_salary_benefit.py", line 238, in _inverse_res_field_public
record.res_field_id = self.sudo().env['ir.model.fields'].browse(record.res_field_public)
^^^^^^^^^^^^^^^^^^^
File "/data/build/odoo/odoo/orm/fields.py", line 1890, in __set__
write_value = self.convert_to_write(value, records)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/build/odoo/odoo/orm/fields_relational.py", line 387, in convert_to_write
return value.id
^^^^^^^^
File "/data/build/odoo/odoo/orm/fields_misc.py", line 115, in __get__
raise ValueError("Expected singleton: %s" % record) from None
ValueError: Expected singleton: ir.model.fields('1', '7', '3', '8', '4')
```
Forward-Port-Of: odoo/enterprise#127743This fix ensures pivot views finish loading before AI-driven adjustments are applied. Users switching to pivot views through the AI agent should no longer see crashes or empty reports when no measures are explicitly requested.
Original PR description
When the AI agent switched from another view to a pivot view, the pivot view could crash or open without any active measures. The AI controller patch applies the agent's adjustments upon receiving…
When the AI agent switched from another view to a pivot view, the pivot view could crash or open without any active measures. The AI controller patch applies the agent's adjustments upon receiving the `APPLY_AI_ADJUST_MODEL` bus event. However, the event could be processed while the pivot model was still executing `_loadData()`. In that case, the following sequence occurred: * `_loadData()` started and awaited. * The controller patch was executed. * The patch called `toggleMeasures()`. * `toggleMeasures()` waited for `_loadData()` to complete. * `_loadData()` finished and updated the metadata with the available measures. * `toggleMeasures()` resumed and wrote back the metadata snapshot it had taken before waiting. Since `toggleMeasures()` operates on a snapshot of the metadata, the measures populated by `_loadData()` were lost when the snapshot replaced the current metadata, leaving the pivot model without its `measures` metadata and causing the view to crash. Prevent this race condition by waiting for the pivot model initialization to complete before applying the AI adjustments. Also preserve the default active measures when the AI agent does not explicitly request any measures instead of clearing them and opening an empty pivot view. task-6384368 Forward-Port-Of: odoo/enterprise#125897
6 changes
Resolved issues and error corrections
This fix prevents Mexican payroll processing from crashing when a company does not have a VAT number entered. It allows payslip checks to continue normally for companies with incomplete tax identifier information.
Original PR description
`res.company.vat` is not required and can be `False`. Guard the `len()` call so `_issue_mx_warnings` doesn't crash on payslips for companies without a VAT set.
```py
File "/home/odoo/src/enterprise/saas-19.3/hr_payroll/models/hr_payslip.py", line 1936, in _compute_issues
issues = generate_issue(slip, context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/enterprise/saas-19.3/l10n_mx_hr_payroll_account_edi/models/hr_payslip.py", line 235, in _issue_mx_warnings
if not slip.company_id.l10n_mx_curp and slip._l10n_mx_is_curp_needed():
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/enterprise/saas-19.3/l10n_mx_hr_payroll_account_edi/models/hr_payslip.py", line 325, in _l10n_mx_is_curp_needed
or len(self.company_id.vat) == 13
^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: object of type 'bool' has no len()
```The AI assistant now waits for pivot reports to finish loading before applying its changes, preventing crashes when switching views. If the AI does not specify measures, the pivot keeps its default measures instead of opening empty.
Original PR description
When the AI agent switched from another view to a pivot view, the pivot view could crash or open without any active measures. The AI controller patch applies the agent's adjustments upon receiving…
When the AI agent switched from another view to a pivot view, the pivot view could crash or open without any active measures. The AI controller patch applies the agent's adjustments upon receiving the `APPLY_AI_ADJUST_MODEL` bus event. However, the event could be processed while the pivot model was still executing `_loadData()`. In that case, the following sequence occurred: * `_loadData()` started and awaited. * The controller patch was executed. * The patch called `toggleMeasures()`. * `toggleMeasures()` waited for `_loadData()` to complete. * `_loadData()` finished and updated the metadata with the available measures. * `toggleMeasures()` resumed and wrote back the metadata snapshot it had taken before waiting. Since `toggleMeasures()` operates on a snapshot of the metadata, the measures populated by `_loadData()` were lost when the snapshot replaced the current metadata, leaving the pivot model without its `measures` metadata and causing the view to crash. Prevent this race condition by waiting for the pivot model initialization to complete before applying the AI adjustments. Also preserve the default active measures when the AI agent does not explicitly request any measures instead of clearing them and opening an empty pivot view. task-6384368 Forward-Port-Of: odoo/enterprise#125897
French VAT reports now only include electronic payment instructions when VAT is actually owed. This prevents refund requests from being rejected by the French tax authority due to an invalid payment block, while leaving normal VAT payment submissions unchanged.
Original PR description
`_prepare_edi_vals` always called `_get_formatted_payment_values()`, adding an EDI-Paiement (telereglement) block to the T-IDENTIF of the 3310CA3, regardless of whether the company owes VAT or is in…
`_prepare_edi_vals` always called `_get_formatted_payment_values()`, adding an EDI-Paiement (telereglement) block to the T-IDENTIF of the 3310CA3, regardless of whether the company owes VAT or is in a credit position. Steps to reproduce: - French company in a VAT credit position, requesting a refund. - Fill a bank account line, the account to receive the refund and send the VAT report to the DGFiP. Current behaviour: The DGFiP returns a negative acknowledgement on the CA3 interchange: "Telereglement 1 rejete: Montant telereglement absent ou invalide. Code erreur : 018", even though the declaration itself is accepted. The wizard's bank account lines are reused for two opposite purposes: the account to debit when VAT is due, and the account to credit when a refund is asked. `_get_formatted_payment_values()` builds a payment order from them unconditionally, so a telereglement for the credit amount is emitted in the refund case. A telereglement is invalid when no VAT is due, hence error 018. A return nets to either a payment or a credit, never both, so the two cases are mutually exclusive. This commit guards the call with `self.is_vat_due`, so the telereglement is only generated when the company actually owes VAT. The VAT-due flow is unchanged. opw-6275695 Forward-Port-Of: odoo/enterprise#120840
Invoice scanning now compares detected bank account numbers with a cleaned version of partner IBANs, ignoring spaces and punctuation. This helps match suppliers' bank details more accurately when scanned invoices use a standardized IBAN format.
Original PR description
When looking for a matching IBAN, we were searching on the `acc_number` field, which can contain spaces or special characters (dots, dashes, etc). But the OCR always returns the IBAN in a sanitized format, without any space or special characters, so it should be compared against the sanitized IBAN of the partners. task-none (issue found by chance) Forward-Port-Of: odoo/enterprise#127775
DIN 5008 PDF reports now consistently show dates in the expected day.month.year format for Germany, Austria, and Switzerland, regardless of the user’s language settings. This prevents customer-facing documents such as invoices, quotes, purchase orders, and service reports from displaying confusing or non-localized dates.
Original PR description
* = din5008_account_followup, din5008_industry_fsm **Steps to reproduce:** * Install the **Germany - Accounting** (`l10n_de`) module (which pulls in `l10n_din5008`) * Set the document layout to **DIN…
* = din5008_account_followup, din5008_industry_fsm
**Steps to reproduce:**
* Install the **Germany - Accounting** (`l10n_de`) module (which pulls in `l10n_din5008`)
* Set the document layout to **DIN 5008** and generate any PDF report (invoice, quotation, purchase order, etc.).
**Observed behavior (date format):**
* All dates in the information block (Invoice Date, Due Date, Delivery Date, Order Date, etc.) are rendered in `yyyy-mm-dd` format instead of the expected `dd.MM.yyyy` format used in DE, AT, and CH.
**Cause (date format):**
* All `t-options="{'widget': 'date'}"` directives across the DIN 5008 template family rely on the active user's language locale for date formatting. If the user language is not `de_DE`, dates render in the locale's default format (e.g. `yyyy-mm-dd` for `en_US`).
**Fix (date format):**
* Add `'format': 'dd.MM.yyyy'` explicitly to all `t-options` date widgets across all DIN 5008 report templates (`l10n_din5008`, `l10n_din5008_sale`, `l10n_din5008_purchase`, `l10n_din5008_sale_subscription`, `l10n_din5008_repair`, `l10n_din5008_account_followup`, `l10n_din5008_industry_fsm`).
* This is correct for all three countries using DIN 5008 (DE, AT, CH), which all follow the `dd.MM.yyyy` convention.
opw-6392649
Forward-Port-Of: odoo/enterprise#128209
Forward-Port-Of: odoo/enterprise#126006ISO 20022 payment files now include a beneficiary's state or province and second address line when those details are present. This helps prevent banks, especially in North America, from rejecting vendor wire payments because of incomplete address information.
Original PR description
The PstlAdr block written into pain.001 files never contains the partner's state/province nor the second street line, even when they are set on the record: _get_all_addr() now returns them, but…
The PstlAdr block written into pain.001 files never contains the partner's state/province nor the second street line, even when they are set on the record: _get_all_addr() now returns them, but _get_PstlAdr() also needs to write them out. Some North American banks reject wire transfers whose beneficiary address lacks the state/province, so those payments fail regardless of how complete the vendor record is. Emit CtrySubDvsn when the address has a state, before Ctry as required by the element order of the PostalAddress schema, and append street2 to the street address line. Steps to reproduce: - Install Accounting and enable a generic ISO 20022 payment method on a bank journal - Create a vendor located in the US or Canada with a complete address, including the state and a second street line - Register a vendor payment, add it to a batch and generate the pain.001 file - The creditor PstlAdr has no state/province, and its street line only carries the first street field: the street2 part is dropped Requires odoo/odoo#282518, which makes _get_all_addr() return the state and street2. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/enterprise#128021 Forward-Port-Of: odoo/enterprise#127958
8 changes
Enhancements to existing features
The Amazon sales integration now uses Amazon's newer Orders API ahead of the old version being retired in 2027. This keeps order imports working reliably and should improve synchronization efficiency by retrieving order details in a more consolidated way.
Original PR description
Amazon has announced the deprecation of the Orders v0 API, with a removal date of March 27, 2027. In this commit, we migrate to the new v2026-01-01 API. This new version restructures how order data is queried and delivered, shifting from a multi-request architecture to a nested consolidated payload. This optimizes our sync performance by eliminating the N+1 query problem when fetching order items. Key changes: - Operation Consolidation: `getOrders` is replaced by `searchOrders`. Because Amazon now embeds orderItems directly inside each order object natively, we remove our secondary item-fetching loops. - Financial aggregation: Item prices, taxes, shipping, and discounts are no longer flat fields on the item but are centralized into a `proceeds` object. - Replacing of deprecated flags. - Reorganization of order-related fields. task-5972714 Forward-Port-Of: odoo/enterprise#126879 Forward-Port-Of: odoo/enterprise#114591
Resolved issues and error corrections
Vendor bank account matching during invoice OCR now compares normalized IBAN values instead of the raw account text. This helps invoices match the correct partner bank account even when stored IBANs include spaces, dots, or dashes.
Original PR description
When looking for a matching IBAN, we were searching on the `acc_number` field, which can contain spaces or special characters (dots, dashes, etc). But the OCR always returns the IBAN in a sanitized format, without any space or special characters, so it should be compared against the sanitized IBAN of the partners. task-none (issue found by chance) Forward-Port-Of: odoo/enterprise#127775
Fixed an issue where accounting users could be blocked from exporting Datev attachment zip files when an invoice’s main image attachment came from a log note. This ensures permitted users can complete German Datev exports without needing administrator access.
Original PR description
### Issue: When an invoice has an image as its main attachment added via a log note, any non-admin user who did not create the attachment gets an `AccessError` when exporting the Datev ATCH zip ###…
### Issue: When an invoice has an image as its main attachment added via a log note, any non-admin user who did not create the attachment gets an `AccessError` when exporting the Datev ATCH zip ### Cause: Since commit `e7c93e5a6f`, attachments uploaded via certain flows can be "orphaned" — their `res_model` is set to `False` and `res_id` to `0` via `_fix_attachments_on_record_from_files_data` This allows the attachment to appear in the chatter without being linked to the move's attachment list However, `_message_set_main_attachment_id` can still set such an orphaned attachment as `message_main_attachment_id` When a user without system rights tries to read it, the ORM access check uses `res_model=False` and `res_id=0`, which does not match the move the user has access to, raising an `AccessError` ### Steps to reproduce: - Install `l10n_de_reports` and switch to the DE company - Create and confirm an Invoice (any lines, any customer) - Add a Log Note with an image - Set Demo user's Accounting rights to `Invoicing & Banks` - Log in as Demo - Open the General Ledger - In the cog menu, choose `Datev ATCH (zip)` Before the fix, an `AccessError` is raised opw-6397804
Split PDF pages now appear in a predictable order in Documents. This prevents confusion when multiple pages are created at the same time and previously appeared randomly in the kanban view.
Original PR description
steps: - upload a multi-page pdf - split all the pages -> they now show in a random order The issue is that the current documents are sorted by create_date desc, but the split creates all the different documents at the same time so they are sorted in the order they happen to be on the disk. We now add a sort by id to act as a tie-breaker. opw-6176840 Forward-Port-Of: odoo/enterprise#126683 Forward-Port-Of: odoo/enterprise#117255
PDF documents using the DIN 5008 layout now show dates in the expected day.month.year format for Germany, Austria, and Switzerland, regardless of the user's language settings. This prevents invoices, quotes, purchase orders, follow-up reports, and field service worksheets from displaying confusing or non-compliant date formats.
Original PR description
* = din5008_account_followup, din5008_industry_fsm **Steps to reproduce:** * Install the **Germany - Accounting** (`l10n_de`) module (which pulls in `l10n_din5008`) * Set the document layout to **DIN…
* = din5008_account_followup, din5008_industry_fsm
**Steps to reproduce:**
* Install the **Germany - Accounting** (`l10n_de`) module (which pulls in `l10n_din5008`)
* Set the document layout to **DIN 5008** and generate any PDF report (invoice, quotation, purchase order, etc.).
**Observed behavior (date format):**
* All dates in the information block (Invoice Date, Due Date, Delivery Date, Order Date, etc.) are rendered in `yyyy-mm-dd` format instead of the expected `dd.MM.yyyy` format used in DE, AT, and CH.
**Cause (date format):**
* All `t-options="{'widget': 'date'}"` directives across the DIN 5008 template family rely on the active user's language locale for date formatting. If the user language is not `de_DE`, dates render in the locale's default format (e.g. `yyyy-mm-dd` for `en_US`).
**Fix (date format):**
* Add `'format': 'dd.MM.yyyy'` explicitly to all `t-options` date widgets across all DIN 5008 report templates (`l10n_din5008`, `l10n_din5008_sale`, `l10n_din5008_purchase`, `l10n_din5008_sale_subscription`, `l10n_din5008_repair`, `l10n_din5008_account_followup`, `l10n_din5008_industry_fsm`).
* This is correct for all three countries using DIN 5008 (DE, AT, CH), which all follow the `dd.MM.yyyy` convention.
opw-6392649
Forward-Port-Of: odoo/enterprise#128209
Forward-Port-Of: odoo/enterprise#126006The project Sales button now shows all linked sales, including rental orders, so the list matches the number shown on the project. This prevents users from missing rental-related orders when reviewing project sales activity.
Original PR description
Steps to Reproduce --- 1. Install sale_renting_project. 2. Create a Project linked to 1 standard Sales Order and 1 Rental Order. 3. Observe the "Sales" stat button counts 2 Sales. 4. Click the stat button. Only the standard Sales Order is displayed. Issue --- In saas-18.4, the project Sales stat button calls action_view_sos without the from_embedded_action context key. As a result, _get_sale_orders_domain applies the non-rental filter by default, causing rental orders to be excluded from the action even though they are included in the displayed counter. Expected Behavior --- The Sales stat button should display all orders linked to the project, including both standard and rental orders, matching its total counter. Fix --- Return the base project domain unmodified when from_embedded_action is not set in the context. task-6140201
ISO 20022 payment files now include the beneficiary's state or province and second street address line when available. This helps prevent North American banks from rejecting wire transfers because of incomplete beneficiary address details.
Original PR description
The PstlAdr block written into pain.001 files never contains the partner's state/province nor the second street line, even when they are set on the record: _get_all_addr() now returns them, but…
The PstlAdr block written into pain.001 files never contains the partner's state/province nor the second street line, even when they are set on the record: _get_all_addr() now returns them, but _get_PstlAdr() also needs to write them out. Some North American banks reject wire transfers whose beneficiary address lacks the state/province, so those payments fail regardless of how complete the vendor record is. Emit CtrySubDvsn when the address has a state, before Ctry as required by the element order of the PostalAddress schema, and append street2 to the street address line. Steps to reproduce: - Install Accounting and enable a generic ISO 20022 payment method on a bank journal - Create a vendor located in the US or Canada with a complete address, including the state and a second street line - Register a vendor payment, add it to a batch and generate the pain.001 file - The creditor PstlAdr has no state/province, and its street line only carries the first street field: the street2 part is dropped Requires odoo/odoo#282518, which makes _get_all_addr() return the state and street2. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/enterprise#128021 Forward-Port-Of: odoo/enterprise#127958
The asset app now detects when an asset's account no longer matches the account used on its related journal entry, which could cause differences between depreciation schedules and balance sheets. Users can see a warning and update affected assets in bulk, helping keep financial reports aligned.
Original PR description
… and balance sheet When a user changes the account on a journal item that was linked to an asset, it creates discrepancy between the Depreciation Schedule, which relies on the asset's Fixed Asset Account, and the Balance Sheet that relies on the journal item's account. We created a warning when such a discrepancy is detected, and allowed the user to mass edit the assets to change those accounts. task-4314894 Forward-Port-Of: odoo/enterprise#122241
7 changes
Resolved issues and error corrections
Fixed an issue where embedded document actions linked to accounting journals could be incorrectly removed during automated cleanup when users were working in a different company. This helps preserve configured document shortcuts in multi-company setups.
Original PR description
Step to reproduce: - You must have at least 2 companies with an account Journal - Create a New Journal Entry actions (child or parent) - Embed it to a folder - Set your company on a different one than the journal's one - Run the Garbage collector cron (Base: Auto-vacuum internal data) - The embed action has been removed The cause of this is that in the `_get_base_server_actions_domain` method in `documents_account` module there is a check on company to avoid using/running the actions when not in the right company. But the garbage collector don't need to have this check. Task-6147618
Invoice scanning now compares detected bank account numbers against a cleaned version of partner IBANs, ignoring spaces and punctuation. This helps match vendors more reliably when stored bank details use different formatting.
Original PR description
When looking for a matching IBAN, we were searching on the `acc_number` field, which can contain spaces or special characters (dots, dashes, etc). But the OCR always returns the IBAN in a sanitized format, without any space or special characters, so it should be compared against the sanitized IBAN of the partners. task-none (issue found by chance) Forward-Port-Of: odoo/enterprise#127775
Opening spreadsheet version history now uses the correct type of database transaction. This avoids an unnecessary retry when contributor information is updated, making the action smoother and more reliable for users.
Original PR description
The get_spreadsheet_history method is marked as readonly, causing RPC requests to use a read-only transaction. However, retrieving the metadata of a document spreadsheet updates its spreadsheet contributors. Opening the version history consequently attempts an UPDATE in a read-only transaction and forces the request to be retried with a read-write cursor. Remove the readonly decorator so the request uses a read-write cursor directly. Task-6176364
Payment XML files now use uppercase encoding labels to satisfy stricter bank validation rules. This helps avoid warnings or rejections from providers such as SIX in Switzerland, while keeping the file content unchanged.
Original PR description
The W3C recommendations for XML state that the encoding defined for an XML document should not be case-sensitive. However, some banking providers (SIX for Switzerland) are stricter and may throw warnings or errors if upper-case is not used. https://www.w3.org/TR/2008/REC-xml-20081126/#NT-EncodingDecl opw-4948708 Forward-Port-Of: odoo/enterprise#125947 Forward-Port-Of: odoo/enterprise#125807
This fix prevents a signer from being prompted to sign a later step before earlier required signers have completed their part. It keeps document signing aligned with the configured order, reducing mistakes and ensuring approval workflows are followed correctly.
Original PR description
### Steps to Reproduce: 1. Create a sign request and have 3 total signers (User, Customer, Employee) 2. Enable Signing Order and make the order as follows: (1) User, (2) Customer, (3) Employee But…
### Steps to Reproduce: 1. Create a sign request and have 3 total signers (User, Customer, Employee) 2. Enable Signing Order and make the order as follows: (1) User, (2) Customer, (3) Employee But make the User and Employee the same contact 3. Send and sign the request > Notice that (1) is able to sign for (3) immediately after, (2) has not signed yet. ### Description of the issue/feature this PR addresses: **Issue:** The signing order is ignored when the same user has to sign multiple times on a document, even if it is configured for a different person to sign in between. This happens because all signature request items are initialized in the 'sent' state upon creation, rather than strictly advancing based on the order. As a result, the system prematurely allows users to sign out of order and prompts them with their next turn too early. **Solution:** To resolve this, the controller was updated to include an `is_mail_sent = True` domain filter. This ensures that the UI's post-sign popup only displays documents where it is explicitly the user's active turn, rather than prompting a premature sign. ### Current behavior before PR: Users are able to sign prematurely, and the system will disregard the configured signing order. ### Desired behavior after PR: Users will only be prompted and able to sign a document when it is explicitly their turn, per the `mail_sent_order`. This way, documents are signed in order. opw-6417327 Forward-Port-Of: odoo/enterprise#125573
This fixes a problem where Android users could not download images or files from the Odoo mobile app file viewer. Downloads are now passed to the mobile app in a way Android supports, improving the experience for mobile users.
Original PR description
Steps to reproduce: - send an image in a Discuss channel - click the image to open the file viewer - click the download button => Android shows "The Odoo Mobile Apps only supports file downloads…
Steps to reproduce: - send an image in a Discuss channel - click the image to open the file viewer - click the download button => Android shows "The Odoo Mobile Apps only supports file downloads using the HTTP protocol." downloadFile()'s GET-by-URL case fetches the URL via XHR, then saves the Blob response by clicking a hidden <a download> anchor on a blob: URL. Android's DownloadManager only accepts http(s) URLs, so it rejects that blob: URL instead of downloading anything. Patch downloadFile._download to hand the URL directly to a new mobile.methods.saveFile bridge method when available, the same way download._download already delegates to mobile.methods.downloadFile. Blob/string content downloads aren't handled here — the only such call site (spreadsheet JSON export) is debug-mode only, so this is left as a console.warn for now. Related to odoo/odoo@e83fd8c08c879f5e262d39f24edcb3f81238ea82 Code made by Claude Changes supervised by HUVW Forward-Port-Of: odoo/enterprise#127693
The asset module now detects when an asset’s account no longer matches the account used in related journal entries, which could cause differences between depreciation schedules and the balance sheet. Users are warned about the issue and can update affected assets in bulk, helping keep financial reports consistent.
Original PR description
… and balance sheet When a user changes the account on a journal item that was linked to an asset, it creates discrepancy between the Depreciation Schedule, which relies on the asset's Fixed Asset Account, and the Balance Sheet that relies on the journal item's account. We created a warning when such a discrepancy is detected, and allowed the user to mass edit the assets to change those accounts. task-4314894 Forward-Port-Of: odoo/enterprise#122241
5 changes
Enhancements to existing features
Belgian payroll now lets companies calculate meal vouchers based on eligible hours worked instead of only eligible days. The existing day-based method remains the default, while voucher amounts and employee contributions are shown per voucher for clearer payroll setup.
Original PR description
Before this commit: - Meal vouchers were only computed from eligible worked days. - The configured amounts were displayed per worked day. After this commit: - A Calculation Method allows choosing between Days and Hours. - Days remains the default and keeps the existing behavior. - Hours divides eligible worked hours by the daily hours of the employee's Working Hours Reference and rounds the resulting quantity. - Voucher values and employee contributions are displayed per voucher. - Fixed meal vouchers for company executives remain unchanged. Task-6427957
This update refreshes icons across manufacturing lifecycle, work order, quality, and barcode screens with a newer, cleaner visual style. The change improves visual clarity and consistency without altering business processes or functionality.
Original PR description
Icons were updated with the newer, more clean ones. task-6443360
Belgian payroll now warns HR teams when DIMONA declarations do not match employee contract version dates, categories, or joint committees. This helps catch payroll compliance issues earlier and reduces the risk of incorrect employment declarations.
Original PR description
add warning when DIMONA declarations don't cover the same date ranges as contract versions or when dimona category doesn't match between periods and versions. Task Id: 5951989
Resolved issues and error corrections
Website-generated pages and blog posts now reuse the same image file instead of creating duplicate media entries. Cropped or customized image versions are also kept out of the general media picker, making it easier for users to find the right images.
Original PR description
Images referenced by several pages (or several blog posts) could get copied into extra ir.attachment records instead of being reused, because `_get_wg_attachment` iterated over the raw filenames list without deduplicating it first, and marked a filename "used" as soon as it was assigned once. The same visually-identical image would then end up cluttering the website media picker under several ids. Also stop customized/cropped image variants from showing up as regular library images in the media picker, since they're only meant to back a specific placement.
Payroll users can now manually adjust computed payslip lines without triggering an error when recalculating. This keeps draft payslip editing reliable and prevents disruption during payroll preparation.
Original PR description
Steps to reproduce:
- Open a draft payslip.
- Go to the Salary Computation tab.
- Manually change the amount of a computed line and compute.
- A traceback is raised (UnboundLocalError).
Reason:
When a line is manually modified, the standard computation is skipped and `explanation_info` is never defined.
Solution:
Initialize `explanation_info = {}` for manually modified lines.
Task-64625264 changes
Resolved issues and error corrections
This fix prevents an error when creating DHL return labels for sales orders that include incoterms. The system now sends the correct incoterm code to DHL, allowing delivery confirmation and return label generation to complete normally.
Original PR description
Issue ----- When "return" is enabled, users get a traceback if the SO has incoterms. Steps to reproduce ----- - Set up DHL - enable return labels - Create a SO with incoterms & confirm it - Confirm the delivery > Traceback Cause ----- The request sent for the return label contains the incoterm record instead of its' code like in `dhl_rest_send_shipping` https://github.com/odoo/enterprise/blob/f6c94d4ca3ef4211a5ab00bf0b39f6a7675c8f79/delivery_dhl_rest/models/delivery_dhl.py#L371-L372 Which is not JSON serializable ----- Ticket: opw-6430371
When a product fails a quality check, Odoo now correctly sends it to the selected failure location even if that location is a parent of the planned storage location. This prevents failed goods from being incorrectly recorded as available in their normal shelf location, improving inventory accuracy and quality handling.
Original PR description
Currently, when the user fails a quality check where the failure location is the parent of the location specified in the picking, the product is still moved to the picking location instead of the…
Currently, when the user fails a quality check where the failure location is the parent of the location specified in the picking, the product is still moved to the picking location instead of the failure location.
## Steps to replicate:
- Install Quality
- Go to Settings Enable Storage Locations
- Create a location `Dried Rice Shelf` with parent Location as WH/Stock
- Create a tracked product 'Rice'
- Create a Quality control point with following Configuration:
- Title: Moisture test
- Products: Rice
- Operations: Receipt
- Failure Locations: WH/Stock
- Create a receipt for Rice with:
- Destination location: WH/Stock/Dried Rice Shelf
- Demand: 1
- Mark as Todo > Validate > Fail the quality check > Confirm failure location.
- Check On hand quantity for the rice.
## Observed Behavior:
Even though the user failed the quality check, the rice still ended up in the dried rice shelf. This is incorrect behavior; it should have been sent to WH/Stock.
## Root cause:
The issue occurs when the user presses Fail and confirms the failure location by clicking Confirm.
`confirm_fail` is called, which invokes `_move_to_failure_location` at [1] with the failure location. Since the quality check is of type product, this calls `_move_to_failure_location_product` at [2], which sets the move location's destination to `WH/Stock` at [3].
This triggers the inverse method [4] of `location_dest_id` on stock.move, which updates the destination locations of the stock move lines. However, since Dried Rice Shelf (set on the stock move lines) is already a child location of WH/Stock (set on the stock move), the move line destinations are not updated.
After `_move_to_failure_location` completes, [1] returns `action_generate_next_window`. This calls `button_validate` at [5], which triggers `_action_done` on the `picking → stock.move → stock.move.line`. At [6], the quants/product inventory are synchronized using the destination location from the move line, which is still Dried Rice Shelf, causing the issue.
[1]-
https://github.com/odoo/enterprise/blob/36efa7b0674db4203d9229d5b7ed6452d6178d70/quality_control/wizard/quality_check_wizard.py#L97-L100
[2]-
https://github.com/odoo/enterprise/blob/36efa7b0674db4203d9229d5b7ed6452d6178d70/quality_control/models/quality.py#L524-L527
[3]-
https://github.com/odoo/enterprise/blob/36efa7b0674db4203d9229d5b7ed6452d6178d70/quality_control/models/quality.py#L583-L589
[4]-
https://github.com/odoo/odoo/blob/13e9e827dc21103052e8607d16f23c07adeb5634/addons/stock/models/stock_move.py#L246-L252
[5]-
https://github.com/odoo/enterprise/blob/36efa7b0674db4203d9229d5b7ed6452d6178d70/quality_control/wizard/quality_check_wizard.py#L115-L119
[6]-
https://github.com/odoo/odoo/blob/13e9e827dc21103052e8607d16f23c07adeb5634/addons/stock/models/stock_move_line.py#L696-L700
## Solution:
We also update the location on stock move lines when a quality check is completed. This ensures the failure location is set correctly and prevents failed items from being moved to a passing location. This is especially useful when the parent location is a failure location. For example, we move the rice back to WH/Stock, dry it, and then store it back on the shelf.
opw-6393693Peruvian accounting reports now use the exchange rate already saved on each accounting entry instead of recalculating it during report generation. This avoids small rounding differences and helps produce more reliable report figures.
Original PR description
Previously, the `_get_ple_report_data` method computed the currency rate when called. Since the calculation was based on the entry totals, it was prone to rounding errors. This PR makes it use the rate stored in the entry itself. This should lead to more accurate results. opw-6411322 Forward-Port-Of: odoo/enterprise#126882
Uploading a document with AI-Sort enabled could crash the Documents view when the file was automatically moved to another folder. The update now checks that the uploaded document is still available in the current view before refreshing, keeping the user interface stable.
Original PR description
When a user uploads a document and AI-Sort is turned on, the document can get automatically sorted and moved to a different folder, causing the documents view to crash. The reason for the crash is that the file uploader triggers a renderer refresh with a focus on the newly-uploaded record. However, the new record no longer exists in the current view because it was moved to a new folder. To solve this problem, this commit checks for the existence of the record first before triggering the UI update. task-6304506
9 changes
Resolved issues and error corrections
The Thai Sales and Purchase Tax Excel reports now include cash basis VAT in the period when payment is made, matching the Thai Tax Return report. This prevents missing VAT amounts in exported reports and improves consistency for tax filing and reconciliation.
Original PR description
### Current behavior: Cash basis tax amounts correctly appear on the Thai Tax Report in the payment month, but missing in the exported Sales Tax Report (xlsx) ### Expected behavior: Sales Tax Report…
### Current behavior: Cash basis tax amounts correctly appear on the Thai Tax Report in the payment month, but missing in the exported Sales Tax Report (xlsx) ### Expected behavior: Sales Tax Report (xlsx) and Purchase Tax Report (xlsx) should include the same cash basis tax entries as the tax report for the selected period Cash basis tax amounts should appear the same both on the Thai Tax Return Report as well as in the exported Sales Tax Report (xlsx) ### Steps to reproduce: 1. Install Thai localization and l10n_th_reports, enable Cash Basis 2. Set Output VAT 7% exigibility to Based on Payment 3. Create a customer invoice in June, register/reconcile payment in July 4. Open Tax Report (TH) for July and confirm cash basis amounts appear 5. Export Sales Tax Report (xlsx) (inside the wrench icon) 6. Observe that the cash basis VAT is missing from the Excel file ### Cause of the issue: When cash basis is enabled, select tax-tagged move lines using the generic tax report's strict period domain and group Cash Basis Entry lines under their origin move. Reapply the existing sale, purchase, and reversal filters and compute move-line amounts only for on-payment taxes. Preserve the existing accrual behavior. ### Fix: When cash basis is enabled, include original invoices paid in the selected period via their Cash Basis Entries. This PR is backporting the fix from https://github.com/odoo/enterprise/pull/75645. Update should only be from 18.0 to 18.4 opw-6369332
Android users can now download files opened from the Odoo mobile file viewer, such as images shared in Discuss. The fix sends download links to the mobile app in a format Android accepts, avoiding the previous error message and failed download.
Original PR description
Steps to reproduce: - send an image in a Discuss channel - click the image to open the file viewer - click the download button => Android shows "The Odoo Mobile Apps only supports file downloads using the HTTP protocol." downloadFile()'s GET-by-URL case fetches the URL via XHR, then saves the Blob response by clicking a hidden <a download> anchor on a blob: URL. Android's DownloadManager only accepts http(s) URLs, so it rejects that blob: URL instead of downloading anything. Patch downloadFile._download to hand the URL directly to a new mobile.methods.saveFile bridge method when available, the same way download._download already delegates to mobile.methods.downloadFile. Blob/string content downloads aren't handled here — the only such call site (spreadsheet JSON export) is debug-mode only, so this is left as a console.warn for now. Related to odoo/odoo@e83fd8c08c879f5e262d39f24edcb3f81238ea82 Code made by Claude Changes supervised by HUVW
The payment terminal lookup no longer depends on manufacturer information that newer IoT Boxes do not provide. This helps ensure payment terminals can still be found and used correctly with updated IoT hardware.
Original PR description
Newer IoT Boxes don't send the `manufacturer` key, so we need to remove it from the search domain of payment terminals in the db.
This fix helps prevent mismatches between asset depreciation schedules and balance sheet reporting when journal item accounts are changed. Users now receive a warning when a discrepancy is detected and can update affected assets in bulk to keep financial reports aligned.
Original PR description
… and balance sheet When a user changes the account on a journal item that was linked to an asset, it creates discrepancy between the Depreciation Schedule, which relies on the asset's Fixed Asset Account, and the Balance Sheet that relies on the journal item's account. We created a warning when such a discrepancy is detected, and allowed the user to mass edit the assets to change those accounts. task-4314894
Corrects a rounding mismatch in Peruvian electronic invoices, especially for down payments with mixed tax rates. This helps invoices pass local validation and avoids rejection by Peru's electronic invoicing service.
Original PR description
**Steps to reproduce:** - Install Accounting, Sales and l10n_pe_edi - Switch to a Peruvian company (e.g. PE Company) - Create a SO: * Customer: [a Peruvian customer] * Order Lines: | Product |…
**Steps to reproduce:**
- Install Accounting, Sales and l10n_pe_edi
- Switch to a Peruvian company (e.g. PE Company)
- Create a SO:
* Customer: [a Peruvian customer]
* Order Lines:
| Product | Quantity | Unit Price | Taxes |
| ------- | -------- | ---------- | ------- |
| any | 3.00 | 123.50 | VAT 18% |
| any | 2.00 | 27.544216 | 0% Ina |
| any | 1.00 | 43.490867 | 0% Exo |
- Confirm the SO
- Create a 40% down payment
- Confirm the down payment
- Process it to sent it to Peru UBL 2.1
**Issue:**
The following error message is returned by the OSE:
`3272|La base imponible a nivel de línea difiere de lainformación consignada en el comprobante - Detalle: xxx.xxx.xxx ticket : 20260000000000221633458 error: Error en la Linea Nro. :1. : 3272 (nodo: "cac:TaxSubtotal/cbc:TaxableAmount" valor: "148.20")`
**Cause:**
In the XML, one line has 148.19 for "cbc:LineExtensionAmount", but 148.20 for "cac:TaxSubtotal/cbc:TaxableAmount".
The issue is coming from the fact that "base_amount_currency" is used instead of "total_excluded_currency" for the computation of "cac:TaxSubtotal/cbc:TaxableAmount".
**Issue 2:**
When a tax is impacting the base amount of a following tax, its tax amount is not taken into account in "total_excluded_currency".
opw-6235909Fixed an issue where reconnecting a Shopee shop with a different API account did not update the linked account in Odoo. This helps businesses keep Shopee shop connections accurate after re-authorization, avoiding mismatches between shops and credentials.
Original PR description
Context: when a user re-authenticate a shop, they might use different shopee.account (API key). Currently Odoo will not change the shopee.account when they re-auth with another shopee.account. Enable a shopee.shop switches to another shopee.account when we run `create_or_update_shop` function.
Odoo now compares scanned invoice IBANs with cleaned partner bank account numbers, ignoring spaces and punctuation. This helps the invoice extraction process find the correct bank account more reliably when supplier records store IBANs with formatting.
Original PR description
When looking for a matching IBAN, we were searching on the `acc_number` field, which can contain spaces or special characters (dots, dashes, etc). But the OCR always returns the IBAN in a sanitized format, without any space or special characters, so it should be compared against the sanitized IBAN of the partners. task-none (issue found by chance) Forward-Port-Of: odoo/enterprise#127775
German DATEV exports now handle bank settlements involving three different currencies by splitting them through the appropriate clearing account. This prevents export errors and ensures each exported accounting line uses only one foreign currency, while leaving standard transactions unchanged.
Original PR description
Issue: - DATEV does not support multiple foreign currencies on a single journal line. - This can occur when reconciling a bank transaction where: - the payer uses one currency (C1), - the bank…
Issue: - DATEV does not support multiple foreign currencies on a single journal line. - This can occur when reconciling a bank transaction where: - the payer uses one currency (C1), - the bank journal is held in another currency (C2), - the company uses a third currency (C3). - The existing export logic could not represent the bank liquidity and foreign AR/AP currencies separately in such cases. Fix: - Detect 3-currency cases from bank statement transactions and their liquidity line. - Use the DATEV clearing account (1360 SKR03 / 1460 SKR04) to split the transaction into two logical legs: - Bank → Clearing (bank journal currency) - AR/AP → Clearing (payer currency) - Emit the liquidity leg only once when multiple foreign AR/AP lines are reconciled against the same bank transaction. - Keep regular 1- and 2-currency transactions on the existing export path. Impact: - Correctly represents 3-currency bank settlements in DATEV. - Keeps each exported line in a single foreign currency. - Leaves manual entries and payment transactions outside this specific handling, as the scenario is specific to the bank liquidity line. taskID-5457547 Forward-Port-Of: odoo/enterprise#109010
Links added inside spreadsheet cell comments can now be opened with a normal click, as users would expect. This removes a small interaction issue that made it harder to follow shared references or supporting information in spreadsheet discussions.
Original PR description
Current behavior before PR: - Clicking a link in a cell comment did not work. A left click was blocked, while Ctrl+click (or Cmd+click) opened the link in a new tab. - This was caused by `t-on-click.prevent` on the comment thread and popover. It was originally added because the scroller service used the URL hash to scroll to anchors, which was removed in https://github.com/odoo/odoo/commit/711e9c9f24818714129f55283e2df64503d93605 Desired behavior after PR is merged: - `t-on-click.prevent` is removed and links in cell comments can be opened normally with both left click and Ctrl+click (Cmd+click on macOS). Task: [6448651](https://www.odoo.com/odoo/project/2328/tasks/6448651)
4 changes
Resolved issues and error corrections
The Amazon sales connector version was updated after its Orders API migration. This helps customers and partners identify that the module includes the latest internal compatibility change for Amazon's newer API.
Original PR description
In https://github.com/odoo/enterprise/pull/114591, we migrated the Orders API from v0 to v2026-01-01 following Amazon's announce of v0's deprecation. In this commit, we update the version of the module to signal the internal change to the community. task-5972714
WhatsApp messages using templates with many mixed variable types now send values in the correct placeholder order. This prevents customers from receiving messages where details such as names, fields, or custom text appear in the wrong place.
Original PR description
**Issue**:
Sending a WhatsApp template with 10 or more variables can assign values to the wrong placeholders when the body contains mixed variable types, such as free text, field, or user name variables.
Templates containing only free-text variables are not affected.
**Reason**:
Meta consumes template parameters positionally, but for mixed variable types, Odoo built the parameter list using the template variable recordset order.
That order can differ from the numeric placeholder order, notably placing {{10}}, {{11}}, {{12}}... before {{1}}
when sending the message, as the payload parameters are not ordered by their numeric placeholder index.
**Fix:**
Sort body variables by their numeric placeholder index before preparing the Meta payload.
Task-6401501This fix corrects several issues in SAF-T/FAIA reporting, including tax amounts, software version length, currency tax values, and customer/supplier invoice details. It helps Luxembourg and related SAF-T reports better match official validation rules and reduces audit or filing errors.
Original PR description
This is one of many errors fixing the FAIA report, which is the SAF-T report for Luxembourg. See PR #113316 for a list of similar PRs. ### Error 1: Negative tax amounts Auditors from Luxemborg…
This is one of many errors fixing the FAIA report, which is the SAF-T report for Luxembourg. See PR #113316 for a list of similar PRs. ### Error 1: Negative tax amounts Auditors from Luxemborg provided one Odoo user with analysis files of their FAIA xml report. The following discrepancy was present in more than 300 lines: `[TaxInformation/TaxAmount/Amount] # is negative. Only postive values are admitted. The sign is automatically determined by the corresponding CreditAmount (-) Or DebitAmount (+) on the same Line.` This discrepancy was caused by two different scenarios. The first was a negative `unit_price` line, such as a Discount product. The second was a tax with negative and positive repartition lines, such as a tax with xml ID `lu_2015_tax_AP-EC-17`. Luxembourg officials confirmed the following behavior: 1. The TaxInformation/TaxAmount/Amount element must be positive. 2. The TaxInformationTotals/TaxAmount/Amount element may be negative. 3. There may only be one TaxInformationTotals element per TaxCode in an Invoice element. This commit ensures that these conditions are met for the FAIA report. I'm not sure if the TaxInformation changes should also be applied to the base `account_saft saft_report.xml` file. ### Error 2: SoftwareVersion The SoftwareVersion element is limited to 18 characters. The relevant error from a customer's analysis file is below. Error: Value exceeds maxLength of "18". ### Error 3: CurrencyAmount The `account_saft` method `GeneralLedgerCustomHandler._saft_fill_report_tax_details_values()` does not report the amount of tax in foreign currency, instead replacing this value with the amount in company currency. No errors prompted this change; it just seems wrong on its face. ### Error 4: PR #113720 ensured that the TaxType element is always TVA. This means that the TaxType should no longer should be ignored in our example documents. ### Error 5: Schema validation failure The elements Inovice/CustomerInfo and Invoice/SupplierInfo are defined with the element `<xs:choice>` in the XSD file linked below. Only one can be present at any time, not both. https://pfi.public.lu/dam-assets/backup/FAIA/FAIA/XSD_Files.zip. note: currently the link is broken. PR #100749 allowed many parts of SAF-T code to display both customer and supplier data, including these elements. This commit ensures that the elements are mutually exclusive. opw-6344914 [Link](https://www.odoo.com/odoo/project.task/6344914)
This fixes a failure when importing some Chilean electronic supplier invoices that include foreign currency details but omit an optional foreign-currency total. The import now falls back to the standard total, helping affected invoices process successfully through email fetching.
Original PR description
When importing an incoming DTE through the fetchmail server, the total amount is read from the MntTotOtrMnda as soon as a Moneda node is present in the document. Steps to reproduce: - Set up a CL company with a DTE mail server - Fetch a DTE that includes the line-level Moneda node but does not include the header OtraMoneda block, so no MntTotOtrMnda - Run the fetchmail cron and check the logs Issue: The DTE fails to import Analysis: Occurs since https://github.com/odoo-dev/enterprise/commit/5805a92f91411846fdffa245cb047397cfc9b1f3 Moneda is defined at line level while MntTotOtrMnda in the optional header block Encabezado/OtraMoneda. Instead of assuming MntTotOtrMnda is always present whenever the document carries a foreign currency, fall back to the base-currency total MntTotal when it is missing. opw-6432612