Daily updates from Odoo
Navigate
Branch
Tuesday, August 18, 2026
245 changes
24 changes
Enhancements to existing features
Odoo now checks an institution's maximum allowed payment amount before initiating single or batch payments through Odoo/Odoofin. This helps prevent failed payment attempts when a bank or provider enforces transaction limits.
Original PR description
Before trying to initiate payments through Odoo/Odoofin, we should check that the total amount for the (batch) payment does not exceed the maximum payment amount allowed by the institution (some Powens institutions introduced that limit). task-6310729 Forward-Port-Of: odoo/enterprise#127110 Forward-Port-Of: odoo/enterprise#121513
When a Bulgarian VAT return is validated, Odoo now automatically creates and attaches the required monthly SAF-T general ledger, purchase, and sales report files alongside the VAT PDF. This reduces manual export work and helps large Bulgarian companies meet monthly tax reporting obligations more reliably.
Original PR description
Bulgaria made it mandatory for large companies to present a monthly file
to report their VAT to the administration. To streamline that process,
when the VAT return is validated and PDF is added to the attachments,
the monthly General Ledger SAF-T file, the POKUPKI Purchase Report and
PRODAGBI Sale Report are produced and added as well.
Simplify the report file download error wizard's visuals and descriptions to improve readability.
task-6007963
Forward-Port-Of: odoo/enterprise#127736
Forward-Port-Of: odoo/enterprise#118326The employee Gantt view in Manufacturing Work Orders now shows the progress bar again. This helps teams quickly see work progress in the schedule, improving visibility for planning and follow-up.
Original PR description
Since 19.4 the progress bar has been hidden for employee gantt. This commit makes sure it is visible. task-6459190
The Timesheet Assistant now shows clearer Helpdesk ticket suggestions using the actual ticket name, making entries easier to recognize. When users add a suggested ticket, the timesheet form is filled with the right ticket automatically, reducing manual entry and avoiding incorrect grouping of unrelated events.
Original PR description
Before this commit, the Timesheet Assistant displayed static labels for Helpdesk Tickets. Furthermore, when a user clicked "Add" on a ticket suggestion, the Timesheet Inline Form did not auto-populate the ticket name, as the source ID was lost during the grouping phase. Task: 6320652 Forward-Port-Of: odoo/enterprise#121662
Resolved issues and error corrections
The timesheet assistant now handles timesheet data more reliably when entries are removed or were created elsewhere. This helps prevent errors and avoids unintended changes to previously processed assistant events.
Original PR description
## [FIX] timesheet_grid: avoid altering consumedEvents Before this commit, the objects inside consumedEvents attribute are altered because we keep the reference of those objects. This commit avoid altering the consumed events. ## [FIX] timesheet_grid: use recordsByStart instead of records Forward-Port-Of: odoo/enterprise#127698
Starting a timesheet timer from a task now correctly carries over the task's sales order item, so the resulting timesheet remains billable as expected. This prevents missing billing information and ensures the Billable option is visible immediately when relevant.
Original PR description
**Problem:** Starting the timer on a task that is linked to a sale order item produces a timesheet that is not linked to it, and the Billable toggle is missing from the timer. **Steps to reproduce:**…
**Problem:** Starting the timer on a task that is linked to a sale order item produces a timesheet that is not linked to it, and the Billable toggle is missing from the timer. **Steps to reproduce:** 1. Install Timesheets and Sales 2. Open a task whose Sales Order Item is set 3. Start the timer from the Timesheets systray 4. Save it and open the resulting timesheet **Current behavior:** The Sales Order Item is empty. The Billable toggle only appears after removing and re-adding the task in the timer. **Expected behavior:** The timer is billable on the task's sale order item as soon as it is opened. **Cause of the issue:** `_get_timesheet_pre_filled_form_data` returns only `project_id` and `task_id`. The timer form merges that pre-fill over `timesheet_default_values`, which `lazy_session_info` computes once per session from `account.analytic.line.new()` - a record with no project and no task, so `so_line`, `allow_billable` and `has_available_so` are all `False` in it. Because the pre-fill carries none of those keys, they keep the task-independent session values: the timesheet stays unlinked from the sale order item, and the Billable toggle stays hidden since it is displayed from `has_available_so`. **Fix:** The pre-fill endpoint is the only place that knows which task the timer is being opened on, so it is where the task-dependent values have to be resolved. Reading them off a new timesheet built with that project and task keeps the endpoint generic - it returns whatever `_get_aw_timesheet_fields_specification` declares, so the sale fields stay owned by sale_timesheet_enterprise rather than being named in timesheet_grid. Dropping the session defaults instead was rejected: they are also the only source of `date`, `user_id` and `company_id` for the timer record, and removing them makes saving fail on the required Date field. opw-6423577 Forward-Port-Of: odoo/enterprise#127800
Fixed an issue where payroll correction batches could be created under the wrong active company when correcting a paid payslip for an employee in another company. Correction batches now use the company of the payslips they contain and avoid mixing payslips from different companies, improving accuracy in multi-company payroll processing.
Original PR description
Steps to reproduce: - Have an employee in company B, with a paid payslip - Log in with company A active (company B allowed but not selected) - Open the employee's paid payslip and click "Correct" The refund and correction payslips are computed in company B (their company follows the employee), but the pay run created for them by the wizard has no explicit company and falls back to the active company A. Set the pay run's company from the payslips it contains, and group the payslips by company as well as by structure so that a batch never mixes companies. task-6428755 Forward-Port-Of: odoo/enterprise#126064
Odoo now correctly recognizes valid Brazilian NF-e invoice XML files even when the main invoice tag has no attributes. This prevents eligible vendor bills from being silently skipped during import, improving reliability for Brazilian accounting workflows.
Original PR description
### Issue before this commit: Certain valid Brazilian NF-e (electronic invoice) XML files fail to import because the system silently ignores them during the initial EDI recognition phase. ### Steps to reproduce the issue: 1. Download Accounting and l10n_br_edi 2. Go to Vendor > Bills 3. Try to import both xmls in the ticket 4. One of the two will not be imported correctly ### Cause of the issue: https://github.com/odoo/enterprise/blob/3ed1721b702555e96c9774969927f6517e855704/l10n_br_edi/models/account_move.py#L819-L827 This function relies on a strict byte string search for b"<NFe " while it's also correct if the tag is only `<NFe>`. ### Reason to introduce the fix: To make the initial NF-e file recognition more robust and compliant with standard XML namespace rules, ensuring Odoo successfully processes all valid Brazilian invoices regardless of attribute formatting. opw-6402843 Forward-Port-Of: odoo/enterprise#127851 Forward-Port-Of: odoo/enterprise#126881
An internal accounting test was adjusted to match a related platform fix in grouped data reading. This helps keep automated checks reliable without changing day-to-day user workflows.
Original PR description
The fix at https://github.com/odoo/odoo/pull/281911 adds bin_size: tru in the web_read_group. This commit adpats an accounting test as a consequence Forward-Port-Of: odoo/enterprise#127994 Forward-Port-Of: odoo/enterprise#127638
The IoT device list now keeps pagination working when users open a device record. This prevents navigation issues in larger device lists and makes managing IoT devices smoother.
Original PR description
Since #72351, the pagination on IoT devices was broken due to how we were getting to the full device form when clicking on a record. We now change the override to use the existing method from the framework `switchToForm` which handles it better. opw-6058532 Forward-Port-Of: odoo/enterprise#127215 Forward-Port-Of: odoo/enterprise#126486
The timesheet menu has been adjusted to display more cleanly on mobile devices. This makes it easier for users to view and enter timesheet information from phones without dealing with a clunky interface.
Original PR description
In this commit, we improve the display of the timesheet systray in mobile view as it was clunky. task-6332208 Forward-Port-Of: odoo/enterprise#122491
Completed kitchen orders are now removed from the Order Status Display instead of reappearing under "Almost There." This keeps customer-facing order progress accurate and avoids confusion after an order has finished preparation.
Original PR description
Steps to reproduce ------------------ - Open a PoS session, the Kitchen Display, and the Order Status Display. - Create an order and send it to the Kitchen. - Process the order through all the stages until it reaches the final (completed) stage. Issue ----- - Once the order reaches the completed stage, it reappears in the "Almost There" section of the Order Status Display instead of being removed. Cause ----- - The applied domain fetched all kitchen orders, including completed ones. The display logic only distinguishes whether an order is in the second last preparation stage than show it as "Ready", all other orders are shown as "Almost There". As a result, completed orders fall back into the "Almost There" section.. Fix --- - Updated the domain to fetch only active kitchen orders and exclude completed ones from the Order Status Display. Task: 6394865
The Timesheet Assistant now combines selected suggestions that share the same rule template into one more natural description. This avoids repetitive text separated by semicolons, making generated timesheet entries easier to read and understand.
Original PR description
In this task, we improved the Timesheet Assistant by merging descriptions generated from the same rule templates. When multiple suggestions are selected that use the same rule template, the assistant now combines them into a single timesheet description instead of joining with ';' Example: Rule template: `Discussing with $1` Before: Discussing with A; Discussing with B After: Discussing with A and B Task-6348575 Forward-Port-Of: odoo/enterprise#127109 Forward-Port-Of: odoo/enterprise#126157
The payment registration flow now uses the bank account selected in the wizard when none is set directly on the invoice or move. This helps ensure customers can see and use the “Pay Now” option when a valid bank account is available, reducing payment friction.
Original PR description
Before this commit, it could happens that when we don't put a partner_bank_id on the move it self. The "pay now" button was never displayed. It was because partner_bank_ids was only checking the value from the move and not the wizard. Now if there is no partner_bank_id in the wizard.batches then we look at the value in the wizard and we use it. task-6374002 Forward-Port-Of: odoo/enterprise#123759
International DPD shipments through Sendcloud now include the recipient tax number in the required customs details. This prevents delivery validation failures and adds safer fallback values for required customs fields when creating new deliveries.
Original PR description
### This is a revision of #119399 which had to be reverted. Original issue ----- Deliveries cannot be validated using DPD with Sendcloud, users get an error. Steps to reproduce ----- - Set up…
### This is a revision of #119399 which had to be reverted. Original issue ----- Deliveries cannot be validated using DPD with Sendcloud, users get an error. Steps to reproduce ----- - Set up Sendcloud DPD - Create a SO - Interntional customer - Some VAT number - Some product - Add sendcloud delivery - Confirm SO - Validate the linked picking > Error: “The receiver VAT number is missing; please provide it to continue” Issue's cause ----- Tax numbers should be included in the `customs_information` field of the request as per the API https://sendcloud.dev/api/v2/parcels/create-a-parcel-or-parcels#body-one-of-0-parcel-customs-information-tax-numbers For the `vat_label` field, we have to force the language to English in the context because the field is translated by default, but sendcloud only accepts the english names (eg French "TVA" is not accepted, expected value is "VAT"). https://github.com/odoo/odoo/blob/d1d1610332a1596d026fb0a42ec236d1a79c71cc/odoo/addons/base/models/res_country.py#L75 Revert cause ----- The vat_label field is marked for translation (translate=True) https://github.com/odoo/odoo/blob/d1d1610332a1596d026fb0a42ec236d1a79c71cc/odoo/addons/base/models/res_country.py#L75 So if the user has the DB in french for example, we are sending "TVA" instead of "VAT" in the name field. Other issues ----- - We need to provide an actual fallback for `customs_invoice_nr`. As it stands, if we create a new delivery it cannot be validated because Sendcloud doesn't accept for the field to be empty. - Same for `name`, we need to provide an actual fallback. ----- Ticket: opw-6250860 Forward-Port-Of: odoo/enterprise#127425 Forward-Port-Of: odoo/enterprise#124245
Restaurant preparation display cards now show the assigned course name, such as “Main Course,” instead of only a course number when course allocation is enabled. This makes kitchen preparation screens clearer and reduces confusion for staff handling multi-course orders.
Original PR description
Before this commit: ====================== Prep cards showed the course index, like T1 - C2, even when course allocation was enabled. After this commit: ==================== Prep cards show the allocated course name, like T1 - Main Course, when course allocation is enabled. Task-6421309
Fixes a timer issue where task or ticket timers could jump between values, show negative seconds, or track the wrong duration after repeated stop, start, and page reload cycles. This improves confidence in recorded work time by ensuring only the active timer on screen updates the displayed elapsed time.
Original PR description
A running task/ticket timer can sometimes stutter, show negative values, and track the wrong elapsed time. ### Steps to reproduce On a record with a timesheet timer (for example, a Field Service…
A running task/ticket timer can sometimes stutter, show negative values, and track the wrong elapsed time. ### Steps to reproduce On a record with a timesheet timer (for example, a Field Service task): 1. Start the timer and let it run for about 15-20 seconds. 2. Stop it and confirm the dialog. 3. Start it again. This creates a new `timer_start`. 4. Reload the page. The timer starts jumping every second between two different values. As it keeps running, it can even show negative values such as `00:00:-57`. If the problem does not appear right away, repeat steps 2-4 a few times. It usually shows up after a few stop/start/reload cycles. ### Cause The timer shown in the button bar is the `timer_start_field` widget. It starts a `setInterval` that updates a shared `TimerReactive` object once per second. While a form is loading, Odoo renders it several times in a row (for example: a first render, another when the chatter is loaded, and another when the record data comes back from the server). Rendering a form builds all of its fields to produce the display, so each of these renders creates its own `timer_start_field`. Odoo keeps and mounts only the render that ends up on screen; the earlier ones are thrown away before being mounted. The interval is started while the field renders, from the record observer set up in `setup`, before the field is mounted. So the fields that are later thrown away also start an interval. Those intervals keep running for the rest of the session. Each one updates the same shared `TimerReactive` object using the `timer_start` it was created with. As long as every instance has the same `timer_start`, they all write the same value and the problem stays hidden. After the timer is stopped and started again, the old instances keep the old `timer_start` while the mounted one uses the new one. Every second they overwrite each other's value, so the timer jumps between two different elapsed times. When the instance with the newer `timer_start` writes right after one with an older start, it tries to show a smaller elapsed time than what is already there, and the subtraction in `TimerReactive` produces a negative number of seconds. ### Fix Move the per-second timer update into a `useEffect`. The effect only runs after the field is mounted, and Owl automatically cleans it up when the field is unmounted or when `timer_start` or `timer_pause` change. This means fields that are destroyed before they are mounted never start an interval, so only the mounted field updates the shared timer. `onRecordChange` no longer starts or stops the interval. It only updates the displayed timer value to match the current record. opw-6209405 Forward-Port-Of: odoo/enterprise#128071 Forward-Port-Of: odoo/enterprise#126242
Belgian payroll now correctly splits a new long sickness leave after the first 30 days, even when it occurs during a relapse window but is not marked as related to the previous illness. This helps ensure payroll calculations classify sick leave consistently and avoid overcounting the initial sick leave category.
Original PR description
Steps to reproduce: - Create a STO for an employee of more than 30 days -> this period is split in 30 days STO and x days SGS. - Within the relapse period, create a second STO of more than 30 days and leave the relapse field empty (which is fine if the second STO is not related to the first sickness) -> the period should be split after the first 30 days just like the first STO, but it remains an STO for the whole duration. task-6296152
The call debrief timeline now keeps transcript markers aligned with the timeline direction in right-to-left language settings. This provides a more consistent and understandable call review experience for users working in languages such as Arabic or Hebrew.
Original PR description
The call debrief timeline was not properly adapted for RTL layouts. In Community, the timeline is forced to always render in LTR, as it represents the physical progression of time moving forward. To ensure consistency, this commit makes transcript markers within the timeline always render in LTR as well. task-6445011 Requires: - https://github.com/odoo/odoo/pull/280218
Creating appraisal goal templates no longer assigns them to a specific employee by default. This ensures templates created from the library or saved from an employee goal remain visible and reusable in the template list.
Original PR description
Steps to reproduce: - Go to Appraisals > Configuration > Goals > Library and create a goal template. - Or open an employee goal and click "Save as Template". - The created goal template is not visible in the goal template list view. Reason: An employee was set by default on new goal templates upon creation. Solution: Do not assign a default employee when creating a goal template. Task-6443339
The vehicle salary tab now shows the Benefit In Kind amount with the correct yearly label and keeps the label aligned next to the value. This avoids confusion when reviewing vehicle-related salary costs, with no changes to calculations or stored data.
Original PR description
The Benefit In Kind (BIK) amount on the vehicle model salary tab was labelled "/ month" while the underlying value is yearly, mismatching the other fields (CO2 fee, Cost, Total Cost) correctly suffixed. The unit label div was also missing the o_hr_narrow_field class used everywhere else in the module, so the unit label stretched to the far right of the row instead of sitting next to the value. Purely visual fix, no behavior or data change. task-6428222
The Timesheet Assistant now identifies time spent in spreadsheets as spreadsheet work instead of grouping it under Documents. This gives users more accurate timesheet suggestions and reduces manual corrections.
Original PR description
## Behavior Before the Commit When a user spent time working in a spreadsheet, the Timesheet Assistant grouped this time under "Working in Documents". This occurred because "Spreadsheet" is a module rather than an application, and the Assistant only creates suggestions for applications. As a result, it attempted to fall back to an application and frequently defaulted to the "Documents" application. ## New Expected Behavior After this Commit: When a user spends time in a spreadsheet, the Timesheet Assistant now generates a "Working in Spreadsheet" suggestion. This is enabled by a dedicated list of non‑application modules that allows specific modules to be recognized even when they are not applications. task-[6438536](https://www.odoo.com/odoo/project/4105/tasks/6438536) Forward-Port-Of: odoo/enterprise#126390
Users can now add AI-generated images to Documents without encountering a crash. The fix also improves handling for attachments that are not linked to another business record, making document creation more reliable in similar cases.
Original PR description
Bug === Since 907f1029e8abb38c1ddd3fa9493fa866322706ca a crash happen when we generate image with AI, and try to add it to documents. Task-6453430
The scheduled cleanup for AI embeddings now correctly detects records created with deprecated embedding models. This helps ensure outdated AI data is refreshed as intended, reducing maintenance issues without changing the user workflow.
Original PR description
The embedding model deprecation cron starts by searching for chunks that are embedded using a model that hasn't been deprecated. However, due to an optimization in the ORM (optimize_type_selection),…
The embedding model deprecation cron starts by searching for chunks that are embedded using a model that hasn't been deprecated. However, due to an optimization in the ORM (optimize_type_selection), the search doesn't return any result. A domain that uses the 'not in' operator is converted by the optimization to use the 'in' operator and the values are replaced by the difference between the available selection values (self._selection) and the values in the domain. So, given that the selection values of the embedding_model field are the non deprecated embedding models and the search domain of the cron is ['embedding_model', 'not in', non deprecated embedding models] , the final domain will be ['embedding_model', 'in', non deprecated models list - non deprecated models list] = ['embedding_model', 'in', []] which will retrieve nothing from the DB because all records have an embedding_model. The main issue is that the optimization assumes that the available selection values are static. However, an old embedding model can be deprecated and a new one added which makes the optimization fail. So, the selection values are changed to be computed dynamically which will be skipped by the optimization.
25 changes
Enhancements to existing features
When a Bulgarian VAT return is validated, Odoo now automatically generates and attaches the required monthly SAF-T general ledger, purchase, and sales report files alongside the PDF. This helps large companies meet Bulgaria's monthly tax reporting requirements with less manual work, and the related download error messages are easier to understand.
Original PR description
Bulgaria made it mandatory for large companies to present a monthly file
to report their VAT to the administration. To streamline that process,
when the VAT return is validated and PDF is added to the attachments,
the monthly General Ledger SAF-T file, the POKUPKI Purchase Report and
PRODAGBI Sale Report are produced and added as well.
Simplify the report file download error wizard's visuals and descriptions to improve readability.
task-6007963
Forward-Port-Of: odoo/enterprise#127736
Forward-Port-Of: odoo/enterprise#118326Cached website generation requests now schedule a quick follow-up task instead of calling webhooks immediately. This helps ensure notifications happen after the generation work is safely saved, without tying up system workers unnecessarily.
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.
Resolved issues and error corrections
Fixes an issue where task or ticket timers could jump between values, show negative time, or record the wrong elapsed time after repeated stop/start and page reload cycles. This helps users trust the displayed timer and improves timesheet accuracy.
Original PR description
A running task/ticket timer can sometimes stutter, show negative values, and track the wrong elapsed time. ### Steps to reproduce On a record with a timesheet timer (for example, a Field Service…
A running task/ticket timer can sometimes stutter, show negative values, and track the wrong elapsed time. ### Steps to reproduce On a record with a timesheet timer (for example, a Field Service task): 1. Start the timer and let it run for about 15-20 seconds. 2. Stop it and confirm the dialog. 3. Start it again. This creates a new `timer_start`. 4. Reload the page. The timer starts jumping every second between two different values. As it keeps running, it can even show negative values such as `00:00:-57`. If the problem does not appear right away, repeat steps 2-4 a few times. It usually shows up after a few stop/start/reload cycles. ### Cause The timer shown in the button bar is the `timer_start_field` widget. It starts a `setInterval` that updates a shared `TimerReactive` object once per second. While a form is loading, Odoo renders it several times in a row (for example: a first render, another when the chatter is loaded, and another when the record data comes back from the server). Rendering a form builds all of its fields to produce the display, so each of these renders creates its own `timer_start_field`. Odoo keeps and mounts only the render that ends up on screen; the earlier ones are thrown away before being mounted. The interval is started while the field renders, from the record observer set up in `setup`, before the field is mounted. So the fields that are later thrown away also start an interval. Those intervals keep running for the rest of the session. Each one updates the same shared `TimerReactive` object using the `timer_start` it was created with. As long as every instance has the same `timer_start`, they all write the same value and the problem stays hidden. After the timer is stopped and started again, the old instances keep the old `timer_start` while the mounted one uses the new one. Every second they overwrite each other's value, so the timer jumps between two different elapsed times. When the instance with the newer `timer_start` writes right after one with an older start, it tries to show a smaller elapsed time than what is already there, and the subtraction in `TimerReactive` produces a negative number of seconds. ### Fix Move the per-second timer update into a `useEffect`. The effect only runs after the field is mounted, and Owl automatically cleans it up when the field is unmounted or when `timer_start` or `timer_pause` change. This means fields that are destroyed before they are mounted never start an interval, so only the mounted field updates the shared timer. `onRecordChange` no longer starts or stops the interval. It only updates the displayed timer value to match the current record. opw-6209405 Forward-Port-Of: odoo/enterprise#126242
The payment flow now checks the bank account selected in the payment wizard when it is missing from the invoice or move. This helps ensure customers can see and use the “Pay Now” button in more situations, reducing failed or delayed online payment attempts.
Original PR description
Before this commit, it could happens that when we don't put a partner_bank_id on the move it self. The "pay now" button was never displayed. It was because partner_bank_ids was only checking the value from the move and not the wizard. Now if there is no partner_bank_id in the wizard.batches then we look at the value in the wizard and we use it. task-6374002
Odoo now correctly recognizes Brazilian NF-e invoice XML files even when the main invoice tag has no attributes. This prevents valid vendor bill files from being silently skipped during import, helping accounting teams process supplier invoices more reliably.
Original PR description
### Issue before this commit: Certain valid Brazilian NF-e (electronic invoice) XML files fail to import because the system silently ignores them during the initial EDI recognition phase. ### Steps to reproduce the issue: 1. Download Accounting and l10n_br_edi 2. Go to Vendor > Bills 3. Try to import both xmls in the ticket 4. One of the two will not be imported correctly ### Cause of the issue: https://github.com/odoo/enterprise/blob/3ed1721b702555e96c9774969927f6517e855704/l10n_br_edi/models/account_move.py#L819-L827 This function relies on a strict byte string search for b"<NFe " while it's also correct if the tag is only `<NFe>`. ### Reason to introduce the fix: To make the initial NF-e file recognition more robust and compliant with standard XML namespace rules, ensuring Odoo successfully processes all valid Brazilian invoices regardless of attribute formatting. opw-6402843 Forward-Port-Of: odoo/enterprise#127851 Forward-Port-Of: odoo/enterprise#126881
This fix prevents Dutch Digipoort tax return status updates from failing when an old or incomplete status record has no linked closing entry. Other tax return status records can now continue processing normally, improving reliability for Dutch reporting workflows.
Original PR description
The `l10n_nl_reports_sbr_status_info` contains the `l10n_nl_reports_sbr.status.service` class. The class is responsible for fetching the status of sent Digipoort tax returns. The status is then posted as a chatter message to the tax return's closing entry. Issues can arise when one of the status service records is, for whatever reason, missing a closing entry. In such case, the message cannot be posted, resulting in an exception being raised. Since the records are processed in a loop without a try-catch, this causes the whole action to fail. This can lead to one broken record effectively shutting down the whole module's functionality. This PR adds some if-else checks to gracefully handle the case where the closing entry is missing. Related tickets: opw-5901446 and opw-6410082 Forward-Port-Of: odoo/enterprise#127844 Forward-Port-Of: odoo/enterprise#125996
The document tests now verify that GIF thumbnails are created and marked correctly instead of requiring an exact byte-for-byte match. This keeps quality checks aligned with improved GIF resizing behavior and reduces false test failures without changing user-facing functionality.
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#128032
This fix removes ambiguity in how Studio approval rule conditions are interpreted. Approval rules with empty conditions now correctly apply to all relevant records, reducing the risk of inconsistent approval behavior for users.
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#127813 Forward-Port-Of: odoo/enterprise#127676
This update corrects payroll category setup for Belgian HR payroll so salary and reporting data are classified more accurately. It helps reduce payroll validation and declaration errors, particularly for Belgian payroll accounting checks.
Partial time off entries on flexible work schedules now keep their actual duration instead of being treated as a full day. This prevents attendance reports from overstating overtime and ensures the Time Off Gantt view only marks the real leave hours.
Original PR description
Problem: On a flexible working schedule, a time off of a few hours (neither a full nor a half day) was treated as a full day off. The Attendance list then reported the whole day's attendance as…
Problem: On a flexible working schedule, a time off of a few hours (neither a full nor a half day) was treated as a full day off. The Attendance list then reported the whole day's attendance as overtime, and the Time Off gantt grayed out the entire day instead of only the leave's hours. Steps to reproduce: 1. Give an employee a flexible working schedule (e.g. 8h/day) with an overtime ruleset based on the contract's expected hours. 2. Record a 2-hour time off, then an 8-hour attendance on the same day. 3. Observe the attendance reports 8 hours of overtime instead of 2. Current behavior: A partial time off makes the whole day count as extra hours. Expected behavior: Only the hours actually taken off reduce the day's expected hours. Cause: For a flexible schedule, _handle_flexible_leave_interval expands a leave to the whole day. The override already narrows full-day and half-day leaves, but any other number of hours fell through to that full-day expansion. The expanded interval is subtracted from the day's expected hours in _work_intervals_batch, so they drop to zero and every worked hour becomes overtime. Fix: A leave of an arbitrary number of hours should only remove the hours it actually covers, so it keeps its requested interval instead of being stretched to the whole day. opw-6291536 Forward-Port-Of: odoo/enterprise#127449 Forward-Port-Of: odoo/enterprise#123778
The IoT device list now opens device details using the standard application behavior, which restores pagination after it was previously broken. This helps users browse and manage IoT devices reliably across multiple pages.
Original PR description
Since #72351, the pagination on IoT devices was broken due to how we were getting to the full device form when clicking on a record. We now change the override to use the existing method from the framework `switchToForm` which handles it better. opw-6058532 Forward-Port-Of: odoo/enterprise#127215 Forward-Port-Of: odoo/enterprise#126486
Leads created from WhatsApp conversations in Discuss now automatically include the correct customer contact. This prevents sales teams from receiving incomplete leads and reduces manual cleanup after WhatsApp interactions.
Original PR description
### Steps to reproduce: - Install 'whatsapp', and 'crm_livechat' - Configure a WhatsApp channel and receive a message to create a conversation in Discuss - Open the WhatsApp conversation in Discuss -…
### Steps to reproduce: - Install 'whatsapp', and 'crm_livechat' - Configure a WhatsApp channel and receive a message to create a conversation in Discuss - Open the WhatsApp conversation in Discuss - Click on the 'Create Lead' smart button - Check the created lead > The Customer/Contact field is empty ### Cause of Issue: When a lead is created from a Discuss conversation, `_convert_visitor_to_lead` in `crm_livechat` attempts to set the lead's customer. It does this by checking if the channel has `livechat_customer_partner_ids`. However, in whatsapp discuss conversations, the client is saved in `whatsapp_partner_id` and `livechat_customer_partner_ids` is empty. https://github.com/odoo/odoo/blob/29556fda44b9f1e6cf08129443ca47fa6cda34f9/addons/crm_livechat/models/discuss_channel.py#L54-L64 ### Fix: Overrode `_convert_visitor_to_lead` in the `whatsapp` module to properly populate the `partner_id` of the created lead if it wasn't already set and the channel has a `whatsapp_partner_id` instead of checking for the client in `crm_livechat`, which will raise an "Attribute Error", since `crm_livechat` isn't dependent on `whatsapp`. **Note**: No automated test could be added for this fix. Testing the `_convert_visitor_to_lead` method from the `whatsapp` module requires the `crm_livechat` module to be installed to access the base method implementation opw-6371274
Belgian payroll now handles salary and work schedule changes that occur within the same month more accurately. This prevents incorrect GPA prorations by splitting the month into the relevant periods and balancing the hours, helping ensure payslips reflect the employee's actual situation.
Original PR description
Handle salary and schedule changes occurring in the same month by splitting the month into salary periods, using the longest period as the anchor, valuing the shorter periods normally, and balancing the longest period with the remaining theoretical hours. Task Id: 6107129
Fixed an issue in spreadsheets where clearing a global filter could also remove its default value from the edit panel. This keeps the displayed filter setting reliable and prevents confusion when users adjust spreadsheet filters.
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)
The accounting reports date filter now keeps the correct fiscal year range when users switch between companies with different fiscal year calendars. This prevents reports from showing misleading periods caused by reusing dates from the previously selected company.
Original PR description
Fix year-mode date filter when switching between companies with different fiscal years With two companies configured: one using a standard fiscal year and one using an offset fiscal year, switching between them could produce incorrect date ranges. This happened because the previous company’s `date_to` value was reused to compute the current period for the newly selected company, and vice versa. The fix is to use the `date_to` year instead and select the latest fiscal year ending in that same year. Forward-Port-Of: odoo/enterprise#117603
The salary simulation for Indian regular pay structures no longer triggers a background tax calculation that clears newly entered fields. This prevents confusing “Missing required fields” 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
Fixed an issue that could cause payroll salary attachment names to fail when several attachments were processed together. This helps HR and payroll users view records reliably without unexpected errors.
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
Belgian payroll now correctly splits a second long sick leave into the standard paid sick leave and subsequent sickness periods, even when it happens during a relapse window but is not linked to the earlier sickness. This helps ensure payroll calculations reflect the intended leave rules and avoids overstating the initial sick leave period.
Original PR description
Steps to reproduce: - Create a STO for an employee of more than 30 days -> this period is split in 30 days STO and x days SGS. - Within the relapse period, create a second STO of more than 30 days and leave the relapse field empty (which is fine if the second STO is not related to the first sickness) -> the period should be split after the first 30 days just like the first STO, but it remains an STO for the whole duration. task-6296152
Users can now see and select WhatsApp templates that are not yet approved when configuring WhatsApp signing. This makes it clearer that templates must be submitted and approved before the related signing flow can be used, while keeping the flow disabled 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
The salary configurator now correctly pre-selects Yes for optional benefits that are already included in a contract offer. This prevents employees from seeing misleading benefit choices when reviewing their 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
Peruvian accounting reports now use the currency rate already saved on each accounting entry instead of recalculating it later. This reduces rounding discrepancies and helps produce more accurate statutory 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#128027 Forward-Port-Of: odoo/enterprise#126882
Fixes how Belgian salary packages account for the mobility budget when employees take extra-legal leave. The configurator now calculates gross salary and mobility budget consistently with standard payroll rules, improving accuracy for employer cost planning.
Original PR description
Forward-Port-Of: odoo/enterprise#112723
Confirmed manufacturing orders now update their work orders correctly when related bill of materials operations are changed or removed. This helps production teams avoid outdated or extra operations after refreshing a manufacturing order from its bill of materials.
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#126938 Forward-Port-Of: odoo/enterprise#120709
Timesheet suggestions based on calendar events now map more reliably to the right project or task. The fix also corrects duration calculations when overlapping events are adjusted, helping users see more accurate suggested entries.
Original PR description
task: 6435164 Forward-Port-Of: odoo/enterprise#126680
Dominican Republic electronic consumer invoice PDFs no longer show the company name twice when the company document layout or address details are not configured. This keeps invoice headers clean and avoids confusion for customers receiving e-CF invoices.
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-64309185 changes
Enhancements to existing features
When Bulgarian VAT returns are validated, Odoo now automatically generates and attaches the required monthly SAF-T General Ledger, purchase, and sales reports. This helps large companies comply with Bulgarian reporting obligations with less manual work, while making related download error messages easier to understand.
Original PR description
Bulgaria made it mandatory for large companies to present a monthly file
to report their VAT to the administration. To streamline that process,
when the VAT return is validated and PDF is added to the attachments,
the monthly General Ledger SAF-T file, the POKUPKI Purchase Report and
PRODAGBI Sale Report are produced and added as well.
Simplify the report file download error wizard's visuals and descriptions to improve readability.
task-6007963
Forward-Port-Of: odoo/enterprise#127736
Forward-Port-Of: odoo/enterprise#118326Resolved issues and error corrections
The document app's automated checks now reflect improved GIF thumbnail processing. This helps keep quality checks reliable after thumbnails are resized correctly, with no expected change for end users.
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
Project settings no longer show the Time Management section unless the Timesheets app is installed. This avoids confusing users with settings for an app they do not use.
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
This fix removes ambiguity in how approval rule conditions are interpreted in Web Studio. Approval rules with no specific condition now reliably apply to all relevant records, reducing the risk of unexpected approval behavior.
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#127676
The IoT device list now opens device details using the standard navigation flow, which restores broken pagination. This helps users browse and manage larger device lists without getting stuck or losing expected navigation behavior.
Original PR description
Since #72351, the pagination on IoT devices was broken due to how we were getting to the full device form when clicking on a record. We now change the override to use the existing method from the framework `switchToForm` which handles it better. opw-6058532 Forward-Port-Of: odoo/enterprise#127215 Forward-Port-Of: odoo/enterprise#126486
10 changes
Enhancements to existing features
When a Bulgarian VAT return is validated, Odoo now automatically generates and attaches the required monthly SAF-T General Ledger, purchase, and sales report files alongside the PDF. This reduces manual work for large companies and helps them meet Bulgaria’s monthly tax reporting requirements more reliably, while making report download errors easier to understand.
Original PR description
Bulgaria made it mandatory for large companies to present a monthly file
to report their VAT to the administration. To streamline that process,
when the VAT return is validated and PDF is added to the attachments,
the monthly General Ledger SAF-T file, the POKUPKI Purchase Report and
PRODAGBI Sale Report are produced and added as well.
Simplify the report file download error wizard's visuals and descriptions to improve readability.
task-6007963
Forward-Port-Of: odoo/enterprise#118326The Dutch payroll module now includes the 2026 income tax rates for residents. This keeps payroll calculations aligned with the latest tax parameters and helps businesses prepare accurate payslips for the new year.
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
Users can now move through return items in the kanban view using the up and down arrow keys without triggering an error. This improves day-to-day usability 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#125164
Odoo now recognizes valid Brazilian electronic invoice XML files even when the main invoice tag has no extra attributes. This prevents eligible vendor bill files from being skipped during import, helping accounting teams process supplier invoices more consistently.
Original PR description
### Issue before this commit: Certain valid Brazilian NF-e (electronic invoice) XML files fail to import because the system silently ignores them during the initial EDI recognition phase. ### Steps to reproduce the issue: 1. Download Accounting and l10n_br_edi 2. Go to Vendor > Bills 3. Try to import both xmls in the ticket 4. One of the two will not be imported correctly ### Cause of the issue: https://github.com/odoo/enterprise/blob/3ed1721b702555e96c9774969927f6517e855704/l10n_br_edi/models/account_move.py#L819-L827 This function relies on a strict byte string search for b"<NFe " while it's also correct if the tag is only `<NFe>`. ### Reason to introduce the fix: To make the initial NF-e file recognition more robust and compliant with standard XML namespace rules, ensuring Odoo successfully processes all valid Brazilian invoices regardless of attribute formatting. opw-6402843 Forward-Port-Of: odoo/enterprise#127851 Forward-Port-Of: odoo/enterprise#126881
Fixed an issue where Sendcloud return labels could print the customer's house number twice in the origin address. This keeps return shipping labels cleaner and helps avoid confusion for customers and carriers.
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#126250
Swiss Payroll now uses the employee's requested time off dates when calculating absences, avoiding accidental extra absence days caused by timezone conversion. This prevents one-day accident leaves from being counted as two days for employees without a working schedule, helping keep regular wage 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
Salary package configuration now correctly offers all relevant contract benefit fields, including fields provided by country-specific payroll modules. This prevents setup errors and avoids a crash when saving a selected public benefit field.
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')
```Peruvian accounting reports now use the currency rate saved on each accounting entry instead of recalculating it later. This reduces 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
This fixes a broken employee appraisal action that could crash when users tried to request appraisals for multiple employees. Users should use the existing Launch Campaign option instead, which already supports selecting employees and avoids the error.
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 salary calculator now keeps simulations separate from existing payroll documents. This prevents employees with draft payslips from seeing calculator fields cleared or missing-field errors, improving reliability during salary planning.
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
1 change
Resolved issues and error corrections
Peruvian accounting reports now use the exchange rate saved on each accounting entry instead of recalculating it during report generation. This reduces rounding differences and helps produce more reliable regulatory 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
3 changes
Resolved issues and error corrections
DIN 5008 PDF documents 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, quotations, purchase orders, follow-ups, 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#126006Peruvian accounting reports now use the currency rate already saved on each accounting entry instead of recalculating it during report generation. This avoids small rounding differences and helps produce more accurate official reporting 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
This fix prevents the incoming invoice journal from being cleared for companies that cannot receive Peppol documents through the Documents app, such as French companies using electronic invoicing. Incoming Peppol documents are now kept as vendor bills in the required journal instead of being incorrectly routed to Documents.
Original PR description
Peppol documents can be received in a journal or in the Documents app (peppol_reception_mode). Some companies cannot use Documents: _peppol_allows_document_reception() returns False and the journal…
Peppol documents can be received in a journal or in the Documents app (peppol_reception_mode). Some companies cannot use Documents: _peppol_allows_document_reception() returns False and the journal stays required. This is the case of French companies (via l10n_fr_pdp). The onchange of the settings and the import did not check this method. So on a French company with the mode set to 'documents': - the Settings cleared the journal on each opening, while it was still required - the incoming documents were saved in Documents instead of vendor bills Steps to reproduce: - Create a Belgian company, with a purchase journal, and register it on Peppol as receiver - Set the reception mode to "Receive in Documents" - Change the fiscal position to France, and install l10n_fr_pdp, the Peppol part is replaced by "French Electronic Invoicing", so the radio button is not visible anymore, but the company still has peppol_reception_mode == 'documents'. - Open the Settings again: the field "Incoming Invoices Journal" is empty. opw-6429691
19 changes
New functionality added to Odoo
The Chinese reports module now includes the required VAT return type configuration. This helps businesses using Odoo in China prepare VAT returns with the correct report setup available in the system.
Original PR description
Declare the account.return.type for the Chinese VAT return. task-6012279
Enhancements to existing features
Belgian payroll now includes the required Journalist Pension Fund contributions for both employees and employers. This helps payroll calculations reflect the additional 1% employee contribution and 2% employer cost based on the NSSO gross base.
Original PR description
**What:** - Added the Journalist Pension Fund (Employee) salary rule to calculate an additional 1% contribution based on the employee's NSSO gross base. - Added the Accounting: ONSS Journalist Pension Fund (Employer) salary rule to compute the corresponding 2% employer contribution based on the NSSO base, correctly impacting the total employer cost. task-6424251
The Dutch reporting module now uses the shared Dutch SBR tax identifier stored on partner records instead of its older company-specific field. This reduces duplicate data entry and keeps Dutch tax reporting aligned with the standard identifier setup.
Original PR description
After adding l10n_nl_sbr_ob_nummer to partner identifiers l10n_nl_reports_sbr_ob_nummer needs to be deleted. - Replacing l10n_nl_reports_sbr_ob_nummer with l10n_nl_sbr_ob_nummer - Removing res company view task: [6349626](https://www.odoo.com/odoo/project/967/tasks/6349626)
Employee-related documents now stay better connected to employee records, including automatic linking when files are uploaded from an employee profile. Access rights are also synchronized with employee folders, helping ensure documents uploaded through employee discussions follow the correct permissions.
Original PR description
This PR adds more synchronization between the documents and the employee by implementing the points below. - Add the support of the `hr.employee` model in the res_model of documents. - When coming from the context of the employee, uploading the file leads to linking the employee by default to the res_model. - Synced the access rights of the employee folder when the parent folder changes or when the employee folder is created. - Now, documents uploaded from the chatter of the employee will inherit the access rights from the folder. Task-6072094
Accrual-related settings in accounting reports have been moved to the newer plugin-based approach. This keeps the reporting feature aligned with the platform’s current architecture and helps reduce future maintenance risk without changing the user workflow.
Original PR description
In account reports, accrual context is defined in the env but the env is deprecated. The correct way to do it now is to define a plugin.
The AI website builder can now use database information and add JavaScript when simpler page edits are not enough, enabling richer interactive website content. New review, tracking, pause, and editing safeguards help keep AI-generated scripts manageable and reduce the risk of unsafe or disruptive changes.
Original PR description
[IMP] ai_website: enable ai in website to have access to the database This commit reuses the skill to retrieve information from the db. --- [FIX] ai: replace binary field's raw content with its url…
[IMP] ai_website: enable ai in website to have access to the database This commit reuses the skill to retrieve information from the db. --- [FIX] ai: replace binary field's raw content with its url Instead of using the binary field raw content and overflowing the context, we replace it with the url serving the content. --- [IMP] ai_website: allow ai to add javascript to the page This commit enables ai to add javascript to the page, when it's needed. By default, scripts are added as a last resort. Firstly, AI tries to satisfy the user's prompt with HTML/CSS changes, and when it's not possible, we allow AI to add javascript. Because static analysis of custom js scripts is quite limited, a dedicated LLM safety reviewer is introduced to validate proposed code. To ensure safety, the review fails if any vulnerability or suspicious pattern is detected, or if the reviewer call itself fails. In addition, to make sure ai scripts don't mess up the history, and that we properly clean up everything that ai generated, we prompt AI to wrap every part of the code that changes the DOM interactively in a wrapper `__aiProtectMutations`, which handles mutations done right after this call, so we can easily track the changes made by ai script, in order to easily reset modifications made by ai before saving the page, or when entering the edit mode. Also, in the edit mode this wrapper ignores the mutations made by the script. To make it work more-or-less acceptably without shenanigans, we stop the execution of ai generated scripts on entering the translation mode. Also, we add an option container top button to pause the execution of the ai scripts in a block, so users can easily edit it. We prompt AI not to hardcode strings on state change, but instead have a few different blocks with necessary text and show/hide them with d-none. This way we show every text block when scripts don't run to be able to edit them. task-6144157 --- [IMP] website_knowledge: add docstrings to some controllers Previous commit allows AI website builder to search for and use backend controllers. To make it work better we improve docstrigs of some useful controllers in website_knowledge. task-6144157
Date fields in the Marketing Automation dashboard are now aligned to the left, making them easier to read and consistent with the intended layout. This is a small visual improvement that helps users interpret dashboard information more clearly.
Original PR description
…e fields This commit adapts the alignment of the date fields in the marketing automation dashboard to ensure they are displayed correctly (aligned to left). Task: 6474652
The spreadsheet action button has been renamed from "Insert in spreadsheet" to "Spreadsheet". This makes the interface clearer and more concise for users working with spreadsheet-related views.
Original PR description
This commit renames the old `Insert in spreadsheet` button to `Spreadsheet` to keep it simple. Task: 6477436
Resolved issues and error corrections
Invoice and bill numbers now reflect fiscal years that span more than 12 months, such as using a 25-26 year range instead of only 2026. This prevents misleading document numbering and keeps accounting sequences aligned with the company’s configured fiscal year records.
Original PR description
Issue: When a fiscal year begins, for example, the 01/12/2025 and ends the 31/12/2026, the sequence mechanism for the invoices and bills do not take into account that the fiscal year covers more than a year and the sequence starts at INV/2026/0001 instead of INV/25-26/0001. Source of the issue: when computing if the year is stagerred, we took into account only the fiscalyear_last_day and fiscalyear_last_month of the company, instead of checking if there exists any record of account.fiscal.year, and if there is, prioritize the existing records. task-4951257
The timesheet assistant display now keeps durations readable, adds clearer spacing, and prevents long titles from overlapping with time information. This makes timesheet entries easier to scan and reduces visual confusion for users.
Original PR description
- enforce duration in one line - add a gap between the title and duration - wrap title if so long - adapt flex direction of chronological view to avoid overlapping of title and start time --- task-6432434 Forward-Port-Of: odoo/enterprise#128037 Forward-Port-Of: odoo/enterprise#126236
The Belgian payroll accounting test demo data now defines employee working schedules correctly. This helps keep payroll-related tests and sample scenarios reliable, reducing confusion from incorrect demo setup.
Original PR description
The way calendar attendances were defined for the working schedules demo data was incorrect. task-6466997
Shopfloor users can now unplan manufacturing work orders without being blocked by an access error linked to Belgian payroll leave records. This fixes a disruption caused when removing planned work time triggered a payroll-related check the user was not allowed to perform.
Original PR description
After https://github.com/odoo/enterprise/pull/104335 , A search on `hr.leave.allocation` is done, which the normal shopfloor user doesn't have access to. In shopfloor,`leave_id` (a field related to `resource_calender_leaves` in `mrp_workorder`) gets unlinked in a couple of different actions, which then triggers the unlink method added that performs the search consequently. Simple steps to reproduce: - Use shopfloor user - Create MO - Plan workorders - Unplan At unplanning, `leave_id` is unlinked, which then triggers the `on_delete` method in `l10n_be_hr_payroll` that performs the search and throws the access error.
The accounting reports comparison dropdown now keeps a consistent width when users hover over the “Percentage of” selector. This prevents a small visual jump, making the reporting interface feel smoother and more polished.
Original PR description
In the comparison dropdown, the "Percentage of" RecordSelector shows its caret only on hover. Since the caret was not accounted for before hover, the selector slightly resized when the caret appeared. Reserve a small, fixed slot for the caret and toggle its visibility so the dropdown keeps a stable width. task-6268556
Users can now select multiple bank journal lines in the kanban view without triggering an error. This restores a smoother bank reconciliation workflow for teams handling multiple statement lines at once.
Original PR description
Fix a bug where an error is raised when the user tries to multi-select lines (with alt) in the kanban view of bank journals. The view for BankRecStatementLine class was using an other object than rootRef defined in the KanbanRecord class as a t-ref. This made every call to rootRef crash in the parent class. This commit fixes it by using the parent rootRef in the BankRecStatementLine class. task-6462758
Luxembourg payroll now calculates historical payslips using the wage index that applied at the payslip date, rather than today’s index. This helps ensure past salary calculations remain accurate when wage index values change over time.
Original PR description
Historical payslips incorrectly used today's wage index instead of the index active during the payslip period. Now, salary rules evaluate the indexed wage using `payslip.date_to` via the new `_get_l10n_lu_indexed_wage(date)` contract method. Task: 6395557 Forward-Port-Of: odoo/enterprise#125861
Rental orders using custom routes now correctly generate the expected return transfer, even when the route is configured for make-to-order purchasing. This prevents missing return logistics after a rental delivery, helping teams track rented products through the full rental cycle.
Original PR description
### Steps to reproduce: - In the settings enable: Multi-Steps Routes and rental transfers - Unarchive the MTO route - Create a rental product P with a buy route and a set vendor - Create a rental…
### Steps to reproduce: - In the settings enable: Multi-Steps Routes and rental transfers - Unarchive the MTO route - Create a rental product P with a buy route and a set vendor - Create a rental order for 1 x P and set the the MTO route on the sol - Confirm the order #### > The delivery as well as the purchase for 1 unit of P was generated but the return was not. ### Cause of the issue: The procurement generated to handle both the delivery and the return rental picking are handled by the `_create_procurements`: https://github.com/odoo/enterprise/blob/b0e48baaf99bdc4faefd2ffdd3bd5637fb548593/sale_stock_renting/models/sale_order_line.py#L353-L374 The `route_ids` set and used is the `mto_route` set on the sol: https://github.com/odoo/odoo/blob/0f061503e26ac8c441d62d91947419119e48c47a/addons/sale_stock/models/sale_order_line.py#L415-L422 https://github.com/odoo/odoo/blob/0f061503e26ac8c441d62d91947419119e48c47a/addons/sale_stock/models/sale_order_line.py#L282-L297 However, in the present case, the mto route does not contain any rule with a relevant `location_src_id` in the rental location so that the return will not be generated. opw-6361322 Forward-Port-Of: odoo/enterprise#126410 Forward-Port-Of: odoo/enterprise#124097
This update adds a missing dependency so barcode batch quality control loads the related batch picking components reliably. It prevents test and module loading failures without changing day-to-day user workflows.
Original PR description
Before this commit, this bridge patches the barcode picking batch model of stock_barcode_picking_batch while not depending on it. This goes unnoticed in the backend, where every installed module lands in the same bundle, but a Hoot test file only loads the modules of the dependency closure of its addon, so the first test suite added here dies on "error while registering suite". This commit adds the missing dependency. stock_barcode_picking_batch is auto installed on top of stock_barcode and stock_picking_batch, both already required here, so it comes along with this module anyway.
The payroll attendance module now explicitly includes a related attendance planning component it already relies on. This prevents automated tests from failing when they load only the module's declared dependencies, improving reliability without changing day-to-day user behavior.
Original PR description
Before this commit, hr_payroll_attendance patches the gantt view of hr_attendance_gantt while not depending on it. This goes unnoticed in the backend, where every installed module lands in the same bundle, but a Hoot test file only loads the modules of the dependency closure of its addon, so the first test suite added here dies on "error while registering suite". This commit adds the missing dependency. hr_attendance_gantt is auto installed on top of hr_attendance and hr_gantt, both already required here, so it comes along with this module anyway.
Code cleanup and technical improvements
The Point of Sale enterprise code was updated to use the newer supported framework behavior, replacing a deprecated internal mechanism. This keeps the module aligned with the latest platform standards and reduces future maintenance risk without introducing intended user-facing changes.
Original PR description
Replaced `useLayoutEffect` in `useDelayedValueChange` with native OWL3 `useEffect` because `useLayoutEffect` is deprecated in OWL3. The hook has no DOM/ref access — it arms a `setTimeout` to snapshot a reactive value after a delay. This is a pure side-effect-on-dep-change, making native `useEffect` the correct fit. A `void getCurrentValue()` call at the top of the effect restores the tracking the old dependency array provided, ensuring the timer resets whenever the tracked value changes. When commenting out the useLayoutEffect there was no error, the code we refactored had NO TEST coverage. The only build failure when commented was a lint error (unused-var from the comment pattern) — no functional tests failed. see runbot build with useLayoutEffect commented out: https://runbot.odoo.com/runbot/batch/2622063/build/116410184
3 changes
Resolved issues and error corrections
The AI assistant now waits until pivot reports are fully ready before applying changes, preventing crashes or blank pivot views when switching views. It also keeps the default measures when the AI has not requested specific ones, so users continue to see meaningful report data.
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
Odoo now recognizes valid Brazilian NF-e invoice XML files even when the main invoice tag has no extra attributes. This prevents some vendor bills from being silently skipped during import, helping accounting teams process compliant Brazilian invoices more consistently.
Original PR description
### Issue before this commit: Certain valid Brazilian NF-e (electronic invoice) XML files fail to import because the system silently ignores them during the initial EDI recognition phase. ### Steps to reproduce the issue: 1. Download Accounting and l10n_br_edi 2. Go to Vendor > Bills 3. Try to import both xmls in the ticket 4. One of the two will not be imported correctly ### Cause of the issue: https://github.com/odoo/enterprise/blob/3ed1721b702555e96c9774969927f6517e855704/l10n_br_edi/models/account_move.py#L819-L827 This function relies on a strict byte string search for b"<NFe " while it's also correct if the tag is only `<NFe>`. ### Reason to introduce the fix: To make the initial NF-e file recognition more robust and compliant with standard XML namespace rules, ensuring Odoo successfully processes all valid Brazilian invoices regardless of attribute formatting. opw-6402843 Forward-Port-Of: odoo/enterprise#127851 Forward-Port-Of: odoo/enterprise#126881
Database neutralization for TikTok shops no longer fails when multiple active shops exist. The change keeps shop references anonymized while ensuring each active shop still has a unique placeholder, allowing safe test or backup database preparation.
Original PR description
Steps to produce: --- - Install sale_tiktok module. - Create two active tiktok.shop records. - Run the database neutralization command. Issue: --- - Neutralization fails with a PostgreSQL error:…
Steps to produce: --- - Install sale_tiktok module. - Create two active tiktok.shop records. - Run the database neutralization command. Issue: --- - Neutralization fails with a PostgreSQL error: ```py duplicate key value violates unique constraint tiktok_shop_unique_active_shop` DETAIL: Key (tiktok_shop_ref)=(1) already exists. ``` Root cause: --- - At [1], we are setting `tiktok_shop_ref = 1` for all `tiktok_shop` records. Because `tiktok_shop` enforces a partial unique constraint on `tiktok_shop_ref` for active shops [2], setting the same reference value `1` on multiple active shops violates this constraint. Solution: --- - Update sql to assign a row-unique string to each shop. This strips the real shop reference while maintaining uniqueness across active shop records so neutralization completes cleanly. [1]https://github.com/odoo/enterprise/blob/85754b0354b76da8b4d87a3a81dd19679ed35d15/sale_tiktok/data/neutralize.sql#L1-L8 [2]https://github.com/odoo/enterprise/blob/85754b0354b76da8b4d87a3a81dd19679ed35d15/sale_tiktok/models/tiktok_shop.py#L136-L139 opw-6451715 ---
7 changes
Enhancements to existing features
Signer email addresses on signature requests can now only be changed by the person who created the request. This helps prevent unintended or unauthorized recipient changes and keeps the signing process more controlled.
Original PR description
Forward-Port-Of: odoo/enterprise#126675
Resolved issues and error corrections
Odoo now correctly reads Colombian vendor bill XML files that include withholding taxes. This ensures ReteRenta, ReteIVA, and ReteICA amounts are added to invoice lines, reducing manual corrections and improving tax accuracy.
Original PR description
Currently, Odoo doesn't detect Withholdings taxes for Colombia while uploading vendor bill XML, causing ReteRenta, ReteIVA, ReteICA withholdings to not being added in invoice lines, for versions under 19.2 Fix: Backport commit https://github.com/odoo/enterprise/commit/edc67858350d1b8408019e51751240026d931dab Versions: 18.0 -> 19.1 Task-id: [6417175](https://www.odoo.com/odoo/project/49/tasks/6417175)
The direct debit export now includes a required scheme name field in the initiating party details. This helps ensure SEPA direct debit files are accepted by banks that require it, including Nordea in Sweden.
Original PR description
We are missing a SchmeNm node in the InitgPty node. This is mandatory for Nordea in Sweden at least. Such as: ```xml <SchmeNm> <Cd>CUST</Cd> </SchmeNm> ``` task-6385960
DIN 5008 PDF documents 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, quotations, purchase orders, follow-ups, 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-6392649Fixes the Moroccan Profit and Loss report so budget figures are included instead of appearing empty. The report also now shows the related budget percentage column, giving users a more complete view for financial planning and comparison.
Original PR description
Issue: Budgets are not working on Profit and Loss in the l10n_ma. Steps to Reproduce: - Install l10n_ma - Go to Profit and Loss, create a budget - Select Profit and Loss Statement (MA) - Budget is empty. Reason: For l10n_ma we use different values on `target_line_res_dict` (`cur_op` `prev_op` `cur_tot`) rather than the usual `balance`. This causes our budget column to have those same 3 types instead of the usual `balance`. When we then search for the dict entry `balance` in `target_line_res_dict`, we won't find anything giving us `None` and causing the current issue where nothing appears. https://github.com/odoo/enterprise/blob/61e3272430b4adeefb90a0a224bd308a3267decf/account_reports/models/account_report.py#L3125-L3129 opw-6385229
This fixes German SEPA credit transfer exports so they no longer include an unsupported LEI field in older bank file formats. The change keeps generated payment files compliant with bank requirements, reducing the risk of rejected vendor payment batches.
Original PR description
### Issue before this commit: When generating a SEPA Credit Transfer batch using the German XML format (pain.001.001.03), the <LEI> tag is erroneously included in the exported file if an LEI is…
### Issue before this commit: When generating a SEPA Credit Transfer batch using the German XML format (pain.001.001.03), the <LEI> tag is erroneously included in the exported file if an LEI is configured on the company. This invalidates the XML, causing banks to reject the file. ### Steps to reproduce the issue: 1. Download Accounting and l10n_de 2. Go to Settins > Vendor Payments > SEPA Credit Transfer / ISO20022 and set Name Identification as 529900T8BM49AURSDO55 and Issuer as LEIMAN 3. Go to companies and set 529900T8BM49AURSDO55 as LEI in the DE company 4. Go to Accounting dashboard and click the 3 dots of the bank group, go to Configuration and set the Account Number and be sure in the Outgoing Payments tab XML Format is German 5. Create a new German company from Contacts with: 1. Country as Germany 2. VAT 3. Account Number in the Bank Accounts by adding one line: 1. example Account Number: DE65100500007201811026 2. example Bank: BNP Paribas 3. activate the Send Money button 7. Then go to Vendor > Payments and create a new one with Payment Method as SEPA Credit Transfer for the German company created 8. Go back and select the new payment from the list and click create batch and print it 9. In the XML of pain.001.001.03.(DE) file, the LEI tag should not be included. ### Cause of the issue: The XML generation logic does not filter out the <LEI> element for older schema versions like pain.001.001.03, which do not support this tag. ### Reason to introduce the fix: To ensure strict schema compliance and prevent bank rejections. The <LEI> element is now properly omitted from pain.001.001.03 files and restricted only to newer formats (e.g., pain.001.001.09) where it is valid. opw-6428150
This fix ensures payment XML files use uppercase encoding labels, matching stricter expectations from some banking providers such as SIX in Switzerland. It helps prevent avoidable bank warnings or rejections when processing SEPA and ISO 20022 payment files.
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#125807
3 changes
Enhancements to existing features
Austria’s SEPA credit transfer payments now use the newer standard payment file format by default, replacing a local older format that will soon be unsupported. This helps businesses stay compliant with bank requirements and reduces the risk of future payment file rejections.
Original PR description
Austria currently uses a local variant of SCT, which is actually a restricted version of pain.001.001.03 (the old SEPA format). This version will no longer be supported within a year. We now use pain.001.001.09 as the default format (for Austria and generic SEPA) and update existing values accordingly in the migration scripts (only for Austria, see upgrade PR). task-5157755
Resolved issues and error corrections
German DATEV exports now correctly handle bank settlements involving a payer currency, bank account currency, and company currency. The transaction is split through the standard DATEV clearing account so each exported line uses only one foreign currency, preventing export issues while leaving simpler 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
Mexican electronic invoices now report the original unit price and discount correctly when invoice lines have extremely high discounts. This prevents incorrect CFDI XML amounts that could affect compliance and customer-facing invoice documents.
Original PR description
### Steps to reproduce issue: 1. Activate the Mexican localization and configure a company able to sign 2. Create an invoice with one line: a product with a valid UNSPSC code, Price 250.00, VAT 16%,…
### Steps to reproduce issue:
1. Activate the Mexican localization and configure a company able to sign
2. Create an invoice with one line: a product with a valid UNSPSC code,
Price 250.00, VAT 16%, Discount 99.99%
3. Confirm, then Send & Print with the CFDI checked
4. In the generated XML, the following values are off:
- In node "Comprobante": SubTotal="200.00" Descuento="199.98"
- In node "Concepto": ValorUnitario="200.00" Importe="200.00"
Descuento="199.98"
- Expected are 250.00 and 249.98
### Explanation:
`discount` is a Float(digits=(16, 2)) and both conversions of the field don't give the same float: `convert_to_column` writes it as '99.99' while `convert_to_cache` rounds it to 99.99000000000001. Hence, 250 * (1 - 99.99000000000001 / 100) = 0.0249999999999695, rounded to 0.02, but 250 * (1 - 99.99 / 100) = 0.0250000000000250, rounded to 0.03. `price_subtotal` is therefore stored as 0.02 when the invoice is posted, while any later request, such as the one generating the CFDI, recomputes 0.03.
`gross_price_subtotal` is computed by dividing the rounded `price_subtotal` by `discount_factor`, which amplifies the rounding error of `price_subtotal` by 1 / discount_factor, so 10 000 times with a 99.99% discount: 0.02 / 0.0001 = 200.00 instead of 250.00. That difference is exactly half a cent, which is also the tolerance of the condition added by 771d3e3cf, so the condition fails and the amount computed from `price_subtotal` wins over `price_unit`. Every price whose net lands on a half cent is affected, i.e. the odd multiples of 50 with a 99.99% discount.
### Fix reasoning:
`price_unit` is the source of truth for `ValorUnitario`. Tolerating one rounding unit instead of half of one is enough to cover the rounding of `price_subtotal`, so `price_unit` keeps the priority. The fallback still applies to the cases it was added for, where something other than the discount affects `price_subtotal` (e.g. price-included taxes), since those differ by much more than one rounding unit. The discount amount is derived from the gross, so it absorbs the difference: `Importe` - `Descuento` still gives `price_subtotal` and the total of the document is unchanged.
ticket-id: [6386895](https://www.odoo.com/odoo/project/49/tasks/6386895)