Daily updates from Odoo
Tuesday, August 18, 2026
193 changes
26 changes
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.
Currently, trying to create a link via typing a url throws traceback in Safari. This happens because after merging commit [1], `addStep` is called in `onBeforeInputPrepareConvertToLink` but from saas-19.4 `addStep` function is replaced by `commit`. This PR aims to replace `addStep` to `commit` to prevent traceback. [1]: https://github.com/odoo/odoo/commit/e5af0308b3fb87abb04de07c8554658e892ee03e --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/subm
Original PR description
Currently, trying to create a link via typing a url throws traceback in Safari. This happens because after merging commit [1], `addStep` is called in `onBeforeInputPrepareConvertToLink` but from saas-19.4 `addStep` function is replaced by `commit`. This PR aims to replace `addStep` to `commit` to prevent traceback. [1]: https://github.com/odoo/odoo/commit/e5af0308b3fb87abb04de07c8554658e892ee03e --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
On a database that was not created with demo data, registering for Peppol using the "Odoo Demo ID" identification fails instead of activating the local demo/training registration. The registration wizard correctly shows "Demo" as the EDI mode once that identifier is selected, but submitting the registration still reaches out to the real Peppol Access Point, which the wizard cannot connect to, so the registration is blocked. Steps to reproduce: ------------------- * Create a new database wit
Original PR description
On a database that was not created with demo data, registering for Peppol using the "Odoo Demo ID" identification fails instead of activating the local demo/training registration. The registration…
On a database that was not created with demo data, registering for Peppol using the "Odoo Demo ID" identification fails instead of activating the local demo/training registration. The registration wizard correctly shows "Demo" as the EDI mode once that identifier is selected, but submitting the registration still reaches out to the real Peppol Access Point, which the wizard cannot connect to, so the registration is blocked. Steps to reproduce: ------------------- * Create a new database without demo data (e.g. a regular Odoo Online database, not a /trial one) * Install Accounting, open the Peppol registration wizard * Select "Odoo Demo ID" as the Electronic Address Scheme and fill in the other required fields * Click on Register > Observation: The wizard displays "Demo" as the EDI mode, yet registering raises "Failed to connect to Odoo Peppol Proxy." Manually creating the system parameter `account_peppol.edi.mode` with the value "demo" makes the registration succeed, but that parameter is only ever set automatically when the module's demo data is loaded. Why the fix: ------------ The wizard's displayed `edi_mode` correctly resolves to "demo" for the Odoo Demo ID EAS, but `handle_demo` - which decides whether `_can_connect`/`_create_connection` run against the local mock or the real Peppol proxy - recomputed the mode itself without passing that EAS along, so it could only detect demo mode via the system parameter or an already existing proxy user. Made `handle_demo` pass the record's own `peppol_eas` as `temporary_eas`, aligning its detection with what the wizard already displays. opw-6448276
Before this commit and since the new read_group (which fetches records from open groups server side), images were loaded as base64, overloading the return payload and potentially triggering overload errors (MemoryError) This was because the bin_size = true context key was forgotten. After this commit, images are not loaded as base64 thanks to that context key Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged:
Original PR description
Before this commit and since the new read_group (which fetches records from open groups server side), images were loaded as base64, overloading the return payload and potentially triggering overload errors (MemoryError) This was because the bin_size = true context key was forgotten. After this commit, images are not loaded as base64 thanks to that context key Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282562 Forward-Port-Of: odoo/odoo#281911
Before this commit, deleting a record leaves its id in the relations of a record deleted before it in the same update, and reading one of those relations hands out an entry for a record that is gone. This happens because an update takes a deleted record out of the relations that hold it, but forgets it as soon as it is deleted. However, deleting a record is what queues the deletion of the records it holds: `channelMembers` carries `onDelete: (r) => r.delete()`, so the members of a thread are
Original PR description
Before this commit, deleting a record leaves its id in the relations of a record deleted before it in the same update, and reading one of those relations hands out an entry for a record that is gone. This happens because an update takes a deleted record out of the relations that hold it, but forgets it as soon as it is deleted. However, deleting a record is what queues the deletion of the records it holds: `channelMembers` carries `onDelete: (r) => r.delete()`, so the members of a thread are deleted once the thread is already forgotten, and their ids stay in its `onlineMembers`. This commit fixes the issue by keeping the records deleted by an update known until it ends. Forward-Port-Of: odoo/odoo#282621 Forward-Port-Of: odoo/odoo#281983
**Problem**: When creating a percentage down payment with a Brazilian tax, the divide by zero error occurs because the factor contains a zero value. **Steps to reproduce**: 1. Set up Brazil localization with ```l10n_br_avatax```, ```l10n_br_edi```. 2. Create a service product with ```LC116``` code set up. 3. Create a sales order with the customer has ```Automatic Tax Mapping (Avalara Brazil)``` in their Fiscal Position. 4. Add the service product to the sales order and confirm it. 5. Cr
Original PR description
**Problem**: When creating a percentage down payment with a Brazilian tax, the divide by zero error occurs because the factor contains a zero value. **Steps to reproduce**: 1. Set up Brazil localization with ```l10n_br_avatax```, ```l10n_br_edi```. 2. Create a service product with ```LC116``` code set up. 3. Create a sales order with the customer has ```Automatic Tax Mapping (Avalara Brazil)``` in their Fiscal Position. 4. Add the service product to the sales order and confirm it. 5. Create a percentage down payment for the sales order. 6. Divide by zero error occurs. **Fix**: Check if the ```sum_of_factors``` is zero and if so, set the ```delta_factor``` to zero. before ```normalize_results['plus_sum_of_factors'] / normalize_results['sum_of_factors']``` opw-6446986 Forward-Port-Of: odoo/odoo#281571
Steps to reproduce: - Make sure you have 2 or more languages installed - Install events and website app - For the website, apply the languages you have in settings - Make sure website default language is different from backend language - Create an event - Go to the event's website page through the smart button - Switch the language of the frontend to one that is different from backend - Translate (alter) the description for this language - Click on the outlook icon to download the ics
Original PR description
Steps to reproduce: - Make sure you have 2 or more languages installed - Install events and website app - For the website, apply the languages you have in settings - Make sure website default…
Steps to reproduce: - Make sure you have 2 or more languages installed - Install events and website app - For the website, apply the languages you have in settings - Make sure website default language is different from backend language - Create an event - Go to the event's website page through the smart button - Switch the language of the frontend to one that is different from backend - Translate (alter) the description for this language - Click on the outlook icon to download the ics Current Behavior: The ics will contain a description based on the backend's chosen language Expected Behavior: The ics will contain a description based on the website's chosen language. If there's no website, it will use the browser's language Clarification: The current behavior is confusing for users as they will expect the ics to match the description of the current webpage which is dependent on the frontend's selected language. This current behavior also contradicts the google calendar behavior which grabs the description based on the frontend's selected language. The endpoints for retrieving the ics file now utilize the website=True flag to take advantage of IrHttp._match which will automatically set the context language to be the frontend's rather than the backend language. We want the ics file to be in the frontend language for visual consistency. Also, send_email_reminder function does not require with context for this reason opw-6235320 Forward-Port-Of: odoo/odoo#280520 Forward-Port-Of: odoo/odoo#271968
23 changes
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-64309186 changes
Resolved 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
Currently, when the user scraps a component during a manufacturing order, the system can still consume the same lot/serial number during production, leading to double consumption even when the product is no longer available in inventory. ## Steps to produce: - Install the Manufacturing app. - Enable 'Work Orders' and 'Lots & Serial Numbers' from the settings. - Create two products: 'Car' and 'Engine'. - Configure 'Engine' to be tracked by 'Serial Number' and update its on-hand quantity to
Original PR description
Currently, when the user scraps a component during a manufacturing order, the system can still consume the same lot/serial number during production, leading to double consumption even when the…
Currently, when the user scraps a component during a manufacturing order, the system can still consume the same lot/serial number during production, leading to double consumption even when the product is no longer available in inventory. ## Steps to produce: - Install the Manufacturing app. - Enable 'Work Orders' and 'Lots & Serial Numbers' from the settings. - Create two products: 'Car' and 'Engine'. - Configure 'Engine' to be tracked by 'Serial Number' and update its on-hand quantity to 2 using two different serial numbers. - Create a Bill of Materials (BoM) for 'Car' with 'Engine' as a component. - Create a work center named 'Car Assembly'. - Go to Manufacturing > Configuration > Operations. - Create a new manufacturing operation named 'Engine Insertion' with: - Bill of Materials: 'Car' - Work Center: 'Car Assembly' - Create and confirm a new Manufacturing Order (MO) for 'Car'. - Open the related work order and start the operation. - From Actions > Scrap, scrap the 'Engine' serial number currently reserved for the MO. - Click Produce All. - Open the product view for 'Engine' and check the on-hand quantity. ## Observed behavior: Even after the user scraps the product during the work order, the system still allows the same serial-numbered component to be consumed during production. This results in negative on-hand quantities and creates inconsistencies in inventory and manufacturing data. ## Root cause: When the user starts the work order, the `button_start` function [1] is called. This sets the `qty_producing` field, which triggers its inverse method `_set_qty_producing`. As part of this flow, the component move is marked as picked at [2]. This, in turn, triggers the inverse method of the `picked` field, which marks all move lines in the move as picked, as shown at [3]. Later, when the user scraps the same serial-numbered product, the `do_scrap` function is called. This invokes `_action_done` at [4], which further calls `_action_done` on the move lines at [5]. During this process, `_free_reservation` is executed at [6]. However, because the move lines were previously marked as picked, they no longer satisfy the domain condition at [7]. As a result, they are not unlinked at [8]. This leaves the reservation intact and eventually causes the scrapped component to still be consumed when the user produces the quantity on the manufacturing order. **Why does this issue not occur in saas-19.3?** This issue no longer occurs in saas-19.3 after this [commit](https://github.com/odoo/odoo/commit/bb56600bfc81b9a6573a867df93a706de8154d0a), which removed the manual consumption field. As a consequence, move lines are no longer marked as picked when starting work orders, preventing the inconsistent state that leads to this issue. [1]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/mrp/models/mrp_workorder.py#L659-L660 [2]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/mrp/models/mrp_production.py#L1417-L1421 [3]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/stock/models/stock_move.py#L287-L290 [4]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/stock/models/stock_scrap.py#L152-L158 [5]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/stock/models/stock_move.py#L2125 [6]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/stock/models/stock_move_line.py#L696-L700 [7]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/stock/models/stock_move_line.py#L807-L817 [8]: https://github.com/odoo/odoo/blob/8f5d7b5cd0c3e1808375baef8f0834d3f2e27a75/addons/stock/models/stock_move_line.py#L851 ## Solution: When a component move line is scrapped, it should be unpicked so that the system can reserve the next available lot/serial number automatically. Once a component has been physically scrapped, it no longer makes sense to keep it marked as picked and reserved. Doing so leaves the MO in an inconsistent state, where the system still considers the scrapped component usable. By unpicking the move line after scrapping, both the system and the user clearly see that there is no longer any reserved quantity associated with that component. This also allows system to automatically pick from next available lot or serial number, ensuring the manufacturing process can continue smoothly. Such behavior is especially important in shop floor scenarios, where mistakes or damages may occur after components have already been registered, and the manufacturing process should continue without interruption. opw-6157406 Forward-Port-Of: odoo/odoo#264589
Validating a negative order manually paid with a standard method (e.g., Card) mistakenly triggers the fast payment fallback. The system wrongly assumed any single negative payment line was an auto-generated unsettled due. As a result, if the first configured payment method is "Customer Account", validation is incorrectly blocked by a popup asking for a customer. This commit restricts the fast payment trigger for negative amounts to only apply when the existing line is specifically a 'pay_l
Original PR description
Validating a negative order manually paid with a standard method (e.g., Card) mistakenly triggers the fast payment fallback. The system wrongly assumed any single negative payment line was an auto-generated unsettled due. As a result, if the first configured payment method is "Customer Account", validation is incorrectly blocked by a popup asking for a customer. This commit restricts the fast payment trigger for negative amounts to only apply when the existing line is specifically a 'pay_later' type. task-6443347
10 changes
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
Currently, returning a product via the PoS does not populate the `origin_returned_move_id` on the generated incoming stock move. For products using AVCO or FIFO valuation, this causes the stock valuation engine to fall back to the product's current standard price instead of using the historical cost of the original sale, resulting in stock valuation errors and accounting imbalances. This commit fixes the issue by updating `_prepare_stock_move_vals` to evaluate the `refunded_orderline_id`. It
Original PR description
Currently, returning a product via the PoS does not populate the `origin_returned_move_id` on the generated incoming stock move. For products using AVCO or FIFO valuation, this causes the stock valuation engine to fall back to the product's current standard price instead of using the historical cost of the original sale, resulting in stock valuation errors and accounting imbalances. This commit fixes the issue by updating `_prepare_stock_move_vals` to evaluate the `refunded_orderline_id`. It traces the refund back to the original PoS order and dynamically links the original completed outgoing stock move. This ensures the valuation waterfall correctly intercepts the return and applies the original historical cost. opw-6216531 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#273847
At installation of the l10n_pl_bank_verification module or when upgrading from a lower version, the field l10n_pl_verification_id on the account.payment model gets computed and it makes the upgrade crash out. Adding a init to the model to create the column to prevent the ORM from computing the field at module installation no-task Forward-Port-Of: odoo/odoo#282504
Original PR description
At installation of the l10n_pl_bank_verification module or when upgrading from a lower version, the field l10n_pl_verification_id on the account.payment model gets computed and it makes the upgrade crash out. Adding a init to the model to create the column to prevent the ORM from computing the field at module installation no-task Forward-Port-Of: odoo/odoo#282504
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
4 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
- Added the BolloVirtuale in the Simplified invoice template - Now it's possible to force the Simplified format on exported invoice when the `l10n_it_document_type` is set to a simplified one - Factored the Italian partner recognition (_l10n_it_edi_is_italian) - Added a check on the invoice, no simplified format for non-domestic / PA partners Task [link](https://www.odoo.com/odoo/project.task/6226436) task-6226436 Forward-Port-Of: odoo/odoo#274493
Original PR description
- Added the BolloVirtuale in the Simplified invoice template - Now it's possible to force the Simplified format on exported invoice when the `l10n_it_document_type` is set to a simplified one - Factored the Italian partner recognition (_l10n_it_edi_is_italian) - Added a check on the invoice, no simplified format for non-domestic / PA partners Task [link](https://www.odoo.com/odoo/project.task/6226436) task-6226436 Forward-Port-Of: odoo/odoo#274493
19 changes
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.
The Time Type form now hides the Validity label for companies outside Hong Kong when the related field is not shown. This prevents misaligned fields and makes the payroll configuration screen clearer for users in non-HK companies.
Original PR description
Steps to reproduce: 1. Go to Payroll > Configuration > Time Types. 2. Open a Time Type form for a non-HK company (e.g. Belgium). 3. The "Validity" label appears without its field, misaligning "Selectable in Time Off". Reason: The "Validity" label had no visibility condition. Solution: Hide the "Validity" label when the company is non-HK. Task-6448696
Barcode receipts now keep the correct putaway destination when users scan multiple lots for the same product. This prevents items from being shown or processed as going to the default stock location instead of the intended shelf, reducing warehouse handling errors.
Original PR description
Steps to reproduce --- 1. Enable Storage Locations and Lots & Serial Numbers. 2. Add a putaway rule sending a lot-tracked product from WH/Stock to WH/Stock/Shelf 1. 3. Confirm a receipt reserving 2…
Steps to reproduce --- 1. Enable Storage Locations and Lots & Serial Numbers. 2. Add a putaway rule sending a lot-tracked product from WH/Stock to WH/Stock/Shelf 1. 3. Confirm a receipt reserving 2 units of that product; putaway sets the reserved move line destination to WH/Stock/Shelf 1. 4. In the Barcode app, scan a first lot, then a second lot. The second lot lands on a separate line at WH/Stock instead of WH/Stock/Shelf 1. Issue --- The first lot reuses the reserved line and keeps its Shelf 1 destination. The second lot cannot reuse it because its tracking number differs, so `_findLine` returns nothing and `_getNewLineDefaultValues` builds a new line with `location_dest_id` set to `_defaultDestLocation()`, the picking's default destination (WH/Stock). https://github.com/odoo/enterprise/blob/314a79b774f30dc9377b2971492576c4b84483e1/stock_barcode/static/src/models/barcode_picking_model.js#L1591-L1601 Putaway relocates the destination on the move line at reservation, never on the picking, so only the reserved line carries Shelf 1. Since `groupKey` includes `location_dest_id`, the new line does not group with the first lot and shows separately at WH/Stock. This is not a regression: new lines have always defaulted to the operation destination. https://github.com/odoo/enterprise/blob/314a79b774f30dc9377b2971492576c4b84483e1/stock_barcode/static/src/models/barcode_picking_model.js#L239-L241 The new line now inherits the selected line's `location_dest_id`, already relocated by putaway, instead of the default. opw-6317077 Forward-Port-Of: odoo/enterprise#127906 Forward-Port-Of: odoo/enterprise#125309
Odoo now recognizes valid Brazilian electronic invoice XML files even when the main invoice tag has no extra attributes. This prevents legitimate vendor bills 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
This update adds a missing dependency needed by the Time Off Gantt module so its related test suites load correctly. It prevents registration failures during automated testing without changing day-to-day user functionality.
Original PR description
Before this commit, every Hoot suite of hr_holidays_gantt, hr_payroll and planning_holidays dies while being registered:
HootError: error while registering suite "avatar_card" (in parent
suite "@hr_holidays_gantt")
This happens because the gantt cog menu imports @base_import/import_records/import_records while the module does not depend on base_import. A test file only loads the modules of the dependency closure of its addon, so the import resolves to undefined and the file throws as soon as it is loaded.
This commit adds the missing dependency. base_import is auto installed and depends only on web, so it is already there in every database.This fixes a display issue in Referrals where the x icon was missing from job positions when sharing jobs. The change helps users clearly see and use the expected close or remove action in that workflow.
Original PR description
Since https://github.com/odoo/enterprise/pull/112607, the x icon is not displayed on job positions. Steps to reproduce: - Referrals - View Jobs - Share Expected behavior: The x icon should be displayed on job positions. <img width="213" height="188" alt="image" src="https://github.com/user-attachments/assets/14d1e75b-f419-4315-93b3-ef0642351079" /> Current Behavior: The x icon is not displayed on job positions. <img width="558" height="374" alt="image" src="https://github.com/user-attachments/assets/52bffb8b-b7ba-43df-8a44-c507966d5073" /> task-6469820
Belgian DIMONA fields are now shown when employee types are relevant to Belgium or not limited to a country, avoiding missing setup options. The update also prevents mismatched employee type and company countries, reducing payroll configuration errors.
Original PR description
company/country validation DIMONA category and sub-types were hidden whenever the employee type's country was not exactly 'BE', including when no country was set at all. They should be visible whenever the type has no country or BE, and no company or a BE company. Add a `company_country_code` related field to expose the company's country for use in the view invisible domain, and add a constraint raising a validation error if an employee type's country and its related company's country don't match. Task: 6442738 PR community: [odoo/odoo#281923](https://github.com/odoo/odoo/pull/281923)
The Timesheets Assistant now opens in a chronological view so employees can match activities to work more naturally. It also avoids repeated loading, keeps dismissed suggestions from coming back, reduces visual flicker when selecting suggestions, and better recognizes Discord activity from a browser.
Original PR description
## [FIX] timesheet_grid: remove duplicate rpc call Before this commit, the `loadTimesheets` method is called 2 times in a row, that method does a rpc call to load the existing timesheets and so, it…
## [FIX] timesheet_grid: remove duplicate rpc call Before this commit, the `loadTimesheets` method is called 2 times in a row, that method does a rpc call to load the existing timesheets and so, it is not needed to call it 2 times since the rpc will return the exact same result. This commit removes the rpc call when we compute the suggestions to only load the timesheets when we load all the data. ## [FIX] timesheet_grid: show chronological view instead of project view Before this commit, the `by project` view were loaded first in the timesheet assistant action, to group the suggestion by project, the problem is at the beginning the view will not really show a perfect matching and so the user could think the feature does not work and he will not understand how to correctly match the suggestions shown in the view. This commit changes the view loaded by default in Timesheets Assistant to first show the chronological view, that view is more logical for the current user to rethink what he did in the past to correctly map the events to a project and a task when he generates his timesheets thanks to those events. The by project view is still useful afterwards when the system has learned the choices made by the current user. ## [FIX] timesheet_grid: ensure events are consumed forever Before this commit, the suggestions removed by the current user comes back when he changes the date and come back to the day he removes the suggestions. The reason is because a shallow copy of events consumed is made and that copy alters the duration of the initial object. This commit avoids copying the consumed events object to make sure the initial object is not altered when processing the events to remove them if they are removed before by the user. ### Steps to reproduce the issue: 1. install timesheet_grid and Activity watch, makes sure Activity watch collects some activities on your computer. 2. Go to Assistant menu in timesheets app. 3. Remove some suggestions displayed in the right panel. 4. Go to next date. 5. Come back to previous date. ### Expected Behavior: The suggestions removed should not appear again. ### Actual Behavior: The suggestions removed come back in the view. ## [FIX] timesheet_grid: fix flicker when suggestion selected Before this commit, when the user selects a suggestion in timesheet assistant, there is a small flicker appears because the height of the row grows because of the border added to highlight the suggestion selected. This commit reviews a bit the style to make sure the border bottom in the previous element is removed if the element is not selected or if the 2 consecutives suggestions are selected. ## [FIX] timesheet_grid: fix discord rules to handle discord in web Before this commit, when the user uses discord in its browser instead of the app on his computer, the discord rules don't catch the activity watch events because the tab title is different than the windows name in the app. This commit adapts the regex of Discord rules to handle the both use cases. task-[6385639](https://www.odoo.com/odoo/project.task/6385639) Forward-Port-Of: odoo/enterprise#126435 Forward-Port-Of: odoo/enterprise#124855
Spreadsheet pivot tables now apply currency exchange rates when they are inserted. This helps users working with multi-currency data see more accurate financial figures in their reports.
Original PR description
apply currency exchange rates when inserting a pivot in spreadsheet Task: 6022608
The Timesheet Assistant now combines selected suggestions that come from the same rule template into one clearer description. This avoids repetitive text separated by semicolons, making generated timesheet entries easier to read and edit.
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#127358 Forward-Port-Of: odoo/enterprise#126157
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 ---
6 changes
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
2 changes
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)