Daily updates from Odoo
Monday, August 10, 2026
80 changes
11 changes
Enhancements to existing features
Users can now sort documents with AI without needing a Studio subscription. This removes an access limitation, making AI-assisted document organization available to more customers.
Original PR description
Purpose ======= We changed our mind, and we should be able to sort documents with AI without having to pay for studio. Revert of bc15543b5ce563bdddb54198eaaf00a5c8d01e54 Task-6383816 Forward-Port-Of: odoo/enterprise#126796
Resolved issues and error corrections
TikTok Shop orders without a payment time, such as Cash On Delivery orders, can now be created and synchronized instead of failing. The order date uses the creation time temporarily, then updates once TikTok later confirms the payment time.
Original PR description
**Issue**
- TikTok Shop orders may be received without any `paid_time` value in the returned API data, particularly for _Cash On Delivery ([COD](https://seller-ph.tiktok.com/university/essay?knowledge_id=10004482&lang=en)) payments.
- When creating the order, `_create_order_from_data` accessed `order_data[‘paid_time’]` directly, which triggered a `KeyError` and prevented all orders from being fully synchronised.
**Changes**
- When creating an order, if `paid_time` is missing from the data provided by TikTok:
-> `date_order` is initialised with `create_time` as a fallback
-> The new field `tiktok_payment_pending` is set to `True` to mark the order as awaiting payment confirmation.
- During subsequent synchronisations, if `paid_time` is now provided by TikTok for a pending order,
-> `date_order` is updated with the new provided timestamp and the `tiktok_payment_pending` flag is reset.
opw-6440127
Forward-Port-Of: odoo/enterprise#126797Fixed an issue where completing a field service task after a delivery-return-return flow could get stuck and exhaust system memory. The update ensures stock movements already checked are not processed repeatedly, improving reliability for field service sales involving returned products.
Original PR description
## **Steps to reproduce:** 1. Create a Service product with Create on Order set to Task and Project set to Field service project. 2. Create a Sales Order containing a storable product and a service…
## **Steps to reproduce:** 1. Create a Service product with Create on Order set to Task and Project set to Field service project. 2. Create a Sales Order containing a storable product and a service product. 3. Confirm the Sales Order to generate the project, task, and delivery order. 4. Validate the delivery order. 5. Create and validate a return for the delivery. 6. Create a return of the return to deliver the products again, but do not validate this new delivery. 7. Open the related task and click on Mark as Done button. ## **Issue:** In a delivery -> return -> return of return workflow, the stock move goes into this code https://github.com/odoo/enterprise/blob/59b86f106862c3a364ba633a1580d9051f2fe7ca/industry_fsm_stock/models/project_task.py#L89-L90 the traversal repeatedly revisits the same stock moves through move_dest_ids, causing the loop to alternate between the same move recordsets indefinitely. As a result, the loop never terminates, eventually exhausting the memory and raising a `MemoryError`. ## **Solution:** Track the stock moves that have already been visited and continue the traversal only with unseen destination moves. Runbot Video : [Video](https://drive.google.com/file/d/1wiqggjx8T-Mtl4T1JgCsfwBbgQU18nYF/view?usp=drive_link) OPW - 6420961 Forward-Port-Of: odoo/enterprise#125962
The Indian EPF Summary report now calculates EPS contributions from the applicable basic salary amount instead of from the EPF contribution amount. This helps payroll teams produce more accurate statutory reporting and avoid incorrect pension contribution figures.
Original PR description
**Steps to Reproduce** - Create an Indian employee. - Create a payslip for this employee. - Go to **Payroll > Reporting > EPF-ECR Report**. - Create a new report with the report type **EPF Summary**. **Before This Commit** - For the EPS contribution, we were calculating `min(15,000, EPF)` and then applying **8.33%** to the resulting amount. - This resulted in an incorrect EPS contribution amount. **After This Commit** - According to Indian payroll rules, the EPS contribution is calculated as 8.33% of `min(15,000, Basic Salary)`, rather than `8.33% of the EPF amount`. - This commit corrects the EPS contribution calculation accordingly. Task: [6442399](https://www.odoo.com/odoo/project/1251/tasks/6442399) Forward-Port-Of: odoo/enterprise#126608
Pay run summary figures now refresh automatically when payslips or related leave data change, so users no longer need to manually refresh to see accurate KPIs. Employer cost totals also exclude cancelled payslips and Belgian payroll KPIs now use the correct totals, improving payroll reporting accuracy.
Original PR description
Bug : - create an unvalidated leave - on the payrun Time view, when validating the view -> click on continue -> error popup appears says you have unvalidated leave (BUG 1) - on the payrun Payslips…
Bug : - create an unvalidated leave - on the payrun Time view, when validating the view -> click on continue -> error popup appears says you have unvalidated leave (BUG 1) - on the payrun Payslips view, when canceling a payslip , the KPIs values don't change unless you refresh (BUG 2) Reason : - PayRunMixin now subscribes to its model's "update" bus event and forwards it to updatePayRun, so any pay run view whose model emits "update" keeps the summary in sync. - The natural trigger is the model's "update" bus event, but the mixin never listened to it. On top of that the relational payslip list never even emits "update" on a programmatic reload: model.load() (cog actions) and root.load() (form close, view-button reloads) rebuild the reactive root without calling notify(), which only fires on search/pager changes. The Time view gantt already worked around this because GanttModel.fetchData() calls notify() itself. Fix : - Subscribe payroll mixing to the "update" notif that will come from the underlaying models. - override the onRootLoad hook in payslipListCOntroller to call notify() which sends an "update" message when loading the data.(added a guarderail: only call the notify on the model if it's mounted) task - 6387933
Automatic values inserted into salary and signing documents now preserve numeric zero values instead of turning them into blanks. Decimal numbers are also rounded properly, helping generated documents show accurate information.
Original PR description
Before this commit, falsy values were always set to '' even when they corresponded to numbers. Moreover, floats values were not rounded. Forward-Port-Of: odoo/enterprise#127064 Forward-Port-Of: odoo/enterprise#121640
The bank reconciliation workflow now shows the "To Review" button consistently after actions such as setting an account, using reconciliation models, or handling payable and receivable entries. This helps accounting users keep transactions marked for follow-up when needed, reducing missed review steps.
Original PR description
This commit will allow to have the "to review" button when using different action: - Set account - Reco model - Payable and receivable task-6409437 Forward-Port-Of: odoo/enterprise#125287
Fixes an issue where Kenyan POS receipts could miss SCU tax information and the QR code after a successful order validation. Receipts now use the available OSCU signature to generate the QR code, helping customers and businesses receive complete compliance details.
Original PR description
The original issue coudn't be reproduced. But based on what was reported on the ticket, this should improve the behavior of the order receipt. Steps to reproduce: ------------------- * Setup the…
The original issue coudn't be reproduced. But based on what was reported on the ticket, this should improve the behavior of the order receipt. Steps to reproduce: ------------------- * Setup the l10n_ke module in 'production' mode * Make an order in POS and validate it > Observation: The receipt doesn't contain the SCU information and QR Why the fix: ------------ Based on the receipt screenshot shared on the ticket (from Odoo 19.3) we can see that some informations are shown on the ticket. The information shown are the one received from the etims api, it means the call went through and the response was received. As the qrCode URL only needs `l10n_ke_oscu_signature` to be generated, we can assume that if this field is set we can safely generate the URL and generate the QR code. https://github.com/odoo/enterprise/blob/ee7e6894ed313db844aeff5ba5e19de3ad94898e/l10n_ke_edi_oscu_pos/models/pos_order.py#L252-L256 Based on this assumption we can change the condition to return the URL or not based on the presence of `l10n_ke_oscu_signature` instead of the state of the order being `sent`. In 19.0, the receipt is showing no info at all (when 19.3 is showing some info like the signature). This is also happening because the URL was falsy. And when that is the case we do not show any SCU information https://github.com/odoo/enterprise/blob/ee7e6894ed313db844aeff5ba5e19de3ad94898e/l10n_ke_edi_oscu_pos/static/src/overrides/components/order_receipt/order_receipt.xml#L56 opw-6352120 Forward-Port-Of: odoo/enterprise#127045
Fixes an issue where international DHL Express deliveries with multiple packages could be blocked during validation. The system now recognizes DHL Express's multi-package rate message as informational when appropriate, while still blocking genuine shipping errors.
Original PR description
Steps to reproduce --- 1. Configure a `delivery_easypost` shipping method using the DHL Express carrier. 2. On an international delivery, use Put in Pack to create two or more packages. 3. Validate…
Steps to reproduce --- 1. Configure a `delivery_easypost` shipping method using the DHL Express carrier. 2. On an international delivery, use Put in Pack to create two or more packages. 3. Validate the delivery. Issue --- Validation is blocked with `DHLExpress: rate_error -- DHLExpress multi-shipment rate includes this shipment.` For an EasyPost multi-shipment order, DHL Express returns the aggregate rate on the first (master) shipment only and adds an informational `rate_error` on the order stating that this rate already covers the whole order, so the remaining shipments carry no rate of their own. The message is not a real error, but `send_shipping` raises on any carrier message whose type is not exempt: https://github.com/odoo/enterprise/blob/534b42def8ae5dc884da4398377f828c49557d6e/delivery_easypost/models/easypost_request.py#L359-L364 `_post_process_ship_response` already detects and clears exactly this harmless message, but only for a hardcoded carrier allowlist introduced in a2de5bc5a14 (`Purolator`, `DPD UK`, `UPS`) that was never extended to DHL Express, so for DHL Express the message survives, reaches the raise, and aborts an otherwise valid shipment: https://github.com/odoo/enterprise/blob/a2de5bc5a14f99b8674c3ee234e4d221b406da0f/delivery_easypost/models/easypost_request.py#L416-L434 `DHL Express` is added to that allowlist so the harmless multi-shipment `rate_error` is posted on the picking and cleared instead of raised. The guard still requires a single `rate_error` carrying the "multi-shipment rate includes this shipment." text with the rate present only on the master shipment, so genuine DHL Express errors keep blocking validation. opw-6450365 Forward-Port-Of: odoo/enterprise#127164
Belgian payroll now counts full working days correctly when an employee's schedule has shorter days mixed with longer ones. This prevents fully worked shorter days from being incorrectly treated as half-day attendance, improving payslip accuracy.
Original PR description
Bug: - Create a working sched were one day has less working hours than the rest. - when generating a payslip for an employee linked to that working sched , you'll have half-day attendances even…
Bug: - Create a working sched were one day has less working hours than the rest. - when generating a payslip for an employee linked to that working sched , you'll have half-day attendances even though he worked all his days Reason : - in _get_work_hours_split_half , when building work_data, we compare the duration worked by the employee that day not against the contractual amount of that that but instead we use self.resource_calendar_id.hours_per_day or the max hours per day on that working day , so the day with the least contractual hours on that working sched for exemple will fail this condition " float_compare(duration_sum, number_of_hours_full_day, 2) != -1" even if the employee worked that whole day. Fix: - for fixed scheds : we builed hours_per_dayofweek where we store the hours for each day of the week , later on , we check the weekday and retreive the corresponding official working hours on that day. - for variable scheds: because in this case each day can have its own officil working hours , we fetch the official hours of a certain date right before the logic that determines if it's a full or half a day. task - 6377396
Belgian payroll now automatically applies the legal minimum employee contribution for meal vouchers when the configured amount is too low. HR managers receive a non-blocking payslip warning so they know the value was adjusted while payroll processing can continue.
Original PR description
**What:** - Refactored the meal voucher salary rule computation to take the maximum between the configured employee share and the parameter-defined minimum threshold (€1.09). - Added a non-blocking warning message on the payslip to notify HR managers when an employee's configured share is below the legal minimum and has been automatically adjusted. task-6428585 Forward-Port-Of: odoo/enterprise#126194
9 changes
Enhancements to existing features
Timesheet users can now select multiple assistant suggestions more smoothly by holding the mouse button and hovering across items. Ctrl-click no longer opens an unintended new Odoo page, reducing accidental navigation and making batch actions faster.
Original PR description
Currently when the user uses ctrl + click on a suggestion, a new odoo page is opened. This is an undesirable side effect and it is removed in this commit. Also, users have to manually click on each suggestion when they want to remove them in batch, or create one timesheet for a bunch of suggestion. This commit lets user hover over suggestion with the mouse button pressed to select them. task-6385047 Forward-Port-Of: odoo/enterprise#124642
Payroll no longer automatically changes a company car’s current driver when benefits are updated. Employees who choose or receive a company car are set as the future driver instead, preventing cars already reserved for someone from being offered again and reducing unnecessary follow-up tasks.
Original PR description
. Remove the auto-assignment of the Driver based on the payroll benefits. . If an employee signs a contract and selects the car or the car gets added to the employee's benefits, he should become the car's future driver. . Don't offer in the salary configurator cars for which the future driver is filled. . Don't generate a task every time the payroll officer assigns a new driver to the car . Add the corresponding tests task-6425360
Resolved issues and error corrections
TikTok Shop orders that arrive without a payment time, such as cash-on-delivery orders, can now be created and synchronized instead of failing. The system uses the order creation time temporarily and updates it once TikTok confirms payment, helping businesses avoid missing orders.
Original PR description
**Issue**
- TikTok Shop orders may be received without any `paid_time` value in the returned API data, particularly for _Cash On Delivery ([COD](https://seller-ph.tiktok.com/university/essay?knowledge_id=10004482&lang=en)) payments.
- When creating the order, `_create_order_from_data` accessed `order_data[‘paid_time’]` directly, which triggered a `KeyError` and prevented all orders from being fully synchronised.
**Changes**
- When creating an order, if `paid_time` is missing from the data provided by TikTok:
-> `date_order` is initialised with `create_time` as a fallback
-> The new field `tiktok_payment_pending` is set to `True` to mark the order as awaiting payment confirmation.
- During subsequent synchronisations, if `paid_time` is now provided by TikTok for a pending order,
-> `date_order` is updated with the new provided timestamp and the `tiktok_payment_pending` flag is reset.
opw-6440127
Forward-Port-Of: odoo/enterprise#126797This fix prevents field service tasks from getting stuck when marking them as done after a delivery, return, and re-delivery flow. It avoids repeated stock movement checks that could exhaust memory, improving reliability for service teams handling product returns.
Original PR description
## **Steps to reproduce:** 1. Create a Service product with Create on Order set to Task and Project set to Field service project. 2. Create a Sales Order containing a storable product and a service…
## **Steps to reproduce:** 1. Create a Service product with Create on Order set to Task and Project set to Field service project. 2. Create a Sales Order containing a storable product and a service product. 3. Confirm the Sales Order to generate the project, task, and delivery order. 4. Validate the delivery order. 5. Create and validate a return for the delivery. 6. Create a return of the return to deliver the products again, but do not validate this new delivery. 7. Open the related task and click on Mark as Done button. ## **Issue:** In a delivery -> return -> return of return workflow, the stock move goes into this code https://github.com/odoo/enterprise/blob/59b86f106862c3a364ba633a1580d9051f2fe7ca/industry_fsm_stock/models/project_task.py#L89-L90 the traversal repeatedly revisits the same stock moves through move_dest_ids, causing the loop to alternate between the same move recordsets indefinitely. As a result, the loop never terminates, eventually exhausting the memory and raising a `MemoryError`. ## **Solution:** Track the stock moves that have already been visited and continue the traversal only with unseen destination moves. Runbot Video : [Video](https://drive.google.com/file/d/1wiqggjx8T-Mtl4T1JgCsfwBbgQU18nYF/view?usp=drive_link) OPW - 6420961 Forward-Port-Of: odoo/enterprise#125962
The EPF Summary report now calculates EPS contributions using the employee's basic salary, capped at ₹15,000, instead of using the EPF amount. This helps Indian payroll reports reflect the correct statutory contribution and reduces the risk of inaccurate compliance reporting.
Original PR description
**Steps to Reproduce** - Create an Indian employee. - Create a payslip for this employee. - Go to **Payroll > Reporting > EPF-ECR Report**. - Create a new report with the report type **EPF Summary**. **Before This Commit** - For the EPS contribution, we were calculating `min(15,000, EPF)` and then applying **8.33%** to the resulting amount. - This resulted in an incorrect EPS contribution amount. **After This Commit** - According to Indian payroll rules, the EPS contribution is calculated as 8.33% of `min(15,000, Basic Salary)`, rather than `8.33% of the EPF amount`. - This commit corrects the EPS contribution calculation accordingly. Task: [6442399](https://www.odoo.com/odoo/project/1251/tasks/6442399) Forward-Port-Of: odoo/enterprise#126608
The Timesheet Assistant now combines similar selected suggestions into one natural description, such as “Discussing with A and B” instead of repeating the same phrase with semicolons. This makes generated timesheet entries easier to read and reduces manual cleanup for users.
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#126157
This fixes an issue where Kenyan POS receipts could miss the official SCU details and QR code even after the tax authority response was received. Receipts can now generate the verification QR code when the required signature is available, improving customer receipt completeness and compliance visibility.
Original PR description
The original issue coudn't be reproduced. But based on what was reported on the ticket, this should improve the behavior of the order receipt. Steps to reproduce: ------------------- * Setup the…
The original issue coudn't be reproduced. But based on what was reported on the ticket, this should improve the behavior of the order receipt. Steps to reproduce: ------------------- * Setup the l10n_ke module in 'production' mode * Make an order in POS and validate it > Observation: The receipt doesn't contain the SCU information and QR Why the fix: ------------ Based on the receipt screenshot shared on the ticket (from Odoo 19.3) we can see that some informations are shown on the ticket. The information shown are the one received from the etims api, it means the call went through and the response was received. As the qrCode URL only needs `l10n_ke_oscu_signature` to be generated, we can assume that if this field is set we can safely generate the URL and generate the QR code. https://github.com/odoo/enterprise/blob/ee7e6894ed313db844aeff5ba5e19de3ad94898e/l10n_ke_edi_oscu_pos/models/pos_order.py#L252-L256 Based on this assumption we can change the condition to return the URL or not based on the presence of `l10n_ke_oscu_signature` instead of the state of the order being `sent`. In 19.0, the receipt is showing no info at all (when 19.3 is showing some info like the signature). This is also happening because the URL was falsy. And when that is the case we do not show any SCU information https://github.com/odoo/enterprise/blob/ee7e6894ed313db844aeff5ba5e19de3ad94898e/l10n_ke_edi_oscu_pos/static/src/overrides/components/order_receipt/order_receipt.xml#L56 opw-6352120 Forward-Port-Of: odoo/enterprise#127045
DHL Express deliveries with multiple packages through EasyPost are no longer blocked by an informational carrier message. This lets valid international shipments proceed while still stopping genuine carrier errors.
Original PR description
Steps to reproduce --- 1. Configure a `delivery_easypost` shipping method using the DHL Express carrier. 2. On an international delivery, use Put in Pack to create two or more packages. 3. Validate…
Steps to reproduce --- 1. Configure a `delivery_easypost` shipping method using the DHL Express carrier. 2. On an international delivery, use Put in Pack to create two or more packages. 3. Validate the delivery. Issue --- Validation is blocked with `DHLExpress: rate_error -- DHLExpress multi-shipment rate includes this shipment.` For an EasyPost multi-shipment order, DHL Express returns the aggregate rate on the first (master) shipment only and adds an informational `rate_error` on the order stating that this rate already covers the whole order, so the remaining shipments carry no rate of their own. The message is not a real error, but `send_shipping` raises on any carrier message whose type is not exempt: https://github.com/odoo/enterprise/blob/534b42def8ae5dc884da4398377f828c49557d6e/delivery_easypost/models/easypost_request.py#L359-L364 `_post_process_ship_response` already detects and clears exactly this harmless message, but only for a hardcoded carrier allowlist introduced in a2de5bc5a14 (`Purolator`, `DPD UK`, `UPS`) that was never extended to DHL Express, so for DHL Express the message survives, reaches the raise, and aborts an otherwise valid shipment: https://github.com/odoo/enterprise/blob/a2de5bc5a14f99b8674c3ee234e4d221b406da0f/delivery_easypost/models/easypost_request.py#L416-L434 `DHL Express` is added to that allowlist so the harmless multi-shipment `rate_error` is posted on the picking and cleared instead of raised. The guard still requires a single `rate_error` carrying the "multi-shipment rate includes this shipment." text with the rate present only on the master shipment, so genuine DHL Express errors keep blocking validation. opw-6450365 Forward-Port-Of: odoo/enterprise#127164
Belgian payroll now automatically applies the legal minimum employee contribution for meal vouchers when a lower amount is configured. HR managers also see a non-blocking payslip warning so they know the amount was adjusted for compliance.
Original PR description
**What:** - Refactored the meal voucher salary rule computation to take the maximum between the configured employee share and the parameter-defined minimum threshold (€1.09). - Added a non-blocking warning message on the payslip to notify HR managers when an employee's configured share is below the legal minimum and has been automatically adjusted. task-6428585
13 changes
Enhancements to existing features
Spreadsheet date selections now insert a real date value instead of a numeric representation. This makes entered dates clearer and more reliable for users working with spreadsheets.
Original PR description
Task: 6353692
Belgian payroll now includes the upcoming fiscal employment bonus rate changes for low-wage workers from August 2026 and the general rate change from 2028. This helps payroll calculations stay aligned with planned Belgian tax rules and reduces manual adjustments for employers.
Original PR description
Starting from August 2026: - The increased fiscal rate for low-wage workers (Volet B) rises from 52.54% to 63% (and to 72% in 2028). - The general fiscal rate (Volet A) rises from 33.14% to 35% starting in 2028. This adds new rule parameters for the fiscal rates and updates computation logic to apply these rates Task-6438319 Forward-Port-Of: odoo/enterprise#126713
All signers can now choose whether to add a frame when adopting their signature, not only internal Odoo users. The signing dialog also has clearer borders around key controls, making the public signing experience easier to understand and use.
Original PR description
Version: 19.0 Before this PR: The 'Frame' checkbox in the 'Adopt Your Signature' dialog was only shown to internal Odoo users (users with the `base.group_user` group).Also, on the public signing page, the Full Name input, the Frame checkbox and the Auto/Draw/Load buttons had no visible border After this PR: The 'Frame' checkbox is now rendered for every signer in the 'Adopt Your Signature' dialog. The Full Name input, Frame checkbox and Auto/Draw/Load buttons now have a visible border. Taskid-4610728 Forward-Port-Of: odoo/enterprise#126789
Resolved issues and error corrections
TikTok Shop orders without a recorded payment time, such as Cash On Delivery orders, can now be created and synchronized instead of failing. The order uses the creation time temporarily and is updated later when TikTok provides the payment confirmation time.
Original PR description
**Issue**
- TikTok Shop orders may be received without any `paid_time` value in the returned API data, particularly for _Cash On Delivery ([COD](https://seller-ph.tiktok.com/university/essay?knowledge_id=10004482&lang=en)) payments.
- When creating the order, `_create_order_from_data` accessed `order_data[‘paid_time’]` directly, which triggered a `KeyError` and prevented all orders from being fully synchronised.
**Changes**
- When creating an order, if `paid_time` is missing from the data provided by TikTok:
-> `date_order` is initialised with `create_time` as a fallback
-> The new field `tiktok_payment_pending` is set to `True` to mark the order as awaiting payment confirmation.
- During subsequent synchronisations, if `paid_time` is now provided by TikTok for a pending order,
-> `date_order` is updated with the new provided timestamp and the `tiktok_payment_pending` flag is reset.
opw-6440127
Forward-Port-Of: odoo/enterprise#126797Planning slots for employees without a fixed working schedule now appear in the Timesheet/Planning Analysis report. This gives managers a more complete and accurate view of planned work for fully flexible employees.
Original PR description
Steps to reproduce: ------------------- 1. Install project_timesheet_forecast. 2. Create a fully flexible employee (without a working schedule). 3. Create a planning slot. 4. Open the Timesheet/planning Analysis report. Issue: ------ Planning slots for fully flexible employees are not included in the report. Cause: ------ https://github.com/odoo/enterprise/blob/7d4b43cfa1934856d41992cbe8242eaf62575c2c/project_timesheet_forecast/report/timesheet_forecast_report.py#L142-L161 The report assumes every resource has a working schedule and only considers resources with a resource calendar. As a result, resources without a calendar are excluded from the report. Solution: --------- Handle resources without a working schedule separately so that planning slots for fully flexible employees are also included in the report. opw-6361571 Forward-Port-Of: odoo/enterprise#126682 Forward-Port-Of: odoo/enterprise#125072
This fixes a problem where marking a field service task as done could get stuck and run out of memory after a delivery was returned and then re-delivered. The system now avoids repeatedly processing the same stock movements, making task completion reliable in this return workflow.
Original PR description
## **Steps to reproduce:** 1. Create a Service product with Create on Order set to Task and Project set to Field service project. 2. Create a Sales Order containing a storable product and a service…
## **Steps to reproduce:** 1. Create a Service product with Create on Order set to Task and Project set to Field service project. 2. Create a Sales Order containing a storable product and a service product. 3. Confirm the Sales Order to generate the project, task, and delivery order. 4. Validate the delivery order. 5. Create and validate a return for the delivery. 6. Create a return of the return to deliver the products again, but do not validate this new delivery. 7. Open the related task and click on Mark as Done button. ## **Issue:** In a delivery -> return -> return of return workflow, the stock move goes into this code https://github.com/odoo/enterprise/blob/59b86f106862c3a364ba633a1580d9051f2fe7ca/industry_fsm_stock/models/project_task.py#L89-L90 the traversal repeatedly revisits the same stock moves through move_dest_ids, causing the loop to alternate between the same move recordsets indefinitely. As a result, the loop never terminates, eventually exhausting the memory and raising a `MemoryError`. ## **Solution:** Track the stock moves that have already been visited and continue the traversal only with unseen destination moves. Runbot Video : [Video](https://drive.google.com/file/d/1wiqggjx8T-Mtl4T1JgCsfwBbgQU18nYF/view?usp=drive_link) OPW - 6420961 Forward-Port-Of: odoo/enterprise#125962
This fix ensures expected working hours use the overtime rule's selected period instead of always treating it as days. It prevents incorrect extra overtime entries, such as multiple shifts being marked as overtime in weeks with public holidays.
Original PR description
In the function '_get_expected_hours_from_contract', there is an improper super call where period is set to 'days' instead of passing in the value already passed into the original function. So when an overtime rule has a quantity_period not set to 'day', downstream calculations can go astray. In one example, when regenerating overtimes, weeks containing public holidays will have multiple shifts set as overtime shifts rather than just the shift on the holiday. Steps to recreate: 1. Create employee 2. Give them contract and flexible work schedule 3. On overtime ruleset, set overtime rule's quantity_period to Weeks (default is days) 4. Add a typical weeks worth of attendances 5. Create public holiday on one of the attendance days 6. Regenerate overtimes 7. See extra hours have been added to multiple shifts that week By fixing this super call, issues like these should be resolved going forward. opw-6373300 Forward-Port-Of: odoo/enterprise#126094
This fix ensures Sendcloud delivery requests include the recipient tax number required for international DPD shipments. It also uses the accepted English tax label and adds fallbacks for required customs fields, helping users validate deliveries without Sendcloud rejection errors.
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#126351 Forward-Port-Of: odoo/enterprise#124245
Point of Sale receipts for Kenyan electronic invoicing will now show SCU information and the QR code whenever the required signature is available. This helps customers receive complete compliance details on receipts even when the order status is not yet marked as sent.
Original PR description
The original issue coudn't be reproduced. But based on what was reported on the ticket, this should improve the behavior of the order receipt. Steps to reproduce: ------------------- * Setup the…
The original issue coudn't be reproduced. But based on what was reported on the ticket, this should improve the behavior of the order receipt. Steps to reproduce: ------------------- * Setup the l10n_ke module in 'production' mode * Make an order in POS and validate it > Observation: The receipt doesn't contain the SCU information and QR Why the fix: ------------ Based on the receipt screenshot shared on the ticket (from Odoo 19.3) we can see that some informations are shown on the ticket. The information shown are the one received from the etims api, it means the call went through and the response was received. As the qrCode URL only needs `l10n_ke_oscu_signature` to be generated, we can assume that if this field is set we can safely generate the URL and generate the QR code. https://github.com/odoo/enterprise/blob/ee7e6894ed313db844aeff5ba5e19de3ad94898e/l10n_ke_edi_oscu_pos/models/pos_order.py#L252-L256 Based on this assumption we can change the condition to return the URL or not based on the presence of `l10n_ke_oscu_signature` instead of the state of the order being `sent`. In 19.0, the receipt is showing no info at all (when 19.3 is showing some info like the signature). This is also happening because the URL was falsy. And when that is the case we do not show any SCU information https://github.com/odoo/enterprise/blob/ee7e6894ed313db844aeff5ba5e19de3ad94898e/l10n_ke_edi_oscu_pos/static/src/overrides/components/order_receipt/order_receipt.xml#L56 opw-6352120 Forward-Port-Of: odoo/enterprise#127045
This fix prevents users in a multi-company setup from hitting an unresolvable error when creating quality-related operation steps. It ensures quality team email aliases have the right company context, so manufacturing quality flows work reliably even when the full quality control app is not installed.
Original PR description
This commit actually reverts [1] and manually forwards [2]. Suppose `mrp_workorder` installed and `quality_control` uninstalled. Because of the default value provided by [1], the only existing quality team is linked to the first company. As a result, when using another company, if the user tries to create an operation step (i.e., a QCP), it will raise an error when the onchange tries to load the default team in charge: https://github.com/odoo/enterprise/blob/f9c99f937bd64e5a0acb4bc88b1fc08249250c4e/quality/models/quality.py#L141-L142 However, the `quality` module doesn't provide any view to create such a team. tldr The module raises an error that is actually impossible to solve... Let's avoid it in the above situation. [1] https://github.com/odoo/enterprise/commit/f9c99f937bd64e5a0acb4bc88b1fc08249250c4e [2] https://github.com/odoo/enterprise/commit/8cd5c9322bef7db49a90d4aef844dd0ba267058e Forward-Port-Of: odoo/enterprise#126439 Forward-Port-Of: odoo/enterprise#126364
This fix ensures Helpdesk tickets no longer appear to have met their SLA as soon as the SLA is assigned. SLA status now reflects whether the ticket actually reached the required stage, while late completion remains tracked separately.
Original PR description
### Steps to Reproduce: 1. Go to Helpdesk and create an SLA 2. Create a new Ticket 3. Use Studio to add the field `sla_reached` 4. Apply the SLA 5. Notice that the field is True ### Description of…
### Steps to Reproduce: 1. Go to Helpdesk and create an SLA 2. Create a new Ticket 3. Use Studio to add the field `sla_reached` 4. Apply the SLA 5. Notice that the field is True ### Description of the issue/feature this PR addresses: **Issue:** The `_compute_sla_reached` method in on `helpdesk_ticket.py` determines whether an SLA has been reached by checking if `exceeded_hours` is less than 0 on its `helpdesk.sla.status` records. Since every newly assigned SLA has a future deadline, `exceeded_hours` starts as negative (AKA <0), so the ticket is immediately flagged as `sla_reached = True` before any progress has been made. Conversely, a ticket that reaches its target stage after the deadline has `exceeded_hours >= 0`, so it's incorrectly flagged as `sla_reached = False` even though the SLA target was genuinely reached (it was just late). **Solution:** Change the domain in `_compute_sla_reached` to check `reached_datetime != False` instead of `exceeded_hours < 0`, matching the field already listed in the method's `@api.depends` and the same field `_sla_reach()` sets when a ticket enters its target stage. This makes `sla_reached` reflect actual stage progression rather than a time-remaining calculation. ### Current behavior before PR: A newly created ticket with a pending SLA shows `sla_reached = True` and `sla_success = True` immediately upon creation, before the ticket has moved to any target stage. SImilarly, a ticket that reaches its target stage after the SLA deadline is incorrectly marked `sla_reached = False`. ### Desired behavior after PR: `sla_reached` is False on ticket creation and only becomes True once the ticket actually enters the SLA's target stage. Lateness continues to be tracked separately and correctly via `sla_reached_late` opw-6361851 Forward-Port-Of: odoo/enterprise#126432
DHL Express deliveries sent through EasyPost can now be validated when an international order is split into multiple packages. The system correctly treats EasyPost's multi-package rate message as informational, while still blocking genuine shipping errors.
Original PR description
Steps to reproduce --- 1. Configure a `delivery_easypost` shipping method using the DHL Express carrier. 2. On an international delivery, use Put in Pack to create two or more packages. 3. Validate…
Steps to reproduce --- 1. Configure a `delivery_easypost` shipping method using the DHL Express carrier. 2. On an international delivery, use Put in Pack to create two or more packages. 3. Validate the delivery. Issue --- Validation is blocked with `DHLExpress: rate_error -- DHLExpress multi-shipment rate includes this shipment.` For an EasyPost multi-shipment order, DHL Express returns the aggregate rate on the first (master) shipment only and adds an informational `rate_error` on the order stating that this rate already covers the whole order, so the remaining shipments carry no rate of their own. The message is not a real error, but `send_shipping` raises on any carrier message whose type is not exempt: https://github.com/odoo/enterprise/blob/534b42def8ae5dc884da4398377f828c49557d6e/delivery_easypost/models/easypost_request.py#L359-L364 `_post_process_ship_response` already detects and clears exactly this harmless message, but only for a hardcoded carrier allowlist introduced in a2de5bc5a14 (`Purolator`, `DPD UK`, `UPS`) that was never extended to DHL Express, so for DHL Express the message survives, reaches the raise, and aborts an otherwise valid shipment: https://github.com/odoo/enterprise/blob/a2de5bc5a14f99b8674c3ee234e4d221b406da0f/delivery_easypost/models/easypost_request.py#L416-L434 `DHL Express` is added to that allowlist so the harmless multi-shipment `rate_error` is posted on the picking and cleared instead of raised. The guard still requires a single `rate_error` carrying the "multi-shipment rate includes this shipment." text with the rate present only on the master shipment, so genuine DHL Express errors keep blocking validation. opw-6450365 Forward-Port-Of: odoo/enterprise#127164
VoIP contact searches and keypad suggestions now also compare against standardized phone numbers, not just the number as originally saved. This helps users find the right contact even when country codes are added automatically or stored numbers use a different format.
Original PR description
Before this fix, the keypad's callee suggestions only matched the search term against the raw `phone` field of contacts. When the user input was automatically prefixed with a country code (e.g. +86), the match could fail if the stored phone number lacked the international prefix. Now `phone_sanitized` is also sent to the frontend via the Store, and the callee suggestion matching falls back to the E164 sanitized number when the raw phone field does not match. Task-6290760 compr https://github.com/odoo/odoo/pull/278018 Forward-Port-Of: odoo/enterprise#127290 Forward-Port-Of: odoo/enterprise#124797
11 changes
Enhancements to existing features
All signers can now choose whether to add a frame when adopting their signature, not just internal Odoo users. The public signing dialog also has clearer borders around key fields and buttons, making it easier for customers and external recipients to complete documents confidently.
Original PR description
Version: 19.0 Before this PR: The 'Frame' checkbox in the 'Adopt Your Signature' dialog was only shown to internal Odoo users (users with the `base.group_user` group).Also, on the public signing page, the Full Name input, the Frame checkbox and the Auto/Draw/Load buttons had no visible border After this PR: The 'Frame' checkbox is now rendered for every signer in the 'Adopt Your Signature' dialog. The Full Name input, Frame checkbox and Auto/Draw/Load buttons now have a visible border. Taskid-4610728 Forward-Port-Of: odoo/enterprise#126789
Belgian payroll now includes the upcoming fiscal employment bonus rate changes for low-wage workers from August 2026 and the general rate increase planned for 2028. This helps payroll calculations stay aligned with future Belgian tax rules without manual rate adjustments.
Original PR description
Starting from August 2026: - The increased fiscal rate for low-wage workers (Volet B) rises from 52.54% to 63% (and to 72% in 2028). - The general fiscal rate (Volet A) rises from 33.14% to 35% starting in 2028. This adds new rule parameters for the fiscal rates and updates computation logic to apply these rates Task-6438319 Forward-Port-Of: odoo/enterprise#126713
Resolved issues and error corrections
TikTok Shop orders that arrive without a payment timestamp, such as Cash On Delivery orders, can now still be created and synchronized. The system uses the order creation time until payment confirmation is later received, preventing affected orders from being blocked.
Original PR description
**Issue**
- TikTok Shop orders may be received without any `paid_time` value in the returned API data, particularly for _Cash On Delivery ([COD](https://seller-ph.tiktok.com/university/essay?knowledge_id=10004482&lang=en)) payments.
- When creating the order, `_create_order_from_data` accessed `order_data[‘paid_time’]` directly, which triggered a `KeyError` and prevented all orders from being fully synchronised.
**Changes**
- When creating an order, if `paid_time` is missing from the data provided by TikTok:
-> `date_order` is initialised with `create_time` as a fallback
-> The new field `tiktok_payment_pending` is set to `True` to mark the order as awaiting payment confirmation.
- During subsequent synchronisations, if `paid_time` is now provided by TikTok for a pending order,
-> `date_order` is updated with the new provided timestamp and the `tiktok_payment_pending` flag is reset.
opw-6440127
Forward-Port-Of: odoo/enterprise#126797Fixed an issue where completing a field service task could get stuck and run out of memory after a delivery was returned and then re-delivered. The system now avoids repeatedly processing the same stock movements, improving reliability for teams handling product returns linked to field service work.
Original PR description
## **Steps to reproduce:** 1. Create a Service product with Create on Order set to Task and Project set to Field service project. 2. Create a Sales Order containing a storable product and a service…
## **Steps to reproduce:** 1. Create a Service product with Create on Order set to Task and Project set to Field service project. 2. Create a Sales Order containing a storable product and a service product. 3. Confirm the Sales Order to generate the project, task, and delivery order. 4. Validate the delivery order. 5. Create and validate a return for the delivery. 6. Create a return of the return to deliver the products again, but do not validate this new delivery. 7. Open the related task and click on Mark as Done button. ## **Issue:** In a delivery -> return -> return of return workflow, the stock move goes into this code https://github.com/odoo/enterprise/blob/59b86f106862c3a364ba633a1580d9051f2fe7ca/industry_fsm_stock/models/project_task.py#L89-L90 the traversal repeatedly revisits the same stock moves through move_dest_ids, causing the loop to alternate between the same move recordsets indefinitely. As a result, the loop never terminates, eventually exhausting the memory and raising a `MemoryError`. ## **Solution:** Track the stock moves that have already been visited and continue the traversal only with unseen destination moves. Runbot Video : [Video](https://drive.google.com/file/d/1wiqggjx8T-Mtl4T1JgCsfwBbgQU18nYF/view?usp=drive_link) OPW - 6420961 Forward-Port-Of: odoo/enterprise#125962
Project tasks created from templates now skip archived users when assigning people through project roles. This prevents inactive team members from being added to new customer project work, keeping task ownership aligned with current staffing.
Original PR description
Steps to reproduce: ------------------------------------------------- 1. Install the `sale_project` module 2. Create a test user with Project User rights 3. Create a Project Role with the Created…
Steps to reproduce:
-------------------------------------------------
1. Install the `sale_project` module
2. Create a test user with Project User rights
3. Create a Project Role with the Created User as a Team Member
4. Create a Template Project as follows:
* Add one task to the template project
* Add the created Project role to the Task
5. Create a Service Type Product with:
* Create on order: Project
* Project Template: Created Template
6. Archive the Created User
7. Create and Confirm the Sale Order with the Created Product
Observation:
-------------------------------------------------
The generated task is assigned to the archived user, although the archived user is no longer part of the Project Role.
Issue:
-------------------------------------------------
While creating Project and Tasks from template, the context disable active record filtering (e.g., `active_test=False`), causing the assignment logic to fetch both active and inactive/archived users linked to the role. https://github.com/odoo/odoo/blob/8ec646e51497b38d34ea59296e0fc8644a50ee3a/odoo/orm/models.py#L4868
After that, during the `copy_data` method, It takes all the users from the roles without checking weather user is active or not
https://github.com/odoo/odoo/blob/8ec646e51497b38d34ea59296e0fc8644a50ee3a/addons/project/models/project_task.py#L890-L904
And even if we pass only Active users from this method, on moving further, it reassigns the users from roles without checking the Active field of the user
https://github.com/odoo/enterprise/blob/5abb147f9bf725daafc202d8259a5bb8a9b78d94/project_enterprise/models/project_task.py#L501-L503
https://github.com/odoo/enterprise/blob/5abb147f9bf725daafc202d8259a5bb8a9b78d94/project_enterprise/models/project_task.py#L544-L553
Due to this, the Archived User is also assigned to the tasks from the project roles
Solution:
-------------------------------------------------
Apply a `filtered('active')` check directly on the project role's users `(role.user_ids)` within the core task-copying logic in both `project` and `project_enterprise` modules. This ensures archived users are universally excluded from task assignments during template copying, regardless of what triggers the template instantiation.
Related Community PR: https://github.com/odoo/odoo/pull/274426
opw-6350841Helpdesk tickets no longer show an SLA as reached immediately after it is applied. The status now updates only when the ticket actually reaches the SLA target stage, making SLA reporting more accurate for on-time and late tickets.
Original PR description
### Steps to Reproduce: 1. Go to Helpdesk and create an SLA 2. Create a new Ticket 3. Use Studio to add the field `sla_reached` 4. Apply the SLA 5. Notice that the field is True ### Description of…
### Steps to Reproduce: 1. Go to Helpdesk and create an SLA 2. Create a new Ticket 3. Use Studio to add the field `sla_reached` 4. Apply the SLA 5. Notice that the field is True ### Description of the issue/feature this PR addresses: **Issue:** The `_compute_sla_reached` method in on `helpdesk_ticket.py` determines whether an SLA has been reached by checking if `exceeded_hours` is less than 0 on its `helpdesk.sla.status` records. Since every newly assigned SLA has a future deadline, `exceeded_hours` starts as negative (AKA <0), so the ticket is immediately flagged as `sla_reached = True` before any progress has been made. Conversely, a ticket that reaches its target stage after the deadline has `exceeded_hours >= 0`, so it's incorrectly flagged as `sla_reached = False` even though the SLA target was genuinely reached (it was just late). **Solution:** Change the domain in `_compute_sla_reached` to check `reached_datetime != False` instead of `exceeded_hours < 0`, matching the field already listed in the method's `@api.depends` and the same field `_sla_reach()` sets when a ticket enters its target stage. This makes `sla_reached` reflect actual stage progression rather than a time-remaining calculation. ### Current behavior before PR: A newly created ticket with a pending SLA shows `sla_reached = True` and `sla_success = True` immediately upon creation, before the ticket has moved to any target stage. SImilarly, a ticket that reaches its target stage after the SLA deadline is incorrectly marked `sla_reached = False`. ### Desired behavior after PR: `sla_reached` is False on ticket creation and only becomes True once the ticket actually enters the SLA's target stage. Lateness continues to be tracked separately and correctly via `sla_reached_late` opw-6361851 Forward-Port-Of: odoo/enterprise#126432
Attachments added to employee records and leave requests now create documents in the expected HR folders instead of the general Employees root folder. Sick leave attachments also reliably generate related documents, making employee document management more complete and easier to navigate.
Original PR description
Before this commit, when adding an attachment to a leave or a employee version the mixin was configured to create the document in the root folder of Employees which was not very convenient. In addition, when creating a Sick leave with an attachment, no document was ever created. This commit fix both those bugs. Task-6095811 Forward-Port-Of: odoo/enterprise#120539 Forward-Port-Of: odoo/enterprise#112993
This fixes an issue where companies other than the first one could be blocked from creating quality-related operation steps when only the basic quality features were installed. The change ensures quality team email aliases have the right company context, preventing an error users could not resolve from the available screens.
Original PR description
This commit actually reverts [1] and manually forwards [2]. Suppose `mrp_workorder` installed and `quality_control` uninstalled. Because of the default value provided by [1], the only existing quality team is linked to the first company. As a result, when using another company, if the user tries to create an operation step (i.e., a QCP), it will raise an error when the onchange tries to load the default team in charge: https://github.com/odoo/enterprise/blob/f9c99f937bd64e5a0acb4bc88b1fc08249250c4e/quality/models/quality.py#L141-L142 However, the `quality` module doesn't provide any view to create such a team. tldr The module raises an error that is actually impossible to solve... Let's avoid it in the above situation. [1] https://github.com/odoo/enterprise/commit/f9c99f937bd64e5a0acb4bc88b1fc08249250c4e [2] https://github.com/odoo/enterprise/commit/8cd5c9322bef7db49a90d4aef844dd0ba267058e Forward-Port-Of: odoo/enterprise#126439 Forward-Port-Of: odoo/enterprise#126364
This fix helps Kenyan point-of-sale receipts display the expected SCU tax information and QR code when a valid tax signature is available. It reduces the chance of customers receiving receipts missing required electronic tax verification details after a successful order.
Original PR description
The original issue coudn't be reproduced. But based on what was reported on the ticket, this should improve the behavior of the order receipt. Steps to reproduce: ------------------- * Setup the…
The original issue coudn't be reproduced. But based on what was reported on the ticket, this should improve the behavior of the order receipt. Steps to reproduce: ------------------- * Setup the l10n_ke module in 'production' mode * Make an order in POS and validate it > Observation: The receipt doesn't contain the SCU information and QR Why the fix: ------------ Based on the receipt screenshot shared on the ticket (from Odoo 19.3) we can see that some informations are shown on the ticket. The information shown are the one received from the etims api, it means the call went through and the response was received. As the qrCode URL only needs `l10n_ke_oscu_signature` to be generated, we can assume that if this field is set we can safely generate the URL and generate the QR code. https://github.com/odoo/enterprise/blob/ee7e6894ed313db844aeff5ba5e19de3ad94898e/l10n_ke_edi_oscu_pos/models/pos_order.py#L252-L256 Based on this assumption we can change the condition to return the URL or not based on the presence of `l10n_ke_oscu_signature` instead of the state of the order being `sent`. In 19.0, the receipt is showing no info at all (when 19.3 is showing some info like the signature). This is also happening because the URL was falsy. And when that is the case we do not show any SCU information https://github.com/odoo/enterprise/blob/ee7e6894ed313db844aeff5ba5e19de3ad94898e/l10n_ke_edi_oscu_pos/static/src/overrides/components/order_receipt/order_receipt.xml#L56 opw-6352120 Forward-Port-Of: odoo/enterprise#127045
DHL Express shipments with multiple packages through EasyPost no longer fail validation because of an informational carrier message. This lets businesses complete valid international deliveries while still blocking genuine shipping errors.
Original PR description
Steps to reproduce --- 1. Configure a `delivery_easypost` shipping method using the DHL Express carrier. 2. On an international delivery, use Put in Pack to create two or more packages. 3. Validate…
Steps to reproduce --- 1. Configure a `delivery_easypost` shipping method using the DHL Express carrier. 2. On an international delivery, use Put in Pack to create two or more packages. 3. Validate the delivery. Issue --- Validation is blocked with `DHLExpress: rate_error -- DHLExpress multi-shipment rate includes this shipment.` For an EasyPost multi-shipment order, DHL Express returns the aggregate rate on the first (master) shipment only and adds an informational `rate_error` on the order stating that this rate already covers the whole order, so the remaining shipments carry no rate of their own. The message is not a real error, but `send_shipping` raises on any carrier message whose type is not exempt: https://github.com/odoo/enterprise/blob/534b42def8ae5dc884da4398377f828c49557d6e/delivery_easypost/models/easypost_request.py#L359-L364 `_post_process_ship_response` already detects and clears exactly this harmless message, but only for a hardcoded carrier allowlist introduced in a2de5bc5a14 (`Purolator`, `DPD UK`, `UPS`) that was never extended to DHL Express, so for DHL Express the message survives, reaches the raise, and aborts an otherwise valid shipment: https://github.com/odoo/enterprise/blob/a2de5bc5a14f99b8674c3ee234e4d221b406da0f/delivery_easypost/models/easypost_request.py#L416-L434 `DHL Express` is added to that allowlist so the harmless multi-shipment `rate_error` is posted on the picking and cleared instead of raised. The guard still requires a single `rate_error` carrying the "multi-shipment rate includes this shipment." text with the rate present only on the master shipment, so genuine DHL Express errors keep blocking validation. opw-6450365 Forward-Port-Of: odoo/enterprise#127164
Flexible-schedule employees who take a few hours off will no longer have the entire day treated as time off. This keeps attendance and overtime calculations accurate and shows only the actual leave period in the Time Off Gantt view.
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#123778
2 changes
Enhancements to existing features
Belgian payroll calculations now include the upcoming fiscal employment bonus rate changes starting in August 2026 and 2028. This helps payroll teams apply the correct tax reductions for low-wage workers and general employment bonuses as Belgian rules change.
Original PR description
Starting from August 2026: - The increased fiscal rate for low-wage workers (Volet B) rises from 52.54% to 63% (and to 72% in 2028). - The general fiscal rate (Volet A) rises from 33.14% to 35% starting in 2028. This adds new rule parameters for the fiscal rates and updates computation logic to apply these rates Task-6438319 Forward-Port-Of: odoo/enterprise#126713
Resolved issues and error corrections
Helpdesk tickets now show an SLA as reached only after the ticket actually reaches the required stage. This prevents new tickets from appearing compliant too early and ensures late completions are still recognized correctly while lateness remains tracked separately.
Original PR description
### Steps to Reproduce: 1. Go to Helpdesk and create an SLA 2. Create a new Ticket 3. Use Studio to add the field `sla_reached` 4. Apply the SLA 5. Notice that the field is True ### Description of…
### Steps to Reproduce: 1. Go to Helpdesk and create an SLA 2. Create a new Ticket 3. Use Studio to add the field `sla_reached` 4. Apply the SLA 5. Notice that the field is True ### Description of the issue/feature this PR addresses: **Issue:** The `_compute_sla_reached` method in on `helpdesk_ticket.py` determines whether an SLA has been reached by checking if `exceeded_hours` is less than 0 on its `helpdesk.sla.status` records. Since every newly assigned SLA has a future deadline, `exceeded_hours` starts as negative (AKA <0), so the ticket is immediately flagged as `sla_reached = True` before any progress has been made. Conversely, a ticket that reaches its target stage after the deadline has `exceeded_hours >= 0`, so it's incorrectly flagged as `sla_reached = False` even though the SLA target was genuinely reached (it was just late). **Solution:** Change the domain in `_compute_sla_reached` to check `reached_datetime != False` instead of `exceeded_hours < 0`, matching the field already listed in the method's `@api.depends` and the same field `_sla_reach()` sets when a ticket enters its target stage. This makes `sla_reached` reflect actual stage progression rather than a time-remaining calculation. ### Current behavior before PR: A newly created ticket with a pending SLA shows `sla_reached = True` and `sla_success = True` immediately upon creation, before the ticket has moved to any target stage. SImilarly, a ticket that reaches its target stage after the SLA deadline is incorrectly marked `sla_reached = False`. ### Desired behavior after PR: `sla_reached` is False on ticket creation and only becomes True once the ticket actually enters the SLA's target stage. Lateness continues to be tracked separately and correctly via `sla_reached_late` opw-6361851 Forward-Port-Of: odoo/enterprise#126432
4 changes
Enhancements to existing features
Belgian payroll now includes upcoming fiscal employment bonus rate changes from August 2026 and 2028. This helps ensure payslip calculations stay aligned with legal changes for low-wage workers and general employment bonus rates.
Original PR description
Starting from August 2026: - The increased fiscal rate for low-wage workers (Volet B) rises from 52.54% to 63% (and to 72% in 2028). - The general fiscal rate (Volet A) rises from 33.14% to 35% starting in 2028. This adds new rule parameters for the fiscal rates and updates computation logic to apply these rates Task-6438319 Forward-Port-Of: odoo/enterprise#126713
Resolved issues and error corrections
This fix prevents UrbanPiper delivery orders from triggering an internal error when the same preparation print request is processed twice during order acceptance. It makes the flow more reliable for point-of-sale users and removes a test workaround that is no longer needed.
Original PR description
mark_urbanpiper_prep_order_as_printed() raised ValueError when the 'urbanpiper_printed' flag was already set, to stop the same order from printing its preparation ticket twice…
mark_urbanpiper_prep_order_as_printed() raised ValueError when the 'urbanpiper_printed' flag was already set, to stop the same order from printing its preparation ticket twice (https://github.com/odoo/enterprise/pull/103894, Task-5353283). Accepting an UrbanPiper order fires this RPC from two places for the same order: synchronously from TicketScreen, and again via the DELIVERY_ORDER_COUNT bus notification the accept flow itself broadcasts. Under load, both requests race for the row lock; Odoo's retrying() replays the loser on lock contention, and by the time it replays the winner has already committed, so the loser hits the already-printed branch and raises. The raise is an unhandled ValueError, so it surfaces as a 500 and fails any tour that accepts an order (test_frontend.py, test_order_receipt.py), intermittently and CI-timing-dependent only. The only caller (pos_store.js: _sendDeliveryOrderForPreparation) already wraps the RPC in try/catch and treats a caught exception exactly like a falsy return value: either way it just skips sending the ticket to preparation. No other code reads or writes urbanpiper_printed, and no webhook path calls this method, so returning False is behaviorally identical for every real caller and safe to make the default. This also removes the mark_urbanpiper_prep_order_as_printed_patch monkeypatch added alongside the original raise in test_01_order_flow: it existed solely to swallow this exact ValueError for that one tour, which is no longer needed now that the method itself is idempotent. runbot error: 941514
The Follow-Up Report no longer crashes when users turn the No Follow-Up option on or off for invoices paid in multiple installments. This keeps credit control workflows reliable when some installments are already paid and others remain open.
Original PR description
Steps to Reproduce: 1. Configure Payment Term (Accounting > Configuration > Payment Terms) containing multiple installments. 2. Create a Customer Invoice with this Payment Term. 3. Post the invoice.…
Steps to Reproduce:
1. Configure Payment Term (Accounting > Configuration > Payment Terms) containing multiple installments.
2. Create a Customer Invoice with this Payment Term.
3. Post the invoice.
4. Register a payment and fully reconcile one of the installments.
5. Navigate to the customer's Follow-Up Report (Accounting > Reporting > Partner Ledger > Report: Follow-Up Report).
6. Navigate to remaining open installment/account move line for that invoice.
7. Turn On or Off the No Follow-Up toggle for the invoice.
An error occurs when enabling or disabling the No Follow-Up toggle.
Issue:
Enabling or disabling the No Follow-Up toggle on a remaining open installment in the Follow-Up Report raises a server error when the invoice contains multiple installments and one or more installments are already fully reconciled.
Root Cause:
The Follow-Up Report only loads and sends non-fully reconciled account move lines from the JavaScript side through all_line_ids. In action_toggle_no_followup(), when the selected line belongs to an invoice, the code retrieves all receivable/payable lines of the invoice, including fully reconciled installments:
```
move.line_ids.filtered(
lambda line: line.account_type in ('asset_receivable', 'liability_payable'),
)
```
The method then attempts to map every receivable/payable line to a report line ID using aml_id_to_line_id. Since fully reconciled installments are not present in all_line_ids, they are missing from the mapping dictionary, causing a KeyError when accessing:
`aml_id_to_line_id[line.id]
`
Fix:
Restricted the impacted lines to those present in the report by adding a check that the account move line exists in aml_id_to_line_id before performing the mapping:
```
lambda line: line.account_type in ('asset_receivable', 'liability_payable')
and line.id in aml_id_to_line_id
```
opw-6245448
Forward-Port-Of: odoo/enterprise#126156Creating a Pay Run could fail when a custom Studio many-to-one field was added, because the system sent the full related record instead of only its identifier. This fix makes Pay Run creation handle those custom relationship fields correctly, reducing errors for payroll teams using Studio customizations.
Original PR description
[FIX] hr_payroll: Studio fields in VersionPayrunListController
Adding a many2one field to hr.payslip.run will cause a postgresql error
when creating a new hr.payslip.run through this controller. This is due
to a dictionary being sent in the API call rather than just the ID of
the related record.
The function 'buildRawRecord' normalizes other many2one fields
(company_id and structure_id) to their ID field. My change extends this
normalization process to any many2one field.
Steps to recreate on runbot:
1. Add many2one field to form view of hr.payslip.run
2. Attempt to create new Pay Run
Notes: There is a change from a constant-time normalization to a
linear-time normalization. Max number of columns a postgres table allows
for is 1600, so this shouldn't lead to any performance implications down
the line18 changes
Enhancements to existing features
Shared document links now keep the user's current context, including the active spreadsheet sheet, even when redirects occur. This helps recipients land directly on the intended sheet instead of needing to navigate there manually.
Original PR description
## Description of the issue/feature this PR addresses: Current behavior before PR: - Fragment parameters in document URLs (such as the active sheet) were lost during redirects, causing the client to lose its current state. Changes introduced: - Redirects now preserve fragment parameters generically, so URL state is maintained without needing feature-specific controller logic. - Shareable document links now include the current `sheet_id` as a URL parameter. Desired behavior after PR is merged: - When a shared document link is opened, the correct sheet is restored automatically, preserving the user’s context. Task: [4760176](https://www.odoo.com/odoo/project/2328/tasks/4760176)
Document sorting with AI is now available without requiring a Studio subscription. This makes the AI sorting capability easier to access for document workflows and removes an unnecessary paid dependency.
Original PR description
Purpose ======= We changed our mind, and we should be able to sort documents with AI without having to pay for studio. Revert of bc15543b5ce563bdddb54198eaaf00a5c8d01e54 Task-6383816 Forward-Port-Of: odoo/enterprise#126796
US payroll calculations now use the date employees are paid, rather than the pay period end date, for year-to-date totals and tax reporting. This helps ensure year-end payslips paid in January are reported in the correct tax year, improving W-2 accuracy.
Original PR description
*: l10n_us_ Purpose: For the US official tax reporting (e.g. W-2), the totals are computed based on the date when the payslip was paid out to the employees, not the pay period in which the wages were earned. It is necessary because a very likely occurrence of a December payslip being paid out in January of the following year must count towards the W-2 of the following year. Current Behavior: The US salary rules and related year-to-date calculations are computed based on the closing date of the pay period. Expected Behavior: The US salary rules and related year-to-date calculations are computed based on the paid_date of the payslip. task-5450399
Existing spreadsheet dashboards across several business apps have been refreshed to use the newer carousel data view experience. This makes dashboard navigation and presentation more modern while keeping the underlying dashboard content unchanged.
Original PR description
This task aims to update existing dashboards to use new carousel data views. Task: 6412458
Belgian payroll users can now see the replacement amount directly in the worked days section of a payslip. This makes absence valuations clearer and reduces the need to rely on generated reports to understand how amounts were calculated.
Original PR description
Period valuations for absences were only computed when generating reports, making it unclear for users how amounts were determined. This adds a "Replacement Amount" column to the worked days tab of the payslip to display the period valuation. Task-6377538
Australian payroll pay runs now show superannuation contributions and tax withholding directly in the payslip list, along with totals at the top of the pay run. This helps payroll teams review key employer contribution and withholding amounts faster without opening individual payslips.
Original PR description
super contribution column (SUPER + SUPER.CONTRIBUTION lines) is shown in the payrun's view
. in the list view of payslips
tax withholding column is shown in the payrun's view
. in the list view of payslips
Total of super contributions and tax withholding is shown in the top of the payrun view
task-6423860Odoo now creates tracking log messages in batches when many records are updated at once, instead of processing each record separately. This improves performance for large updates while preserving attachments and tracking details for each document.
Original PR description
When tracked fields change on many records without a subtype change, each record's tracking message was logged individually through `_message_log`, resulting in one `_message_create` call per record. Grouping the logs by author and going through `_message_log_batch` creates all the messages of a batch in a single `_message_create` call. To allow this, `_message_log_batch` now takes `attachment_ids` and `tracking_values` as dicts keyed by record id instead of flat values, lifting the previous limitation that batch logs could not carry attachments or tracking values for more than one document. task-6354178
When logging a VoIP call, Odoo now automatically suggests the business record the user already has open, reducing manual selection and helping calls get linked to the right customer, ticket, project, or document. The update also prevents access errors for non-HR users when call logging touches HR-related options.
Original PR description
When log a call, if the user has a opened form of a record, we will try select this record in the log call wizard. Task-[6204862](https://www.odoo.com/odoo/5778/tasks/6204862)
Belgian payroll users can now correct previously submitted 281.10 and 281.45 forms using the Belcotax reference number. The system also warns teams when payroll changes make submitted forms inconsistent, helping them identify and handle corrections on time.
Original PR description
Task: 5470512
Belgian payroll dashboards now warn when an employee has Dimona declarations that are not yet covered by a payslip, even though a pay run exists without that employee. This helps payroll teams spot missing payslips sooner and reduce compliance or processing oversights.
Original PR description
This commit adds a dashboard warning for employees that have dimonas not covered by any payslip yet a payrun exists in which they are not included. task- 6201337
Resolved issues and error corrections
TikTok Shop orders that arrive without a payment timestamp, such as Cash On Delivery orders, can now be created and synchronized instead of failing. The system temporarily uses the order creation time and marks payment as pending, then updates the order once TikTok provides the payment confirmation time.
Original PR description
**Issue**
- TikTok Shop orders may be received without any `paid_time` value in the returned API data, particularly for _Cash On Delivery ([COD](https://seller-ph.tiktok.com/university/essay?knowledge_id=10004482&lang=en)) payments.
- When creating the order, `_create_order_from_data` accessed `order_data[‘paid_time’]` directly, which triggered a `KeyError` and prevented all orders from being fully synchronised.
**Changes**
- When creating an order, if `paid_time` is missing from the data provided by TikTok:
-> `date_order` is initialised with `create_time` as a fallback
-> The new field `tiktok_payment_pending` is set to `True` to mark the order as awaiting payment confirmation.
- During subsequent synchronisations, if `paid_time` is now provided by TikTok for a pending order,
-> `date_order` is updated with the new provided timestamp and the `tiktok_payment_pending` flag is reset.
opw-6440127
Forward-Port-Of: odoo/enterprise#126797Belgian payroll now calculates employment bonuses correctly for both full-time and part-time employees. This improves payroll accuracy and helps align payslips and social security reporting with official Belgian guidance.
Original PR description
[IMP] l10n_be: fix the calculation of employement bonus A According to the document: https://www.socialsecurity.be/employer/instructions/dmfa/fr/latest/instructions/deductions/workers_reductions/workbonus.html We were calculating the employement bonus A according to S = (W/H) x U calculation in the document. But it is only for part-time workers. Now, we consider full time cases as well and the final result is accurate. task - 6334065
The EPF Summary report now calculates EPS contributions based on basic salary, in line with Indian payroll rules. This helps payroll teams avoid incorrect statutory contribution amounts in reporting.
Original PR description
**Steps to Reproduce** - Create an Indian employee. - Create a payslip for this employee. - Go to **Payroll > Reporting > EPF-ECR Report**. - Create a new report with the report type **EPF Summary**. **Before This Commit** - For the EPS contribution, we were calculating `min(15,000, EPF)` and then applying **8.33%** to the resulting amount. - This resulted in an incorrect EPS contribution amount. **After This Commit** - According to Indian payroll rules, the EPS contribution is calculated as 8.33% of `min(15,000, Basic Salary)`, rather than `8.33% of the EPF amount`. - This commit corrects the EPS contribution calculation accordingly. Task: [6442399](https://www.odoo.com/odoo/project/1251/tasks/6442399) Forward-Port-Of: odoo/enterprise#126608
Activity Watch can now use a task ID captured directly from a configured rule pattern, such as one found in a page URL or window title. This makes timesheet suggestions more accurate when the task is already visible in the activity name, reducing reliance on less precise guessing from past activity.
Original PR description
Before this commit, when the task_id to link to activity watch can be found in the URL or window/tab name but it is not possible for the user to create a regex to be able to automatically say to the system the task_id is found in the event name recorded by activity watch. This commit adds the possibility to define `task_id` group name inside the regex to be able to take that information instead of searching which task is linked to that event based on previous key event or the frequency of the current user. task-[6384029](https://www.odoo.com/odoo/project.task/6384029) Forward-Port-Of: odoo/enterprise#126588 Forward-Port-Of: odoo/enterprise#124113
Automatic values inserted into salary contract and signature documents now keep valid zero values instead of turning them into blanks. Decimal values are also rounded properly, helping business documents show accurate amounts.
Original PR description
Before this commit, falsy values were always set to '' even when they corresponded to numbers. Moreover, floats values were not rounded. Forward-Port-Of: odoo/enterprise#127064 Forward-Port-Of: odoo/enterprise#121640
This update improves how the Belgian payroll module calculates DMFA reported days, especially for employees working 40-hour weeks. It helps prevent overstated day totals in payroll declarations, improving reporting accuracy and reducing correction work.
Original PR description
Currently, there is problem with DMFA Number of days Computation, If we have case where the employee working hour is 40h/week, the calculations mentioned in report 67.5 days, which is too many, In this PR, expected to update the number of days on DMFA computations task-6432418
This fixes an issue in the Timesheet timer form where clicking Save or Reset could trigger an error instead of completing the action. The change helps users record or adjust time entries without interruption.
Original PR description
469a7d200ce ([FIX] timesheet_grid: focus on description input, odoo/enterprise#125993) was forward-ported from saas-19.4 onto master a day after 6d3c12624e4 had turned `descriptionFieldRef` into a `signal.ref()`, which has no `.el`: clicking Save or Reset in the timesheet timer form throws on `undefined.querySelector` in onPatched -- the very refocus the original fix was adding. The read is correct on the back branches, where the ref is still a `useRef()`; call the ref on master.
Code cleanup and technical improvements
German Elster tax submission features have been moved into the main German reports module instead of staying in a separate add-on. This simplifies setup and maintenance while keeping the same reporting capability available in one place.
Original PR description
After introducing the module l10n_de_reports_elster in 19.0 ( PR: https://github.com/odoo/enterprise/pull/121805 ), we need to merge it in l10n_de_reports in master as it doesn't make sense to keep this module as a standalone module. l10n_de_reports_elster is now part of l10n_de_reports. task-6396219
4 changes
Enhancements to existing features
Belgian payroll now includes the upcoming fiscal employment bonus rate changes starting in August 2026 and 2028. This helps ensure payslip calculations stay aligned with new rules for low-wage workers and the general fiscal rate.
Original PR description
Starting from August 2026: - The increased fiscal rate for low-wage workers (Volet B) rises from 52.54% to 63% (and to 72% in 2028). - The general fiscal rate (Volet A) rises from 33.14% to 35% starting in 2028. This adds new rule parameters for the fiscal rates and updates computation logic to apply these rates Task-6438319 Forward-Port-Of: odoo/enterprise#126713
Resolved issues and error corrections
Fixes an error that could occur when users turned the No Follow-Up setting on or off for invoices paid in multiple installments. This keeps the Follow-Up Report usable when some installments are already settled and others remain open.
Original PR description
Steps to Reproduce: 1. Configure Payment Term (Accounting > Configuration > Payment Terms) containing multiple installments. 2. Create a Customer Invoice with this Payment Term. 3. Post the invoice.…
Steps to Reproduce:
1. Configure Payment Term (Accounting > Configuration > Payment Terms) containing multiple installments.
2. Create a Customer Invoice with this Payment Term.
3. Post the invoice.
4. Register a payment and fully reconcile one of the installments.
5. Navigate to the customer's Follow-Up Report (Accounting > Reporting > Partner Ledger > Report: Follow-Up Report).
6. Navigate to remaining open installment/account move line for that invoice.
7. Turn On or Off the No Follow-Up toggle for the invoice.
An error occurs when enabling or disabling the No Follow-Up toggle.
Issue:
Enabling or disabling the No Follow-Up toggle on a remaining open installment in the Follow-Up Report raises a server error when the invoice contains multiple installments and one or more installments are already fully reconciled.
Root Cause:
The Follow-Up Report only loads and sends non-fully reconciled account move lines from the JavaScript side through all_line_ids. In action_toggle_no_followup(), when the selected line belongs to an invoice, the code retrieves all receivable/payable lines of the invoice, including fully reconciled installments:
```
move.line_ids.filtered(
lambda line: line.account_type in ('asset_receivable', 'liability_payable'),
)
```
The method then attempts to map every receivable/payable line to a report line ID using aml_id_to_line_id. Since fully reconciled installments are not present in all_line_ids, they are missing from the mapping dictionary, causing a KeyError when accessing:
`aml_id_to_line_id[line.id]
`
Fix:
Restricted the impacted lines to those present in the report by adding a check that the account move line exists in aml_id_to_line_id before performing the mapping:
```
lambda line: line.account_type in ('asset_receivable', 'liability_payable')
and line.id in aml_id_to_line_id
```
opw-6245448
Forward-Port-Of: odoo/enterprise#126156Timesheets now use each employee's scheduled weekly hours when checking weekly totals, rather than comparing part-time schedules against a full-time reference. This prevents correct timesheets from being incorrectly marked as under target for employees on flexible or reduced schedules.
Original PR description
Root cause: For an employee on a flexible working schedule, the weekly overtime shown in the timesheet grid compares the entered total against the full time equivalent of the schedule instead of its…
Root cause: For an employee on a flexible working schedule, the weekly overtime shown in the timesheet grid compares the entered total against the full time equivalent of the schedule instead of its own weekly hours. The target is sent by _count_daily_working_hours: https://github.com/odoo/enterprise/blob/27f3a05ca8a5ad6e8d4537dac4f613e60931f0cd/timesheet_grid/models/hr_employee.py#L117-L119 It reads full_time_required_hours, but since https://github.com/odoo/odoo/commit/5cb102546ea4370b4fe5e4a4f37cccdc90c263fd this field holds the company full time reference used to compute the work time rate, and the hours the schedule actually expects per week are on hours_per_week. So for a part time schedule at 80% of a 42 hours full time, the grid expects 42 hours instead of 33h36 and the weekly total shows in red with a wrong negative overtime. Fix: In _count_daily_working_hours, send the hours_per_week of the calendar as the weekly target and keep full_time_required_hours as a fallback when it is not set. For a full time schedule both fields hold the same value so nothing changes there. The result key stays the same so the grid renderers need no change. Steps to reproduce: 1. Go to Employees > Configuration > Working Schedules and create a schedule with Schedule Type Flexible, Full Time Equivalent 42:00 and Total 33:36 hours per week 2. Assign this schedule to an employee linked to a user 3. As that user, go to Timesheets > My Timesheets 4. On a past week, log 6:44 on Monday and 6:43 from Tuesday to Friday on a project 5. Check the Time Spent weekly total => the weekly total 33:36 shows in red with a -8:24 overtime while the employee worked exactly the 33h36 expected by the schedule Ticket [link](https://www.odoo.com/odoo/project.task/6352965) opw-6352965
The Shop Floor view now preserves the intended order of work orders after refreshes or filter changes. This prevents confusing reshuffling and keeps production teams seeing work orders ordered by status and scheduled start date.
Original PR description
Records already in cache are intended to be sorted by their position in `recordCacheIds` to preserve the previously computed display order. However, `recordCacheIds` stores database record ids (`resId`), and currently the cache lookup incorrectly uses `id` instead. As a result, every lookup returns `-1`, and could lead to inconsistent ordering. Steps to reproduce: 1. Create several manufacturing orders with work orders assigned to the same work center. 2. Give the work orders different states and scheduled start dates. 3. Open Shop Floor and display that work center. 4. Refresh the view or change a filter so the records are recomputed. It should use `resId` so the previously computed display order remains, which is based on state and scheduled start date. Related: odoo/enterprise#74421 opw-6402233 Forward-Port-Of: odoo/enterprise#125928
4 changes
Enhancements to existing features
Belgian payroll now includes the upcoming fiscal employment bonus rate changes for low-wage workers from August 2026 and the general rate increase from 2028. This helps payroll calculations stay aligned with future Belgian tax rules and reduces manual compliance updates.
Original PR description
Starting from August 2026: - The increased fiscal rate for low-wage workers (Volet B) rises from 52.54% to 63% (and to 72% in 2028). - The general fiscal rate (Volet A) rises from 33.14% to 35% starting in 2028. This adds new rule parameters for the fiscal rates and updates computation logic to apply these rates Task-6438319 Forward-Port-Of: odoo/enterprise#126713
Resolved issues and error corrections
Australian payroll now correctly checks unused leave for each individual employee when preparing payslips. This prevents unused leave from being missed when multiple payslips are processed together, improving payroll accuracy.
Original PR description
`_l10n_au_get_unused_leave_by_type` compared leave allocations to `self.employee_id` while looping payslips. On a multi-recordset that is the whole employee set, so the match never holds and unused leave is skipped. Use `payslip.employee_id` so each payslip keeps its own allocations. task-6451508
This update prevents an error when users customize worksheet design templates in Studio after upgrading from an older version. It ensures the correct company-specific worksheet template is selected, so businesses with multi-company setups can continue editing templates without disruption.
Original PR description
Since `company_id` on `worksheet.template` changed due to this a973d7d from a Many2many to a Many2one field. For example, in v17, a single worksheet template linked to 3 companies via the m2m field…
Since `company_id` on `worksheet.template` changed due to this a973d7d from a Many2many to a Many2one field.
For example, in v17, a single worksheet template linked to 3 companies via the m2m field was returned as 1 record when opening Design Template. After the upgrade in v18, company_id became m2o, and the same data is split into 3 separate records (one per company).
When trying to add a customization via Studio, the search [fetches](https://github.com/odoo/enterprise/blob/18.0/worksheet/controllers/main.py#L12) records based on the model set on the worksheet. In the new version, Studio
[creates](https://github.com/odoo/enterprise/blob/18.0/worksheet/models/worksheet_template.py#L112)
a new model, but for existing records the
model is the same across the 3 worksheet records tied to the same template. This causes the search to match all 3 records and raise a SingletonError.
```py
Traceback (most recent call last):
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 2856, in __call__
response = request._serve_db()
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 2331, in _serve_db
raise self._update_served_exception(exc)
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 2329, in _serve_db
return service_model.retrying(serve_func, env=self.env)
File "/home/odoo/src/odoo/19.0/odoo/service/model.py", line 188, in retrying
result = func()
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 2384, in _serve_ir_http
response = self.dispatcher.dispatch(rule.endpoint, args)
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 2599, in dispatch
result = self.request.registry['ir.http']._dispatch(endpoint)
File "/home/odoo/src/odoo/19.0/odoo/addons/base/models/ir_http.py", line 353, in _dispatch
result = endpoint(**request.params)
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 838, in route_wrapper
result = endpoint(self, *args, **params_ok)
File "/home/odoo/src/enterprise/19.0/industry_fsm_report/controllers/main.py", line 9, in edit_view
action = super().edit_view(view_id, studio_view_arch, operations, model, context)
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 838, in route_wrapper
result = endpoint(self, *args, **params_ok)
File "/home/odoo/src/enterprise/19.0/worksheet/controllers/main.py", line 17, in edit_view
worksheet_template_to_change._generate_qweb_report_template()
File "/home/odoo/src/enterprise/19.0/worksheet/models/worksheet_template.py", line 490, in _generate_qweb_report_template
new_arch = self._get_qweb_arch(worksheet_template.model_id, report_name, form_view_id)
File "/home/odoo/src/enterprise/19.0/worksheet/models/worksheet_template.py", line 460, in _get_qweb_arch
if 'name' in row_node.attrib and row_node.attrib['name'] not in self._get_qweb_arch_omitted_fields() and row_node.attrib['name'] in form_view_fields:
File "/home/odoo/src/enterprise/19.0/worksheet/models/worksheet_template.py", line 378, in _get_qweb_arch_omitted_fields
'x_%s_id' % self.res_model.replace('.', '_'), 'x_name', # redundant
File "/home/odoo/src/odoo/19.0/odoo/orm/fields.py", line 1657, in __get__
record.ensure_one()
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 5942, in ensure_one
raise ValueError("Expected singleton: %s" % self)
ValueError: Expected singleton: worksheet.template(3, 14, 18)
```
OPW: 6389190This fixes an issue where cancelled point-of-sale refunds in Mexico could still be counted when creating a global invoice. Businesses can now create global invoices correctly when an order has both cancelled and paid refunds, avoiding erroneous negative totals and invoice failures.
Original PR description
Steps to reproduce: ------------------- 1. Install `l10n_mx_edi_pos` and set the company to Mexico. 2. In PoS, make an order with one product and pay it. 3. Open the order in the backend, click…
Steps to reproduce: ------------------- 1. Install `l10n_mx_edi_pos` and set the company to Mexico. 2. In PoS, make an order with one product and pay it. 3. Open the order in the backend, click "Return Products" to make a refund, but don't pay it, cancel it instead. 4. From the same order, click "Return Products" again to make a second refund, and pay it normally. 5. Go to the orders list, select the main order and the paid refund (not the cancelled one), then Actions > Create Global Invoice. -> Observation: error in the global invoice. In the CFDI tab of the main order the line is "Send Global In Error", and hovering on it the detail says "Failed to distribute some negative lines". Why: ---- When we make the global invoice, we remove the refunds from the order. A cancelled refund was never paid, so we should not count it. But we were counting it too. So we removed the refund amount twice in our case, one for the paid refund, and one for the cancelled one, and we end up with an order with negative amount that cannot be distributed. The fix: -------- We now skip the cancelled orders when we search the refunds, the same way it is done above when we collect the refunded orders. opw-6261404
4 changes
Enhancements to existing features
The point of sale preparation display now retrieves pending orders much faster, especially when many orders are shown. This reduces waiting time for staff and helps busy locations keep their kitchen or preparation screens responsive.
Original PR description
Improve the execution time of _get_stageless_orders_in_display by replacing the OR condition with IN(subquery) by using UNION ALL to improve query performance. Benchmarks: | display orders count | Time Before | Time After | |-------------------------------|---------------------|------------------| | 1k | 84 s | 0.416 s | | 500 | 37.6 s | 0.150 s | | 250 | 19.4 s | 0.110 s | | 100 | 8 s | 0.068 s | opw-6420949
Belgian payroll now includes updated fiscal employment bonus rates scheduled from August 2026 and 2028. This helps payroll calculations stay aligned with upcoming legal changes for low-wage workers and general fiscal bonus treatment.
Original PR description
Starting from August 2026: - The increased fiscal rate for low-wage workers (Volet B) rises from 52.54% to 63% (and to 72% in 2028). - The general fiscal rate (Volet A) rises from 33.14% to 35% starting in 2028. This adds new rule parameters for the fiscal rates and updates computation logic to apply these rates Task-6438319
Resolved issues and error corrections
The Chilean electronic invoicing module now recognizes five new response codes introduced by SII. This prevents supplier electronic tax documents from getting stuck during claim or acceptance status processing after the SII regulatory update.
Original PR description
**Before this PR:** After the implementation of Resolution 161 of November 13th 2025, SII responses included keys not supported by the current l10n_cl_edi implementation. This resulted in supplier DTEs not being processed as they were before the change, because the five new keys were not found in Odoo's current `l10n_cl_claim` field, causing that the documents with these responses, were kept in a loop not solved. **After this PR:** The five new values from the resolution, along with their translations, were added to the selector field, fixing the process flow. **SII Reference:** https://www.sii.cl/normativa_legislacion/resoluciones/2025/reso161.pdf (see Event Code, page 5)
This fix prevents one Dutch Digipoort tax return record with missing accounting information from stopping status updates for all other records. The system now handles the missing closing entry gracefully, improving reliability for Dutch tax 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