Daily updates from Odoo
Navigate
Branch
Wednesday, July 8, 2026
439 changes
20 changes
Enhancements to existing features
Hong Kong payroll can now use the scheduled payroll data update process. This helps keep standard, unmodified salary rules up to date automatically, reducing manual maintenance for payroll teams.
Original PR description
Currently, the "Payroll: Update data" cron doesn't work for HK payroll as we never set up the _get_data_files_to_update. We can set up the list of data files to keep up to date to better support our users by automatically keeping non-edited salary rules up to date. task-6360339 Forward-Port-Of: odoo/enterprise#122777
Belgian blackbox POS sessions now warn cashiers when they need to be closed within the required 24-hour period, helping ensure Z reports are sent on time. Session and sales reports now handle negative order lines in the required refund summaries, and self-ordering with a blackbox now requires the appropriate Belgian blackbox self-ordering support.
Original PR description
Belgian fiscal regulations require a blackbox POS session to be closed at least once every 24h so the Z reports are sent to the FDM. The POS now warns the cashier once the session should be closed. Also: - include negative lines of regular orders in the negative quantities summary of session reports, and show them as positive amounts in the refund tables of the sale details report as required by the SPF - require `l10n_be_pos_blackbox_self_order` when self-ordering (mobile/kiosk) is enabled with a blackbox community PR: https://github.com/odoo/odoo/pull/273994
Belgian payroll rules are updated with the new employment bonus parameters taking effect on 1 July 2026 and 1 September 2026. This helps ensure payslip calculations remain aligned with Belgian payroll requirements for the affected periods.
Original PR description
Update the employment bonus parameters for 1st July 2026 and 1st September 2026. task-6369633 Forward-Port-Of: odoo/enterprise#123262
Resolved issues and error corrections
This fix prevents Belgian CODA bank statement imports from crashing when the file contains a type 4 block. Businesses using Belgian bank statement imports can now process these files reliably without manual workarounds.
Original PR description
### Issue: After the fix in commit (https://github.com/odoo/enterprise/commit/3ef8ae7a6b8eb362e18c74dfab9aadce792b5dc2), importing a CODA file containing a type 4 block raises a traceback ### Cause: That commit introduced `communication_struct_by_ref_move`, which iterates over all lines and accesses `line['communication_struct']` Type 4 lines are not assigned a `communication_struct` value by the parser in `_get_coda_file_statements` Accessing the key directly raises a `KeyError` in `_get_coda_final_statements` in `communication_struct_by_ref_move` ### Steps to reproduce: - Install `l10n_be_coda` - Switch to the BE company - Create a Bank Journal with account `BE33737018595246` - Go to the Accounting Dashboard and import a CODA file containing a type 4 block (Like the one on the ticket) Before the fix, a traceback is raised on import opw-6363148 Forward-Port-Of: odoo/enterprise#123222
Pasting document links into an empty message no longer adds an unnecessary blank line at the start. Messages look cleaner while still keeping links separated when text is already present.
Original PR description
Before this commit, adding document links always prepended a line break before the generated links. When the composer was empty, this resulted in messages starting with an unnecessary blank line. This commit only inserts a line break when the composer already contains text, avoiding the extra spacing while preserving the separation between existing content and pasted links. task-[5947683](https://www.odoo.com/odoo/project/1519/tasks/5947683) Forward-Port-Of: odoo/enterprise#120952
Scanning a component product barcode on the shop floor now opens or selects the matching item even when that component is tied to a specific manufacturing operation. This prevents operators from being blocked by scans that previously appeared to do nothing, improving reliability during work order processing.
Original PR description
When scanning a product barcode in the shopfloor, it would not simulate a click on the product if the corresponding BoM line was linked to an operation. Steps to reproduce: ------------------- * Create a BoM for product A with a BoM line for product B and link it to any operation. * Create a WO for product A and confirm it * Open the shopfloor with the WO and scan the barcode of product B > Observation: Nothing happens Why the fix: ------------ We simulate the onClick for the product even if the move is not marked manual_consumption. opw-6268665 Forward-Port-Of: odoo/enterprise#122337
New planning shifts now use the employee or user's local timezone when setting default hours. This prevents shifts from appearing offset, such as 10 AM to 6 PM instead of the expected 8 AM to 4 PM, improving scheduling accuracy.
Original PR description
Before: When creating a new shift, we set 8 AM - 4 PM as the default hours in UTC. With the timezone in Belgium, this becomes 10 AM - 6 PM. After: Change the timezone of the new shift to match the user's timezone. This will make the hours always be from 8 to 4 (working hours) --- task-6285596 Forward-Port-Of: odoo/enterprise#120062
This fixes an error that could block Sendcloud batch deliveries when a transfer was split into multiple packages. Businesses can now validate those deliveries normally, ensuring labels are generated without interruption.
Original PR description
Issue ----- When using Sendcloud batch deliveries, users get a traceback if the transfer is split into multiple packages. Steps to reproduce ----- - Setup Sendcloud - Enable batch delivery - Create a…
Issue ----- When using Sendcloud batch deliveries, users get a traceback if the transfer is split into multiple packages. Steps to reproduce ----- - Setup Sendcloud - Enable batch delivery - Create a delivery for 2 units of a product - Delivery method set to sendcloud - Put each unit in a separate package - Validate the delivery > Traceback Cause ----- Bug introduced by #90302 (so only present in 19.3+). When using the batch deliveries option, the packages are sent in a single list. This means that, in `_prepare_parcel`, when we iterate over the packages, `pkg` can be a list https://github.com/odoo/enterprise/blob/2c89c8e5da7dcb83d5a60c9616de4d5d07f0b9ab/delivery_sendcloud/models/sendcloud_service.py#L462 This means that `pkg.name` will fail https://github.com/odoo/enterprise/blob/2c89c8e5da7dcb83d5a60c9616de4d5d07f0b9ab/delivery_sendcloud/models/sendcloud_service.py#L481 Considering that a batch delivery uses the same reference number on all labels, we can pass `None` as a fallback to `_get_unique_order_number_reference` (it is an accepted value) https://github.com/odoo/enterprise/blob/2c89c8e5da7dcb83d5a60c9616de4d5d07f0b9ab/delivery_sendcloud/models/sendcloud_service.py#L388-L393 ----- Ticket: opw-6267928
The live field service map no longer recalculates routes when the current user's position changes. This avoids unnecessary route processing and helps preserve routing service tokens without changing the displayed routes.
Original PR description
The routing fetching in `updateUserPosition` of the `MapModel` should not be triggered for the live map. Right now, when changing user position, the routes are fetched again. However, for the live map, this should have no effect on routes, as they start from the user pins instead of the current user's position. This will avoid computing all over routes and preserve tokens. task-6307279
Fixes an issue where opening the AI chat could hide the dialog used to add snippets in the fullscreen mailing editor. Users can now add mailing blocks normally without the editor appearing frozen or blocking save and discard actions.
Original PR description
When an AI chatbox is active, all non-error dialog overlays are set to be behind the chatbox through their z-index. This causes an issue where the dialog overlay that adds new snippets to a mailing is placed behind the fullscreen mailing editor, preventing its use and freezing the use of some commands (save & discard). This commit restores the snippet dialog's z-index to its original value. Steps to reproduce: - Create a new mailing - Select a builder-enabled theme (such as Events Promo) - Open a new AI chat by clicking the AI icon in the top right - Open the fullscreen editor - Click on the Headers block category task-6321624 Forward-Port-Of: odoo/enterprise#123276
This fix corrects how eco cheques are calculated in Belgian payroll so employees receive the right voucher amounts. It also updates related payroll and accounting tests to help prevent future mistakes in payslip validation, reporting, and performance checks.
Original PR description
Forward-Port-Of: odoo/enterprise#119074
This fix prevents delivery tracking from failing when EasyPost returns an empty tracker value. Users can continue viewing shipment information without encountering an error screen in affected delivery workflows.
Original PR description
The PR https://github.com/odoo/enterprise/pull/111833 handled the specific case when the tracker data is missing from the EasyPost response, however in certain cases `tracker` key exists, but it has a `None` value, which leads to a traceback when trying to access the stock move:
```
File "/home/odoo/src/enterprise/18.0/delivery_easypost/models/easypost_request.py", line 392, in get_tracking_link
public_url = shipment.get('tracker', {}).get('public_url')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'get'
```
This commit provides a fallback to avoid getting the error from the side of the user.
opw-6270242
Forward-Port-Of: odoo/enterprise#119400The Swiss payroll time off request form now consistently shows the start date field. This prevents confusion and ensures employees can always enter the required date regardless of the type of time off request.
Original PR description
The time off request view was showing the request_date_from field conditionally, which makes no sense as you would always need to pick a date for a time off no matter which unit the request uses. runbot-241099
The website now shows the exact discount percentage configured for subscription products when prices are displayed tax-included. This prevents customers from seeing an incorrect lower discount, such as 4% instead of 5%, due to rounding precision.
Original PR description
Steps to reproduce: 1. Install eCommerce and Subscriptions. 2. Create a 21% Excluded tax. 3. Create a subscription product with a price of 45 with 21% tax and enable "Accept One-Time" in the…
Steps to reproduce: 1. Install eCommerce and Subscriptions. 2. Create a 21% Excluded tax. 3. Create a subscription product with a price of 45 with 21% tax and enable "Accept One-Time" in the Recurring Prices tab. 4. Publish the product on the website under the Sales tab. 5. Create a pricelist for 6 months recurring with two lines: - If min quantity is 0, then 0% discount - If min quantity is 2, then 5% discount 6. Set "Display Product Prices" to "Tax Included" in the Settings. 7. Open the product on the website, select the 6-month plan, and increase quantity to 2. Issue: The discount percentage displayed on the website shows 4% instead of the configured 5%. Why this happens: In `_get_additionnal_combination_info`, the discount is reverse-calculated from the tax-included price vs the tax-included sales price. When the 21% tax is included to both prices, it introduces a floating-point precision loss (4.9954..%), which floor() then truncates to 4%. Fix: When the pricelist rule uses 'percentage' discount, read `percent_price` directly from the pricing rule instead of reverse-calculating from tax-adjusted prices, as it represents the exact discount percentage the merchant configured with no floating-point involvement. opw-6224735 Forward-Port-Of: odoo/enterprise#121654
Marketing Automation now shows the correct reason when a campaign participant is removed because they no longer match the campaign filter. This avoids misleading users with a "Record deleted" message when the underlying lead or record still exists.
Original PR description
`sync_participants` calls `action_set_unlink` on every participant whose record is no longer in the campaign domain, and `action_set_unlink` writes "Record deleted" on each scheduled trace. The…
`sync_participants` calls `action_set_unlink` on every participant whose record is no longer in the campaign domain, and `action_set_unlink` writes "Record deleted" on each scheduled trace. The removed bucket also contains records that still exist but no longer match the campaign filter, so the cancelled trace dialog shows "Record deleted" even when the record was only filtered out. In `sync_participants`, the to_remove participants are split between those whose record still exists in the database (filtered out by the campaign domain) and those whose record was actually deleted. `action_set_unlink` accepts an optional `trace_message` argument, defaulting to "Record deleted", and the filtered-out batch passes "Record no longer matches campaign filter" so the cancelled trace dialog reflects the real cause. Steps to reproduce: 1. Install Marketing Automation and CRM. 2. Open Marketing Automation, create a campaign on Lead with filter Stage = New. 3. Add a begin activity to the workflow. 4. Open CRM, create a Lead in the New stage. 5. Back in the campaign, click Generate Participants. 6. In the CRM pipeline, drag the Lead from New to Qualified. 7. Back in the campaign, click Generate Participants again. 8. Open the Participants smart button, click the participant for the moved Lead. 9. Click the cancelled activity in the workflow timeline. => The activity dialog shows "Error message: Record deleted" although the Lead still exists. Ticket [link](https://www.odoo.com/odoo/project/49/tasks/6251264) opw-6251264 Forward-Port-Of: odoo/enterprise#118692
Shop floor operators can now see employee profile photos instead of generic placeholder images when viewing or changing operators. This makes it easier to identify the right person during manufacturing work without requiring HR-level access.
Original PR description
Currently when a shopfloor operator modifies the operator, operator images are not visible and only place holder images are visible. ## Steps to produce: - Install Manufacturing with demo data -…
Currently when a shopfloor operator modifies the operator, operator images are not visible and only place holder images are visible. ## Steps to produce: - Install Manufacturing with demo data - Login as Marc Demo - Open shopfloor ## Observed Behavior: Operator is unable to see their own profile picture and when you press the Edit Operator the operator is not able to see their as well as other operators images as well. ## Root cause: This issue occurs because by default an operator's user does not have access right for `Employees` in Human Resources section as they are not an HR officer nor an administrator. So when the user tries to access the image url that is meant for users with those access rights (HR officer / Administrator) at [1] and at [2]. The system returns a placeholder image instead of the employee image since the operator does not have access to hr employee model but does have access to hr employee public as per this [commit](https://github.com/odoo/odoo/commit/c9ca3761464413327d2beb697553a3ccd7eef4d1) [1]- https://github.com/odoo/enterprise/blob/5fe2fb44f4c8a938390343f7cdc90674cbf09391/mrp_workorder/static/src/mrp_display/dialog/mrp_employee_dialog.js#L16 [2]- https://github.com/odoo/enterprise/blob/5fe2fb44f4c8a938390343f7cdc90674cbf09391/mrp_workorder/static/src/mrp_display/employees_panel.xml#L9-L10 ## Solution: Allowing operators to see images can be done by modifying the links to use the public model instead of the private model. This will allow operators see other operators based on images and should improve user experience. | Before | |--------| | <img width="1865" height="844" alt="image" src="https://github.com/user-attachments/assets/c28f4209-0a60-486a-bfec-2a4db39cd946" /> | | <img width="995" height="899" alt="image" src="https://github.com/user-attachments/assets/7541edd9-6cc9-42d1-b5c5-39e0740d9a93" /> | | After | |--------| | <img width="1881" height="903" alt="image" src="https://github.com/user-attachments/assets/df3f8357-5cc2-4688-83dd-9a009fd56957" /> | | <img width="1221" height="862" alt="image" src="https://github.com/user-attachments/assets/c9d72b07-e1ad-43e4-85f5-00d8f1db9095" /> | opw-6321989 Forward-Port-Of: odoo/enterprise#121751
Hong Kong payroll now handles payslips with missing start or end dates without crashing. This prevents interruptions when users edit payslip periods and ensures related wage and end-of-year pay calculations are safely skipped until valid dates are provided.
Original PR description
Currently, an error occurs when a user removes the payslip date. **Steps to Reproduce:** - Install `l10n_hk_hr_payroll` with demo data. - Switch to the `Hong Kong` company. - Go to `Payroll` >…
Currently, an error occurs when a user removes the payslip date. **Steps to Reproduce:** - Install `l10n_hk_hr_payroll` with demo data. - Switch to the `Hong Kong` company. - Go to `Payroll` > `Payslips` > `Payslips`. - Create a `payslip` and remove the `start` or `end` period. **Error 1:** `TypeError: unsupported operand type(s) for +: 'bool' and 'relativedelta'` **Error2:** `AttributeError: 'bool' object has no attribute 'month'` When a user removes the start or end date of a payslip, the system computes the Average Daily Wage. Based on the payslip dates, it finds the previous year's payslips [1]. If the start or end date is not set, it raises an error [2]. For the second error, when computing whether to include EOY pay, it compares the company's EOY pay date with the end date's month. If the end date is not set, accessing its month raises an error [3]. This commit ensures that when retrieving previous-year payslips, if the start or end date is not set, it returns an empty payslip recordset. It also ensures that when computing whether to include EOY pay, if the end date is not set, `include_eoy_pay` is set to `False`. [1]: https://github.com/odoo/enterprise/blob/ec8a009794863090351d91650aff727e6fbeab7e/l10n_hk_hr_payroll/models/hr_payslip.py#L124 [2]- https://github.com/odoo/enterprise/blob/ec8a009794863090351d91650aff727e6fbeab7e/l10n_hk_hr_payroll/models/hr_payslip.py#L209-L215 [3]- https://github.com/odoo/enterprise/blob/ec8a009794863090351d91650aff727e6fbeab7e/l10n_hk_hr_payroll/models/hr_payslip.py#L141 Forward-Port-Of: odoo/enterprise#123135 Forward-Port-Of: odoo/enterprise#120586
Popover content now looks more consistent and easier to use in dark mode. Secondary buttons stand out as clickable actions, and forms inside popovers better match the surrounding popover background.
Original PR description
Before this commit, content like form & secondary button rendered inside popovers had inconsistent styling in dark mode: - secondary buttons did not stand out properly from the popover background, making them look like plain text blocks rather than actionable buttons; - forms rendered inside popovers (such as the multi-create popover) kept their default background, which visually clashed with the popover background. This commit fixes these issues by: - adding dedicated secondary button background colors for popovers, including hover state; - aligning form backgrounds inside popovers with the popover background. task-6249985 Forward-Port-Of: odoo/enterprise#121631
International shipments through Easypost now use the sale order currency on commercial invoices when available, instead of defaulting to the company currency. This helps ensure customs paperwork matches customer pricing and reduces errors for cross-border deliveries.
Original PR description
Issue ----- When shipping internationally with Easypost, the currency on the commercial invoice does not always match the one of the sale order. Steps to reproduce ----- - Install Easypost - Create a…
Issue ----- When shipping internationally with Easypost, the currency on the commercial invoice does not always match the one of the sale order. Steps to reproduce ----- - Install Easypost - Create a new pricelist using a different currency from the company - Create a SO - Some product with a weight & HS code - Customer must be in another country from company (for commercial invoice) - Use the new pricelist - Add easypost delivery - Confirm SO - Validate linked picking > Commercial invoice uses company currency instead of SO's Cause ----- The currency being sent to Easypost is retrieved from the package in https://github.com/odoo/enterprise/blob/4c540f450d4de8b59b871662123f85ed54cca2a9/delivery_easypost/models/easypost_request.py#L146 The package object is actually created by calling the carrier's `_get_packages_from_picking` method https://github.com/odoo/enterprise/blob/4c540f450d4de8b59b871662123f85ed54cca2a9/delivery_easypost/models/easypost_request.py#L266-L270 Solution ----- We could be fixing this in `stock_delivery` by creating the package with the correct currency when calling `_get_packages_from_picking`. The problem with this approach is that this might negatively affect other carrier services, as discussed in https://github.com/odoo/odoo/pull/268224. Instead, we can apply a band-aid fix to take the currency from the picking's sale in the `delivery_easypost` module, which is the only one where the problem was reported. ----- Ticket: opw-6224883 Forward-Port-Of: odoo/enterprise#123083
Lazada order synchronization no longer updates individual item statuses when they differ from the overall order status. This prevents sync failures caused by trying to reduce quantities for items that were already delivered, helping orders continue importing reliably.
Original PR description
Lazada stores order statuses at the item level. When an item is canceled, we mirrored this by decreasing the ordered quantity on the sale order line. But if the item was already delivered, decreasing the quantity below the delivered amount is forbidden and raises a `UserError`, which aborts the whole order sync:
```python
File ".../sale_stock/models/sale_order_line.py", line 420, in _update_line_quantity
raise UserError(_('The ordered quantity of a sale order line cannot be decreased below the amount already delivered. [...]'))
```
In practice, item-level statuses only differ from the order status in exceptional cases. Stop syncing statuses at the item level and assume the entire order shares a single status, which avoids the quantity decrease and the resulting traceback.
opw-6267730
Forward-Port-Of: odoo/enterprise#12285123 changes
Enhancements to existing features
Hong Kong payroll data updates will now include the relevant salary rule files. This helps keep standard, non-customized payroll rules up to date automatically, reducing manual maintenance for users.
Original PR description
Currently, the "Payroll: Update data" cron doesn't work for HK payroll as we never set up the _get_data_files_to_update. We can set up the list of data files to keep up to date to better support our users by automatically keeping non-edited salary rules up to date. task-6360339 Forward-Port-Of: odoo/enterprise#122777
Resolved issues and error corrections
Pasting document links into an empty message no longer adds an unnecessary blank line at the start. This keeps shared document messages cleaner while still separating links from any existing text when needed.
Original PR description
Before this commit, adding document links always prepended a line break before the generated links. When the composer was empty, this resulted in messages starting with an unnecessary blank line. This commit only inserts a line break when the composer already contains text, avoiding the extra spacing while preserving the separation between existing content and pasted links. task-[5947683](https://www.odoo.com/odoo/project/1519/tasks/5947683) Forward-Port-Of: odoo/enterprise#120952
The AI module tests now skip markdown rendering checks when the optional markdown component is not installed. This prevents unnecessary test failures in environments that do not include that optional dependency, improving reliability for releases and maintenance.
Original PR description
markdown2 is an optional dependency, so `markdown_format` can fail to process markdown, in which case all the markdown tests fail. Skip the markdown rendering test if there's no markdown rendering to test.
Manufacturing shopfloor users can now see real employee profile pictures instead of generic placeholders when viewing or changing operators. This makes it easier for operators to identify themselves and colleagues during shopfloor work without requiring HR-level access.
Original PR description
Currently when a shopfloor operator modifies the operator, operator images are not visible and only place holder images are visible. ## Steps to produce: - Install Manufacturing with demo data -…
Currently when a shopfloor operator modifies the operator, operator images are not visible and only place holder images are visible. ## Steps to produce: - Install Manufacturing with demo data - Login as Marc Demo - Open shopfloor ## Observed Behavior: Operator is unable to see their own profile picture and when you press the Edit Operator the operator is not able to see their as well as other operators images as well. ## Root cause: This issue occurs because by default an operator's user does not have access right for `Employees` in Human Resources section as they are not an HR officer nor an administrator. So when the user tries to access the image url that is meant for users with those access rights (HR officer / Administrator) at [1] and at [2]. The system returns a placeholder image instead of the employee image since the operator does not have access to hr employee model but does have access to hr employee public as per this [commit](https://github.com/odoo/odoo/commit/c9ca3761464413327d2beb697553a3ccd7eef4d1) [1]- https://github.com/odoo/enterprise/blob/5fe2fb44f4c8a938390343f7cdc90674cbf09391/mrp_workorder/static/src/mrp_display/dialog/mrp_employee_dialog.js#L16 [2]- https://github.com/odoo/enterprise/blob/5fe2fb44f4c8a938390343f7cdc90674cbf09391/mrp_workorder/static/src/mrp_display/employees_panel.xml#L9-L10 ## Solution: Allowing operators to see images can be done by modifying the links to use the public model instead of the private model. This will allow operators see other operators based on images and should improve user experience. | Before | |--------| | <img width="1865" height="844" alt="image" src="https://github.com/user-attachments/assets/c28f4209-0a60-486a-bfec-2a4db39cd946" /> | | <img width="995" height="899" alt="image" src="https://github.com/user-attachments/assets/7541edd9-6cc9-42d1-b5c5-39e0740d9a93" /> | | After | |--------| | <img width="1881" height="903" alt="image" src="https://github.com/user-attachments/assets/df3f8357-5cc2-4688-83dd-9a009fd56957" /> | | <img width="1221" height="862" alt="image" src="https://github.com/user-attachments/assets/c9d72b07-e1ad-43e4-85f5-00d8f1db9095" /> | opw-6321989 Forward-Port-Of: odoo/enterprise#121751
This fixes an error that could stop Belgian CODA bank statement imports when the file included a specific type of summary block. Businesses using Belgian bank feeds can now import these CODA files without interruption.
Original PR description
### Issue: After the fix in commit (https://github.com/odoo/enterprise/commit/3ef8ae7a6b8eb362e18c74dfab9aadce792b5dc2), importing a CODA file containing a type 4 block raises a traceback ### Cause: That commit introduced `communication_struct_by_ref_move`, which iterates over all lines and accesses `line['communication_struct']` Type 4 lines are not assigned a `communication_struct` value by the parser in `_get_coda_file_statements` Accessing the key directly raises a `KeyError` in `_get_coda_final_statements` in `communication_struct_by_ref_move` ### Steps to reproduce: - Install `l10n_be_coda` - Switch to the BE company - Create a Bank Journal with account `BE33737018595246` - Go to the Accounting Dashboard and import a CODA file containing a type 4 block (Like the one on the ticket) Before the fix, a traceback is raised on import opw-6363148 Forward-Port-Of: odoo/enterprise#123222
Sales orders now consistently block recurring products when no subscription plan is set, including when products are added from the catalog view. This prevents invalid subscription orders from being created through an alternate workflow and keeps the user experience aligned across entry points.
Original PR description
Steps to reproduce: --------------------------------------- 1. Install Subscription Module 2. Create and Confirm SO with no recurring plan and a non-recurring product 3. Add a recurring product >…
Steps to reproduce: --------------------------------------- 1. Install Subscription Module 2. Create and Confirm SO with no recurring plan and a non-recurring product 3. Add a recurring product > Save SO > Observe the User Error 4. Now add the same recurring product through Catalog View Observation: --------------------------------------- No User Error raised stating 'You cannot save a sale order with recurring product and no subscription plan.' Issue: --------------------------------------- When you manually add a line and click 'Save', the constraint (`_constraint_subscription_plan`) is triggered and raised `UserError` https://github.com/odoo/enterprise/blob/434d88960abb5e424fdc1106fc93935d328bff78/sale_subscription/models/sale_order.py#L176-L177 When you add a product via the catalog view, it calls `_update_order_line_info` which directly creates/updates order lines, Which do not trigger the python constraint. https://github.com/odoo/odoo/blob/ef9772bba1515bdaf5410c3af5a3e395f562d513/addons/sale/models/sale_order.py#L1926-L1933 Solution: --------------------------------------- Two private helpers are introduced: * `_is_exempt_from_subscription_plan_check`: single source of truth for all exempt states (draft, cancelled, upsell, and legacy upgrade orders). * `_check_recurring_plan_mismatch`: raises a `UserError` when the order has or will have a recurring product but no subscription plan, reusing the exemption helper so both call sites stay in sync. `_constraint_subscription_plan` is refactored to delegate to these helpers, and `_update_order_line_info` is overridden to call `_check_recurring_plan_mismatch` before the catalog update is applied, ensuring consistent validation across both entry points. opw-6194865 Forward-Port-Of: odoo/enterprise#122799 Forward-Port-Of: odoo/enterprise#117879
Lazada order syncing now avoids changing ordered quantities based on individual item statuses. This prevents sync failures when an item was already delivered and later appears canceled, keeping order imports more reliable for merchants.
Original PR description
Lazada stores order statuses at the item level. When an item is canceled, we mirrored this by decreasing the ordered quantity on the sale order line. But if the item was already delivered, decreasing the quantity below the delivered amount is forbidden and raises a `UserError`, which aborts the whole order sync:
```python
File ".../sale_stock/models/sale_order_line.py", line 420, in _update_line_quantity
raise UserError(_('The ordered quantity of a sale order line cannot be decreased below the amount already delivered. [...]'))
```
In practice, item-level statuses only differ from the order status in exceptional cases. Stop syncing statuses at the item level and assume the entire order shares a single status, which avoids the quantity decrease and the resulting traceback.
opw-6267730
Forward-Port-Of: odoo/enterprise#122851This fix prevents delivery tracking from failing when EasyPost returns an empty tracker value. Users can continue processing shipments without encountering an unexpected error while viewing or generating tracking links.
Original PR description
The PR https://github.com/odoo/enterprise/pull/111833 handled the specific case when the tracker data is missing from the EasyPost response, however in certain cases `tracker` key exists, but it has a `None` value, which leads to a traceback when trying to access the stock move:
```
File "/home/odoo/src/enterprise/18.0/delivery_easypost/models/easypost_request.py", line 392, in get_tracking_link
public_url = shipment.get('tracker', {}).get('public_url')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'get'
```
This commit provides a fallback to avoid getting the error from the side of the user.
opw-6270242
Forward-Port-Of: odoo/enterprise#119400The timesheet grid now marks public holidays and approved personal time off according to each employee's own working schedule, instead of always using the company default. This helps employees and managers see accurate unavailable days when entering or reviewing timesheets.
Original PR description
Steps to Reproduce --- - Create two different working schedules with different public holidays - Assign employees to specific working schedules - Set company default working schedule to a different…
Steps to Reproduce --- - Create two different working schedules with different public holidays - Assign employees to specific working schedules - Set company default working schedule to a different schedule - Login as employee with specific working schedule - Navigate to Timesheets app -> My Timesheets - Observe public holidays and personal time-off displayed in the timesheet grid Issue --- - The timesheet grid displays unavailable dates (public holidays, weekends) from the company's default working schedule instead of the employee's assigned working schedule. - Personal time-off requests are not reflected as unavailable dates in the timesheet grid. Current Behaviour --- - Public holidays shown are always from the company's default working schedule, ignoring employee-specific working schedule assignments. - Employee's approved time-off requests don't appear as unavailable in the timesheet. Expected Behaviour --- - Public holidays should display based on the employee's assigned working schedule, with company schedule as fallback only when no specific schedule is assigned. - Employee's personal time-off requests should appear as unavailable dates. - This should align with Time Off app behavior. Fix --- - Included employee-specific work interval calculation with personal time-off requests. - Added support for contract-based calendar changes and calendar validity periods. - Implemented proper fallback when valid intervals are not found. task-4997080 Forward-Port-Of: odoo/enterprise#95458
Fixed an issue where scanning a component product barcode on the shopfloor did nothing when that component was tied to a manufacturing operation. Operators can now scan these barcodes and have the correct product action triggered, reducing manual work and production delays.
Original PR description
When scanning a product barcode in the shopfloor, it would not simulate a click on the product if the corresponding BoM line was linked to an operation. Steps to reproduce: ------------------- * Create a BoM for product A with a BoM line for product B and link it to any operation. * Create a WO for product A and confirm it * Open the shopfloor with the WO and scan the barcode of product B > Observation: Nothing happens Why the fix: ------------ We simulate the onClick for the product even if the move is not marked manual_consumption. opw-6268665 Forward-Port-Of: odoo/enterprise#122337
Restored and updated automated checks for rental and Kenyan stock valuation flows after earlier accounting valuation changes. This helps ensure stock receipts and vendor bill amounts stay aligned, reducing the risk of incorrect valuation-related behavior in future updates.
Original PR description
*: sale_stock_renting, l10n_ke_edi_oscu_stock Re-enable and adapt the tests skipped to fast merge the valuation refactoring made in 08b62a4bbcc6f9a391b2cc00a621ef4c76100229. The stock IO now values the receipt from the vendor bill, so the shared purchase fixtures `l10n_ke_edi_oscu` need to match the values provided in `l10n_ke_edi_oscu_stock` see for instance: https://github.com/odoo/enterprise/blob/ce68644f97ac28568b9497a18079a4ad5ce4a125/l10n_ke_edi_oscu/tests/expected_requests/save_purchase_2.json#L11-L13 Forward-Port-Of: odoo/enterprise#122857
The website now shows the exact discount percentage configured for subscription products when prices are displayed with tax included. This avoids confusing customers and merchants with rounded-down discounts, such as showing 4% instead of the intended 5%.
Original PR description
Steps to reproduce: 1. Install eCommerce and Subscriptions. 2. Create a 21% Excluded tax. 3. Create a subscription product with a price of 45 with 21% tax and enable "Accept One-Time" in the…
Steps to reproduce: 1. Install eCommerce and Subscriptions. 2. Create a 21% Excluded tax. 3. Create a subscription product with a price of 45 with 21% tax and enable "Accept One-Time" in the Recurring Prices tab. 4. Publish the product on the website under the Sales tab. 5. Create a pricelist for 6 months recurring with two lines: - If min quantity is 0, then 0% discount - If min quantity is 2, then 5% discount 6. Set "Display Product Prices" to "Tax Included" in the Settings. 7. Open the product on the website, select the 6-month plan, and increase quantity to 2. Issue: The discount percentage displayed on the website shows 4% instead of the configured 5%. Why this happens: In `_get_additionnal_combination_info`, the discount is reverse-calculated from the tax-included price vs the tax-included sales price. When the 21% tax is included to both prices, it introduces a floating-point precision loss (4.9954..%), which floor() then truncates to 4%. Fix: When the pricelist rule uses 'percentage' discount, read `percent_price` directly from the pricing rule instead of reverse-calculating from tax-adjusted prices, as it represents the exact discount percentage the merchant configured with no floating-point involvement. opw-6224735 Forward-Port-Of: odoo/enterprise#121654
This fixes an issue where website dynamic snippets could point to the wrong filters when modules were installed in a different order. The change ensures the correct database filter is used, helping generated website content display as intended.
Original PR description
Our default dynamic snippets filter ids are set based on the order that we install our modules. This can cause issues if the user installs their modules in a different order. To fix this, we need to update the data-filter-id value to the correct value of the DB. To be able to do this, we also change the regex replacement to use lxml instead since it's much simpler. Lxml part from 799f83575e162eb683cfaebb4eb602ccc1fbe466. Forward-Port-Of: odoo/enterprise#123143 Forward-Port-Of: odoo/enterprise#122671
Popovers in dark mode now display buttons and embedded forms with clearer, more consistent styling. This makes actions easier to identify and prevents form areas from visually clashing with the popover background.
Original PR description
Before this commit, content like form & secondary button rendered inside popovers had inconsistent styling in dark mode: - secondary buttons did not stand out properly from the popover background, making them look like plain text blocks rather than actionable buttons; - forms rendered inside popovers (such as the multi-create popover) kept their default background, which visually clashed with the popover background. This commit fixes these issues by: - adding dedicated secondary button background colors for popovers, including hover state; - aligning form backgrounds inside popovers with the popover background. task-6249985 Forward-Port-Of: odoo/enterprise#121631
The Belgian payroll rules no longer apply a special public holiday eligibility condition for time credit contracts because it had no legal basis. This helps ensure payroll calculations follow the correct legal interpretation and avoids incorrect treatment of affected employees.
Original PR description
The specific code related to the eligibility to public holiday for time credit contracts has no legal base. This commit removes it. task-6370653
DHL shipments could fail for customers or warehouses in regions whose province code is stored as a single character, such as Barcelona. The update converts those short region codes into the longer format DHL expects, helping deliveries validate successfully for affected countries.
Original PR description
Steps:
- Install delivery_dhl_rest
- Create a new customer with barcelona as address
- Create a new Delivery
- Set DHL
- Validate de delivery
- Validation error #/customerDetails/receiverDetails/postalAddress/provinceCode: expected minLength: 2, actual: 1
DHL requires `provinceCode` to be at least 2 characters. Several countries in `res.country.state` data use single-character codes (e.g. ES: B, M, A…; AR: C, B, S…; CN: 京, 沪…). This caused API validation errors when shipping from or to addresses in those regions.
Add `PROVINCE_CODE_MAP`, a dict keyed by `(country_ISO2, state_code)`, mapping each offending code to its ISO 3166-2 form (e.g. ('ES', 'B') -> 'ES-B'). Both `_get_consignee_vals` and `_get_shipper_vals` now look up the map before sending `provinceCode`, falling back to the raw code for countries not in the map.
links: https://developer.dhl.com/api-reference/mydhl-api-dhl-express#shipments
opw-6341745
Forward-Port-Of: odoo/enterprise#122138This fixes an issue where some users could receive an access error when opening WhatsApp messages linked to restricted business documents. The change restores the expected behavior while keeping WhatsApp message visibility controlled by existing access rules.
Original PR description
The `body` field on `whatsapp.message` was defined with `related_sudo=False` with the intent of restricting access to messages from restricted records. However, this was never actually providing any…
The `body` field on `whatsapp.message` was defined with `related_sudo=False` with the intent of restricting access to messages from restricted records. However, this was never actually providing any security value because [`mail.message.fetch()`] was overriding it with `self.sudo()` till `v19.1`, meaning the body was always fetched as superuser regardless:
```py
web_search_read() -> search_fetch()
-> fields.py _compute_related()
-> record[self.related_field.name] # triggers fetch of mail.message.body
-> models.py _fetch_field()
-> mail_message.py fetch()
-> self = self.sudo() # sudo hack overrides related_sudo=False silently
```
In `v19.2`, the `fetch()` sudo hack was intentionally removed (see commit odoo/odoo@4727f12d274a0b2d7c455363d189565bd8fb2e7a) as access rights are now cached and can be checked without a performance penalty. This exposed the broken `related_sudo=False` which now causes an `AccessError` when trying to read the body of a `whatsapp.message` whose linked `mail.message` points to a document the current user cannot access (e.g. `purchase.order`).
Access control on `whatsapp.message` is already correctly enforced at the `ir.rule` level:
- Regular users can only see messages they created (`create_uid = user.id`)
- WA Admins can see all messages
We have upgrade requests failing on this issue: TBG-[2765]
[`mail.message.fetch()`]: https://github.com/odoo/odoo/blob/saas-19.1/addons/mail/models/mail_message.py#L812-L819
[2765]: https://upgrade.odoo.com/odoo/tbg/2765?debug=1Marketing Automation now shows the correct reason when a participant is removed from a campaign. If a record still exists but no longer matches the campaign filter, users will see that it was filtered out instead of incorrectly being told the record was deleted.
Original PR description
`sync_participants` calls `action_set_unlink` on every participant whose record is no longer in the campaign domain, and `action_set_unlink` writes "Record deleted" on each scheduled trace. The…
`sync_participants` calls `action_set_unlink` on every participant whose record is no longer in the campaign domain, and `action_set_unlink` writes "Record deleted" on each scheduled trace. The removed bucket also contains records that still exist but no longer match the campaign filter, so the cancelled trace dialog shows "Record deleted" even when the record was only filtered out. In `sync_participants`, the to_remove participants are split between those whose record still exists in the database (filtered out by the campaign domain) and those whose record was actually deleted. `action_set_unlink` accepts an optional `trace_message` argument, defaulting to "Record deleted", and the filtered-out batch passes "Record no longer matches campaign filter" so the cancelled trace dialog reflects the real cause. Steps to reproduce: 1. Install Marketing Automation and CRM. 2. Open Marketing Automation, create a campaign on Lead with filter Stage = New. 3. Add a begin activity to the workflow. 4. Open CRM, create a Lead in the New stage. 5. Back in the campaign, click Generate Participants. 6. In the CRM pipeline, drag the Lead from New to Qualified. 7. Back in the campaign, click Generate Participants again. 8. Open the Participants smart button, click the participant for the moved Lead. 9. Click the cancelled activity in the workflow timeline. => The activity dialog shows "Error message: Record deleted" although the Lead still exists. Ticket [link](https://www.odoo.com/odoo/project/49/tasks/6251264) opw-6251264 Forward-Port-Of: odoo/enterprise#118692
The Turkish Central Bank exchange rate provider now uses the official selling rate instead of averaging buying and selling rates. This improves accuracy for accounting and import valuation, helping align currency calculations with Turkish customs requirements.
Original PR description
## Short fix summary: The TCMB (Central Bank of Turkey) provider computed the exchange rate as an average of the buying and selling rates (`2 / (ForexBuying + ForexSelling)`). This is inaccurate for real accounting flows and does not follow Turkish customs regulation (Customs Law No. 4458, Art. 30), which requires the Central Bank's selling rate for goods import valuation. This now uses the selling rate (`ForexSelling`) only. task-6227500 I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/enterprise#122770
International Easypost shipments now use the sale order currency for commercial invoices when that information is available. This prevents customers and customs documents from showing the company currency instead of the currency agreed on the order.
Original PR description
Issue ----- When shipping internationally with Easypost, the currency on the commercial invoice does not always match the one of the sale order. Steps to reproduce ----- - Install Easypost - Create a…
Issue ----- When shipping internationally with Easypost, the currency on the commercial invoice does not always match the one of the sale order. Steps to reproduce ----- - Install Easypost - Create a new pricelist using a different currency from the company - Create a SO - Some product with a weight & HS code - Customer must be in another country from company (for commercial invoice) - Use the new pricelist - Add easypost delivery - Confirm SO - Validate linked picking > Commercial invoice uses company currency instead of SO's Cause ----- The currency being sent to Easypost is retrieved from the package in https://github.com/odoo/enterprise/blob/4c540f450d4de8b59b871662123f85ed54cca2a9/delivery_easypost/models/easypost_request.py#L146 The package object is actually created by calling the carrier's `_get_packages_from_picking` method https://github.com/odoo/enterprise/blob/4c540f450d4de8b59b871662123f85ed54cca2a9/delivery_easypost/models/easypost_request.py#L266-L270 Solution ----- We could be fixing this in `stock_delivery` by creating the package with the correct currency when calling `_get_packages_from_picking`. The problem with this approach is that this might negatively affect other carrier services, as discussed in https://github.com/odoo/odoo/pull/268224. Instead, we can apply a band-aid fix to take the currency from the picking's sale in the `delivery_easypost` module, which is the only one where the problem was reported. ----- Ticket: opw-6224883 Forward-Port-Of: odoo/enterprise#123083
This fix preserves the expected behavior of Knowledge file navigation when using newer Chrome versions. It prevents an underlying browser change from altering how scroll actions complete, helping avoid small interaction glitches for users.
Original PR description
Since Chrome 150, scrolling methods like `scrollIntoView()` return a Promise instead of `undefined`. This commit adds block braces to ensure the action returns `undefined` and keeps the same behavior as before. Reference: - https://chromestatus.com/feature/5082138340491264 - https://chromium.googlesource.com/chromium/src/+/50f3e3d0a9bc02aad8b8161dbdd59046991dd2c7 runbot-941309 Forward-Port-Of: odoo/enterprise#123231 Forward-Port-Of: odoo/enterprise#123031
Fixed an issue that could cause the Balance Sheet report to crash when analytic accounting was enabled and a custom Studio field linked to journal items existed. Users can now apply analytic account filters to the report without hitting an error, improving reliability for accounting reporting.
Original PR description
Steps to reproduce ================== - Activate Analytic Accounting. - Go to Accounting > Accounting > Reconcile. - Open Studio. - Add a new many2many field. - Set Journal Item as the related model. - Go to Reporting > Balance Sheet. - Select an analytic account. => RecursionError: maximum recursion depth exceeded Cause of the issue ================== Calling `self.env['account.move.line'].fields_get()` will cause a recursion error. `account.report::_prepare_lines_for_analytic_groupby()` calls `account.move.line::_where_calc()` which in turns calls _prepare_lines_for_analytic_groupby again Solution ======== It turns out we don't actually need to retrieve the groupable attribute, thus bypassing the error. opw-6129149 Forward-Port-Of: odoo/enterprise#122244 Forward-Port-Of: odoo/enterprise#116251
The Peru Profit and Loss report now includes Other Operating Income when calculating gross profit and related profit totals. This ensures business results such as operating profit, pre-tax result, and net profit reflect all relevant income correctly.
Original PR description
**Steps to reproduce:** 1. Install `l10n_pe` and switching to the Peru company. 2. Create and post a journal entry with a line on account 7520000 (Other Operating Income). 3. Open the Profit and Loss report (PE). 4. The amount appears correctly under "Other operating income" (`PE_PNL_A_5`). 5. "Gross profit", "Operating profit" , "Result before taxes" and "Net Profit" do not change when this amount is added or removed. **Issue:** The "Other operating income" line is excluded from the Gross Profit calculation, and consequently from Operating Profit and every downstream total in the PE Profit and Loss report. **Why this happens:** Gross Profit (`PE_PNL_A_4`) balance expression uses the aggregation with formula `PE_PNL_A.balance - PE_PNL_A_3.balance`, which doesn't include `PE_PNL_A_5.balance` as a term opw-6283907 Forward-Port-Of: odoo/enterprise#123063
24 changes
Enhancements to existing features
Hong Kong payroll data is now included in the automatic payroll update process. This helps keep standard, unmodified salary rules up to date, reducing manual maintenance and improving payroll reliability for Hong Kong users.
Original PR description
Currently, the "Payroll: Update data" cron doesn't work for HK payroll as we never set up the _get_data_files_to_update. We can set up the list of data files to keep up to date to better support our users by automatically keeping non-edited salary rules up to date. task-6360339 Forward-Port-Of: odoo/enterprise#122777
Resolved issues and error corrections
The Turkish Central Bank currency provider now uses the official selling rate instead of averaging buying and selling rates. This improves accounting accuracy for Turkish transactions and better aligns import valuation with local customs requirements.
Original PR description
## Short fix summary: The TCMB (Central Bank of Turkey) provider computed the exchange rate as an average of the buying and selling rates (`2 / (ForexBuying + ForexSelling)`). This is inaccurate for real accounting flows and does not follow Turkish customs regulation (Customs Law No. 4458, Art. 30), which requires the Central Bank's selling rate for goods import valuation. This now uses the selling rate (`ForexSelling`) only. task-6227500 I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/enterprise#122770
Shopfloor users can now see employee profile photos instead of generic placeholder images when viewing or changing operators. This makes it easier for manufacturing teams to identify the right person during shopfloor operations.
Original PR description
Currently when a shopfloor operator modifies the operator, operator images are not visible and only place holder images are visible. ## Steps to produce: - Install Manufacturing with demo data -…
Currently when a shopfloor operator modifies the operator, operator images are not visible and only place holder images are visible. ## Steps to produce: - Install Manufacturing with demo data - Login as Marc Demo - Open shopfloor ## Observed Behavior: Operator is unable to see their own profile picture and when you press the Edit Operator the operator is not able to see their as well as other operators images as well. ## Root cause: This issue occurs because by default an operator's user does not have access right for `Employees` in Human Resources section as they are not an HR officer nor an administrator. So when the user tries to access the image url that is meant for users with those access rights (HR officer / Administrator) at [1] and at [2]. The system returns a placeholder image instead of the employee image since the operator does not have access to hr employee model but does have access to hr employee public as per this [commit](https://github.com/odoo/odoo/commit/c9ca3761464413327d2beb697553a3ccd7eef4d1) [1]- https://github.com/odoo/enterprise/blob/5fe2fb44f4c8a938390343f7cdc90674cbf09391/mrp_workorder/static/src/mrp_display/dialog/mrp_employee_dialog.js#L16 [2]- https://github.com/odoo/enterprise/blob/5fe2fb44f4c8a938390343f7cdc90674cbf09391/mrp_workorder/static/src/mrp_display/employees_panel.xml#L9-L10 ## Solution: Allowing operators to see images can be done by modifying the links to use the public model instead of the private model. This will allow operators see other operators based on images and should improve user experience. | Before | |--------| | <img width="1865" height="844" alt="image" src="https://github.com/user-attachments/assets/c28f4209-0a60-486a-bfec-2a4db39cd946" /> | | <img width="995" height="899" alt="image" src="https://github.com/user-attachments/assets/7541edd9-6cc9-42d1-b5c5-39e0740d9a93" /> | | After | |--------| | <img width="1881" height="903" alt="image" src="https://github.com/user-attachments/assets/df3f8357-5cc2-4688-83dd-9a009fd56957" /> | | <img width="1221" height="862" alt="image" src="https://github.com/user-attachments/assets/c9d72b07-e1ad-43e4-85f5-00d8f1db9095" /> | opw-6321989 Forward-Port-Of: odoo/enterprise#121751
Belgian CODA bank statement imports no longer fail when the file contains a type 4 block. This prevents an import error and lets users process affected bank files normally.
Original PR description
### Issue: After the fix in commit (https://github.com/odoo/enterprise/commit/3ef8ae7a6b8eb362e18c74dfab9aadce792b5dc2), importing a CODA file containing a type 4 block raises a traceback ### Cause: That commit introduced `communication_struct_by_ref_move`, which iterates over all lines and accesses `line['communication_struct']` Type 4 lines are not assigned a `communication_struct` value by the parser in `_get_coda_file_statements` Accessing the key directly raises a `KeyError` in `_get_coda_final_statements` in `communication_struct_by_ref_move` ### Steps to reproduce: - Install `l10n_be_coda` - Switch to the BE company - Create a Bank Journal with account `BE33737018595246` - Go to the Accounting Dashboard and import a CODA file containing a type 4 block (Like the one on the ticket) Before the fix, a traceback is raised on import opw-6363148 Forward-Port-Of: odoo/enterprise#123222
Pasting document links into an empty message no longer adds an unnecessary blank line at the start. This keeps shared document messages cleaner while still separating links from any existing text.
Original PR description
Before this commit, adding document links always prepended a line break before the generated links. When the composer was empty, this resulted in messages starting with an unnecessary blank line. This commit only inserts a line break when the composer already contains text, avoiding the extra spacing while preserving the separation between existing content and pasted links. task-[5947683](https://www.odoo.com/odoo/project/1519/tasks/5947683) Forward-Port-Of: odoo/enterprise#120952
Timesheets now mark public holidays and approved personal time off based on each employee's own working schedule instead of always using the company's default schedule. This helps employees and managers see accurate unavailable days when entering or reviewing time.
Original PR description
Steps to Reproduce --- - Create two different working schedules with different public holidays - Assign employees to specific working schedules - Set company default working schedule to a different…
Steps to Reproduce --- - Create two different working schedules with different public holidays - Assign employees to specific working schedules - Set company default working schedule to a different schedule - Login as employee with specific working schedule - Navigate to Timesheets app -> My Timesheets - Observe public holidays and personal time-off displayed in the timesheet grid Issue --- - The timesheet grid displays unavailable dates (public holidays, weekends) from the company's default working schedule instead of the employee's assigned working schedule. - Personal time-off requests are not reflected as unavailable dates in the timesheet grid. Current Behaviour --- - Public holidays shown are always from the company's default working schedule, ignoring employee-specific working schedule assignments. - Employee's approved time-off requests don't appear as unavailable in the timesheet. Expected Behaviour --- - Public holidays should display based on the employee's assigned working schedule, with company schedule as fallback only when no specific schedule is assigned. - Employee's personal time-off requests should appear as unavailable dates. - This should align with Time Off app behavior. Fix --- - Included employee-specific work interval calculation with personal time-off requests. - Added support for contract-based calendar changes and calendar validity periods. - Implemented proper fallback when valid intervals are not found. task-4997080 Forward-Port-Of: odoo/enterprise#95458
Lazada order synchronization no longer updates individual item statuses when they differ from the overall order status. This prevents delivered orders from failing to sync when a later item cancellation would otherwise reduce quantities below what has already been delivered.
Original PR description
Lazada stores order statuses at the item level. When an item is canceled, we mirrored this by decreasing the ordered quantity on the sale order line. But if the item was already delivered, decreasing the quantity below the delivered amount is forbidden and raises a `UserError`, which aborts the whole order sync:
```python
File ".../sale_stock/models/sale_order_line.py", line 420, in _update_line_quantity
raise UserError(_('The ordered quantity of a sale order line cannot be decreased below the amount already delivered. [...]'))
```
In practice, item-level statuses only differ from the order status in exceptional cases. Stop syncing statuses at the item level and assume the entire order shares a single status, which avoids the quantity decrease and the resulting traceback.
opw-6267730
Forward-Port-Of: odoo/enterprise#122851Shopfloor barcode scanning now correctly opens the related product action even when the component is tied to a manufacturing operation. This prevents operators from scanning a valid product barcode and seeing no response, reducing confusion and improving work order flow.
Original PR description
When scanning a product barcode in the shopfloor, it would not simulate a click on the product if the corresponding BoM line was linked to an operation. Steps to reproduce: ------------------- * Create a BoM for product A with a BoM line for product B and link it to any operation. * Create a WO for product A and confirm it * Open the shopfloor with the WO and scan the barcode of product B > Observation: Nothing happens Why the fix: ------------ We simulate the onClick for the product even if the move is not marked manual_consumption. opw-6268665 Forward-Port-Of: odoo/enterprise#122337
Previously paused checks for rental stock valuation and Kenyan electronic stock reporting have been restored and updated. This helps ensure stock values now align with vendor bill amounts, reducing the risk of incorrect reporting or inventory valuation going unnoticed.
Original PR description
*: sale_stock_renting, l10n_ke_edi_oscu_stock Re-enable and adapt the tests skipped to fast merge the valuation refactoring made in 08b62a4bbcc6f9a391b2cc00a621ef4c76100229. The stock IO now values the receipt from the vendor bill, so the shared purchase fixtures `l10n_ke_edi_oscu` need to match the values provided in `l10n_ke_edi_oscu_stock` see for instance: https://github.com/odoo/enterprise/blob/ce68644f97ac28568b9497a18079a4ad5ce4a125/l10n_ke_edi_oscu/tests/expected_requests/save_purchase_2.json#L11-L13 Forward-Port-Of: odoo/enterprise#122857
This fix prevents an error when EasyPost returns an empty tracking record while processing shipment tracking links. Users can continue working with deliveries without being interrupted by a system traceback in this edge case.
Original PR description
The PR https://github.com/odoo/enterprise/pull/111833 handled the specific case when the tracker data is missing from the EasyPost response, however in certain cases `tracker` key exists, but it has a `None` value, which leads to a traceback when trying to access the stock move:
```
File "/home/odoo/src/enterprise/18.0/delivery_easypost/models/easypost_request.py", line 392, in get_tracking_link
public_url = shipment.get('tracker', {}).get('public_url')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'get'
```
This commit provides a fallback to avoid getting the error from the side of the user.
opw-6270242
Forward-Port-Of: odoo/enterprise#119400The website now shows the exact configured percentage discount for subscription products when prices are displayed with tax included. This prevents customers from seeing a lower discount, such as 4% instead of the intended 5%, due to rounding precision issues.
Original PR description
Steps to reproduce: 1. Install eCommerce and Subscriptions. 2. Create a 21% Excluded tax. 3. Create a subscription product with a price of 45 with 21% tax and enable "Accept One-Time" in the…
Steps to reproduce: 1. Install eCommerce and Subscriptions. 2. Create a 21% Excluded tax. 3. Create a subscription product with a price of 45 with 21% tax and enable "Accept One-Time" in the Recurring Prices tab. 4. Publish the product on the website under the Sales tab. 5. Create a pricelist for 6 months recurring with two lines: - If min quantity is 0, then 0% discount - If min quantity is 2, then 5% discount 6. Set "Display Product Prices" to "Tax Included" in the Settings. 7. Open the product on the website, select the 6-month plan, and increase quantity to 2. Issue: The discount percentage displayed on the website shows 4% instead of the configured 5%. Why this happens: In `_get_additionnal_combination_info`, the discount is reverse-calculated from the tax-included price vs the tax-included sales price. When the 21% tax is included to both prices, it introduces a floating-point precision loss (4.9954..%), which floor() then truncates to 4%. Fix: When the pricelist rule uses 'percentage' discount, read `percent_price` directly from the pricing rule instead of reverse-calculating from tax-adjusted prices, as it represents the exact discount percentage the merchant configured with no floating-point involvement. opw-6224735 Forward-Port-Of: odoo/enterprise#121654
Popover content now looks more consistent and easier to use in dark mode. Secondary buttons are more clearly visible as actions, and forms inside popovers better match the surrounding popover styling.
Original PR description
Before this commit, content like form & secondary button rendered inside popovers had inconsistent styling in dark mode: - secondary buttons did not stand out properly from the popover background, making them look like plain text blocks rather than actionable buttons; - forms rendered inside popovers (such as the multi-create popover) kept their default background, which visually clashed with the popover background. This commit fixes these issues by: - adding dedicated secondary button background colors for popovers, including hover state; - aligning form backgrounds inside popovers with the popover background. task-6249985 Forward-Port-Of: odoo/enterprise#121631
The Balance Sheet report now avoids a crash that could happen after adding a custom Journal Item field in Studio and filtering by analytic account. This keeps financial reporting accessible for users working with analytic accounting and customizations.
Original PR description
Steps to reproduce ================== - Activate Analytic Accounting. - Go to Accounting > Accounting > Reconcile. - Open Studio. - Add a new many2many field. - Set Journal Item as the related model. - Go to Reporting > Balance Sheet. - Select an analytic account. => RecursionError: maximum recursion depth exceeded Cause of the issue ================== Calling `self.env['account.move.line'].fields_get()` will cause a recursion error. `account.report::_prepare_lines_for_analytic_groupby()` calls `account.move.line::_where_calc()` which in turns calls _prepare_lines_for_analytic_groupby again Solution ======== It turns out we don't actually need to retrieve the groupable attribute, thus bypassing the error. opw-6129149 Forward-Port-Of: odoo/enterprise#122244 Forward-Port-Of: odoo/enterprise#116251
DHL deliveries no longer fail when customer or sender addresses use regions with one-character province codes, such as some areas in Spain, Argentina, or China. The system now sends DHL the accepted full province format, helping users validate and create shipments successfully.
Original PR description
Steps:
- Install delivery_dhl_rest
- Create a new customer with barcelona as address
- Create a new Delivery
- Set DHL
- Validate de delivery
- Validation error #/customerDetails/receiverDetails/postalAddress/provinceCode: expected minLength: 2, actual: 1
DHL requires `provinceCode` to be at least 2 characters. Several countries in `res.country.state` data use single-character codes (e.g. ES: B, M, A…; AR: C, B, S…; CN: 京, 沪…). This caused API validation errors when shipping from or to addresses in those regions.
Add `PROVINCE_CODE_MAP`, a dict keyed by `(country_ISO2, state_code)`, mapping each offending code to its ISO 3166-2 form (e.g. ('ES', 'B') -> 'ES-B'). Both `_get_consignee_vals` and `_get_shipper_vals` now look up the map before sending `provinceCode`, falling back to the raw code for countries not in the map.
links: https://developer.dhl.com/api-reference/mydhl-api-dhl-express#shipments
opw-6341745
Forward-Port-Of: odoo/enterprise#122138Easypost shipping documents now use the sale order currency when it is available, instead of defaulting to the company currency. This helps international shipments show the correct invoice currency and reduces errors in customs paperwork.
Original PR description
Issue ----- When shipping internationally with Easypost, the currency on the commercial invoice does not always match the one of the sale order. Steps to reproduce ----- - Install Easypost - Create a…
Issue ----- When shipping internationally with Easypost, the currency on the commercial invoice does not always match the one of the sale order. Steps to reproduce ----- - Install Easypost - Create a new pricelist using a different currency from the company - Create a SO - Some product with a weight & HS code - Customer must be in another country from company (for commercial invoice) - Use the new pricelist - Add easypost delivery - Confirm SO - Validate linked picking > Commercial invoice uses company currency instead of SO's Cause ----- The currency being sent to Easypost is retrieved from the package in https://github.com/odoo/enterprise/blob/4c540f450d4de8b59b871662123f85ed54cca2a9/delivery_easypost/models/easypost_request.py#L146 The package object is actually created by calling the carrier's `_get_packages_from_picking` method https://github.com/odoo/enterprise/blob/4c540f450d4de8b59b871662123f85ed54cca2a9/delivery_easypost/models/easypost_request.py#L266-L270 Solution ----- We could be fixing this in `stock_delivery` by creating the package with the correct currency when calling `_get_packages_from_picking`. The problem with this approach is that this might negatively affect other carrier services, as discussed in https://github.com/odoo/odoo/pull/268224. Instead, we can apply a band-aid fix to take the currency from the picking's sale in the `delivery_easypost` module, which is the only one where the problem was reported. ----- Ticket: opw-6224883 Forward-Port-Of: odoo/enterprise#123083
Marketing Automation now shows the correct reason when a campaign activity is cancelled because a record no longer matches the campaign filter. This prevents users from seeing a misleading “Record deleted” message when the underlying customer or lead record still exists.
Original PR description
`sync_participants` calls `action_set_unlink` on every participant whose record is no longer in the campaign domain, and `action_set_unlink` writes "Record deleted" on each scheduled trace. The…
`sync_participants` calls `action_set_unlink` on every participant whose record is no longer in the campaign domain, and `action_set_unlink` writes "Record deleted" on each scheduled trace. The removed bucket also contains records that still exist but no longer match the campaign filter, so the cancelled trace dialog shows "Record deleted" even when the record was only filtered out. In `sync_participants`, the to_remove participants are split between those whose record still exists in the database (filtered out by the campaign domain) and those whose record was actually deleted. `action_set_unlink` accepts an optional `trace_message` argument, defaulting to "Record deleted", and the filtered-out batch passes "Record no longer matches campaign filter" so the cancelled trace dialog reflects the real cause. Steps to reproduce: 1. Install Marketing Automation and CRM. 2. Open Marketing Automation, create a campaign on Lead with filter Stage = New. 3. Add a begin activity to the workflow. 4. Open CRM, create a Lead in the New stage. 5. Back in the campaign, click Generate Participants. 6. In the CRM pipeline, drag the Lead from New to Qualified. 7. Back in the campaign, click Generate Participants again. 8. Open the Participants smart button, click the participant for the moved Lead. 9. Click the cancelled activity in the workflow timeline. => The activity dialog shows "Error message: Record deleted" although the Lead still exists. Ticket [link](https://www.odoo.com/odoo/project/49/tasks/6251264) opw-6251264 Forward-Port-Of: odoo/enterprise#118692
This fix ensures the Frontdesk app includes the needed scheduling component for its timeline-style planning view. It prevents missing dependency issues so users can access the view without installation or loading errors.
Original PR description
runbot-237869
The POS now shows longpolling as the default IoT connection type instead of WebRTC. This avoids misleading users when their IoT setup does not support WebRTC, while still allowing WebRTC to appear once it is actually used.
Original PR description
Before this commit, the connection status for the IoT HTTP service was defaulting to "webrtc", at least until the first IoT call is made. This would lead to WebRTC being shown as the connection type in the POS even when using a new IoT image that doesn't support it. After this commit, we use "longpolling" as the default status, which is supported by all IoT images. If WebRTC is being used, that status will be set after the first action is performed. task-6366366 Forward-Port-Of: odoo/enterprise#123445
This update preserves the expected behavior of file navigation in Knowledge after a Chrome browser change. It prevents a minor compatibility issue where scrolling actions could return a different result than before, helping keep the user experience stable.
Original PR description
Since Chrome 150, scrolling methods like `scrollIntoView()` return a Promise instead of `undefined`. This commit adds block braces to ensure the action returns `undefined` and keeps the same behavior as before. Reference: - https://chromestatus.com/feature/5082138340491264 - https://chromium.googlesource.com/chromium/src/+/50f3e3d0a9bc02aad8b8161dbdd59046991dd2c7 runbot-941309 Forward-Port-Of: odoo/enterprise#123231 Forward-Port-Of: odoo/enterprise#123031
The Peru Profit and Loss report now includes Other Operating Income when calculating gross profit. This ensures gross profit, operating profit, pre-tax result, and net profit reflect these income entries accurately.
Original PR description
**Steps to reproduce:** 1. Install `l10n_pe` and switching to the Peru company. 2. Create and post a journal entry with a line on account 7520000 (Other Operating Income). 3. Open the Profit and Loss report (PE). 4. The amount appears correctly under "Other operating income" (`PE_PNL_A_5`). 5. "Gross profit", "Operating profit" , "Result before taxes" and "Net Profit" do not change when this amount is added or removed. **Issue:** The "Other operating income" line is excluded from the Gross Profit calculation, and consequently from Operating Profit and every downstream total in the PE Profit and Loss report. **Why this happens:** Gross Profit (`PE_PNL_A_4`) balance expression uses the aggregation with formula `PE_PNL_A.balance - PE_PNL_A_3.balance`, which doesn't include `PE_PNL_A_5.balance` as a term opw-6283907 Forward-Port-Of: odoo/enterprise#123063
The French Intrastat export warning links now open only the journal entries related to missing required values. This helps users quickly find and correct the affected records instead of sorting through unrelated entries.
Original PR description
Steps to reproduce: 1. Have a French company with intrastat report module installed 2. Create and validate a bill to another EU country, without filling out at least one of the required intrastat fields 3. Go to the intrastat report, and export it as XML DEBWEB2 4. In the export wizard, click on the internal links on the warning messages Issues: 1. In the Intrastat report in French localization, when there are missing values detected in the export, the Export Wizard shows internal links that lead to every journal entries - instead of showing only the relevant entries. The warning banner on the report uses the action action_invalid_code_moves which has a domain to limit what is shown on the view form. However in the method _fill_value_errors there was no domain. opw-6215339 Forward-Port-Of: odoo/enterprise#117997
Grid views grouped by a selection field now show the readable label in the list title when users open a cell's details. This avoids exposing internal technical values and makes the screen clearer for end users.
Original PR description
When grouping a grid view by a selection field and clicking on the cell magnifier, the list title showed the technical name (e.g. non_billable) instead of the display name (e.g. "Non Billable"). This commit adds a condition specifically for selection fields, ensuring that their display names are used. task-5980035 Forward-Port-Of: odoo/enterprise#122303 Forward-Port-Of: odoo/enterprise#120894
This fixes blank US check printing so the payment stub lines appear consistently, matching pre-printed check behavior. It also adjusts the blank check layout so the bottom section fits on a single page, reducing printing issues and wasted checks.
Original PR description
See individual commits. task-6359599 Forward-Port-Of: odoo/enterprise#123144
Opening the restriction fields on appointment slots no longer causes an error. This fixes a broken form interaction so users can manage appointment slot restrictions without interruption.
Original PR description
Clicking the "Restrict to User" or "Restrict to Resources" field on a slot crashed with:
invalid input syntax for type integer: "appointment_type_id.staff_user_ids"
The field domain was a quoted string instead of a list, so it was passed through as a literal value. Remove the domain: it never filtered anything and only broke the form.
opw-6349497
Forward-Port-Of: odoo/enterprise#12265111 changes
Resolved issues and error corrections
The Czech VIES report export now follows the required XML structure more accurately by excluding email information and adding missing taxpayer city and individual representative details. This helps businesses submit valid VIES reports to Czech tax authorities and avoid upload validation errors.
Original PR description
**PROBLEM** For VIES report, the xml should not contains the email. The city of the tax payer is missing, and while it's not strictly require, it can modify the tax regime of the payer, so we need to include it in the xml. There is missing fields in the case the company is an individual (zast_jmeno, zast_prijmeni). **STEP TO REPRODUCE** 1. Create an invoice to a EU partner, don't forget to set the transaction code on the invoice line (unhide the field). 2. Go to the VIES reports, and generate the xml. 3. Upload it to https://mojedane.gov.cz/pmd/epo to validate and see the errors. documentation: https://mojedane.gov.cz/dpr/adis/idpr_pub/epo2_info/popis_struktury_detail.faces?zkratka=DPHSHV opw-6190983 Forward-Port-Of: odoo/enterprise#122996 Forward-Port-Of: odoo/enterprise#117698
This fixes an error that could stop Belgian CODA bank statement files from importing when they include type 4 blocks. Businesses using Belgian bank statement imports can now process these files without encountering a traceback.
Original PR description
### Issue: After the fix in commit (https://github.com/odoo/enterprise/commit/3ef8ae7a6b8eb362e18c74dfab9aadce792b5dc2), importing a CODA file containing a type 4 block raises a traceback ### Cause: That commit introduced `communication_struct_by_ref_move`, which iterates over all lines and accesses `line['communication_struct']` Type 4 lines are not assigned a `communication_struct` value by the parser in `_get_coda_file_statements` Accessing the key directly raises a `KeyError` in `_get_coda_final_statements` in `communication_struct_by_ref_move` ### Steps to reproduce: - Install `l10n_be_coda` - Switch to the BE company - Create a Bank Journal with account `BE33737018595246` - Go to the Accounting Dashboard and import a CODA file containing a type 4 block (Like the one on the ticket) Before the fix, a traceback is raised on import opw-6363148 Forward-Port-Of: odoo/enterprise#123222
Delivery processing with Envia now correctly handles Colombian cities that use shorter postal codes, such as Santa Fe de Antioquia. This helps prevent failed shipments for affected Colombian locations by formatting codes according to official postal standards.
Original PR description
Issue ----- Delivery does not always work from/to some cities in Colombia, like Antioquia. Cause ----- There was an oversight in fix 7654c55 where only 5 digit postal codes taken from the colombian localisation were padded in https://github.com/odoo/enterprise/blob/390acf532e8932fd9b9a708382a5e36cdbb35754/delivery_envia/models/envia_request.py#L726-L727 However, some of the colombian cities listed in `l10n_co_edi/data/res.city.csv` have 4 digit codes (like `SANTA FÉ DE ANTIOQUIA`, code `5042`). https://github.com/odoo/enterprise/blob/7cceddaf086d849b8e2121e1023ef3479397534f/l10n_co_edi/data/res.city.csv#L12 These 4 digit codes have to be right-padded to 5 characters before the left-padding to match the official colombian zip codes. See colombian gov official document (PDF download) where the code is actually `05042`. https://www.dane.gov.co/files/censo2005/provincias/subregiones.pdf ----- Ticket: opw-6248252 Forward-Port-Of: odoo/enterprise#120164
This fix prevents delivery tracking from failing when EasyPost returns an empty tracking record. Users can continue processing shipments without seeing an unexpected error caused by missing carrier tracking details.
Original PR description
The PR https://github.com/odoo/enterprise/pull/111833 handled the specific case when the tracker data is missing from the EasyPost response, however in certain cases `tracker` key exists, but it has a `None` value, which leads to a traceback when trying to access the stock move:
```
File "/home/odoo/src/enterprise/18.0/delivery_easypost/models/easypost_request.py", line 392, in get_tracking_link
public_url = shipment.get('tracker', {}).get('public_url')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'get'
```
This commit provides a fallback to avoid getting the error from the side of the user.
opw-6270242
Forward-Port-Of: odoo/enterprise#119400This fix keeps Knowledge file actions working consistently after a Chrome browser change. It prevents a background browser behavior change from altering how the feature responds, preserving the previous user experience.
Original PR description
Since Chrome 150, scrolling methods like `scrollIntoView()` return a Promise instead of `undefined`. This commit adds block braces to ensure the action returns `undefined` and keeps the same behavior as before. Reference: - https://chromestatus.com/feature/5082138340491264 - https://chromium.googlesource.com/chromium/src/+/50f3e3d0a9bc02aad8b8161dbdd59046991dd2c7 runbot-941309 Forward-Port-Of: odoo/enterprise#123231 Forward-Port-Of: odoo/enterprise#123031
The Peru Profit and Loss report now includes Other Operating Income when calculating gross profit and related totals. This ensures figures such as operating profit, result before taxes, and net profit accurately reflect these income entries.
Original PR description
**Steps to reproduce:** 1. Install `l10n_pe` and switching to the Peru company. 2. Create and post a journal entry with a line on account 7520000 (Other Operating Income). 3. Open the Profit and Loss report (PE). 4. The amount appears correctly under "Other operating income" (`PE_PNL_A_5`). 5. "Gross profit", "Operating profit" , "Result before taxes" and "Net Profit" do not change when this amount is added or removed. **Issue:** The "Other operating income" line is excluded from the Gross Profit calculation, and consequently from Operating Profit and every downstream total in the PE Profit and Loss report. **Why this happens:** Gross Profit (`PE_PNL_A_4`) balance expression uses the aggregation with formula `PE_PNL_A.balance - PE_PNL_A_3.balance`, which doesn't include `PE_PNL_A_5.balance` as a term opw-6283907 Forward-Port-Of: odoo/enterprise#123063
Grid view list titles now show the user-friendly label for grouped selection values, such as "Non Billable", instead of internal codes like "non_billable". This makes drill-down results easier to understand and reduces confusion for users reviewing grouped grid data.
Original PR description
When grouping a grid view by a selection field and clicking on the cell magnifier, the list title showed the technical name (e.g. non_billable) instead of the display name (e.g. "Non Billable"). This commit adds a condition specifically for selection fields, ensuring that their display names are used. task-5980035 Forward-Port-Of: odoo/enterprise#122303 Forward-Port-Of: odoo/enterprise#120894
The French Intrastat export wizard now opens only the journal entries related to missing required Intrastat information. This prevents users from being sent to unrelated accounting entries and makes it faster to identify and fix export issues.
Original PR description
Steps to reproduce: 1. Have a French company with intrastat report module installed 2. Create and validate a bill to another EU country, without filling out at least one of the required intrastat fields 3. Go to the intrastat report, and export it as XML DEBWEB2 4. In the export wizard, click on the internal links on the warning messages Issues: 1. In the Intrastat report in French localization, when there are missing values detected in the export, the Export Wizard shows internal links that lead to every journal entries - instead of showing only the relevant entries. The warning banner on the report uses the action action_invalid_code_moves which has a domain to limit what is shown on the view form. However in the method _fill_value_errors there was no domain. opw-6215339 Forward-Port-Of: odoo/enterprise#117997
Fixed an issue where selecting the "Restrict to User" or "Restrict to Resources" fields on appointment slots caused the form to crash. Removing the faulty filter keeps the slot form usable without changing how restrictions are applied.
Original PR description
Clicking the "Restrict to User" or "Restrict to Resources" field on a slot crashed with:
invalid input syntax for type integer: "appointment_type_id.staff_user_ids"
The field domain was a quoted string instead of a list, so it was passed through as a literal value. Remove the domain: it never filtered anything and only broke the form.
opw-6349497
Forward-Port-Of: odoo/enterprise#122651Blank US checks now include the same stub line details that already appeared on pre-printed checks. The check bottom layout was also adjusted so blank checks fit on a single page, reducing printing errors and wasted paper.
Original PR description
See individual commits. task-6359599 Forward-Port-Of: odoo/enterprise#123144
Users can now create an expense from a document even if their account is not linked to an employee record. They must still have permission to create expenses for another employee, so access controls remain in place while removing an unnecessary blocker.
Original PR description
Removes the constraint saying a user has to be linked to an employee to create an expense from a document. In this case, the user still needs the rights to create an expense for another employee. task-6237021 Forward-Port-Of: odoo/enterprise#123368 Forward-Port-Of: odoo/enterprise#118955
12 changes
Resolved issues and error corrections
This fix preserves the existing behavior of file-related navigation in Knowledge when users run newer Chrome versions. It prevents a browser change from unintentionally affecting how the Knowledge app handles scrolling actions.
Original PR description
Since Chrome 150, scrolling methods like `scrollIntoView()` return a Promise instead of `undefined`. This commit adds block braces to ensure the action returns `undefined` and keeps the same behavior as before. Reference: - https://chromestatus.com/feature/5082138340491264 - https://chromium.googlesource.com/chromium/src/+/50f3e3d0a9bc02aad8b8161dbdd59046991dd2c7 runbot-941309 Forward-Port-Of: odoo/enterprise#123231 Forward-Port-Of: odoo/enterprise#123031
Fixed an issue where time off warnings appeared in the project Gantt view but the unavailable time was not visually grayed out when multiple companies were selected. This keeps scheduling guidance consistent for users managing tasks across several companies.
Original PR description
Steps to reproduce: - Create one user linked to two companies. - Create one employee per company for that user. - Select one company and approve a time off for the employee. - Open Tasks > Gantt and…
Steps to reproduce: - Create one user linked to two companies. - Create one employee per company for that user. - Select one company and approve a time off for the employee. - Open Tasks > Gantt and create a task during the approved time-off period the warning is shown and the Gantt cell is grayed out. - Select both companies and create a task during the same time-off period in Tasks > Gantt. Issue: When multiple companies are selected, the time-off warning is still displayed but the corresponding Gantt cells are no longer grayed out, leading to an inconsistency between the warning logic and the Gantt rendering. Cause: In multi-company setups, a user can be linked to multiple resources. The Gantt unavailability logic assumed a one-to-one relationship between user and resource causing unavailability intervals from some resources to be overwritten. Solution: Aggregate unavailability intervals from all resources linked to the same user, limited to the selected companies, and merge them with the company calendar unavailability to ensure consistent Gantt gray rendering. Related PR: https://github.com/odoo/enterprise/pull/57028 task-5089385
Canceled appointment bookings no longer prevent businesses from lowering a resource's capacity. This avoids an error when updating resources after a booking has been canceled, keeping appointment management smoother and more accurate.
Original PR description
Steps to reproduce: 1. Install `appointment` 2. Create an appointment type using resources 3. Create a resource with capacity `4` 4. Book an appointment on that resource with reserved capacity `4` 5.…
Steps to reproduce: 1. Install `appointment` 2. Create an appointment type using resources 3. Create a resource with capacity `4` 4. Book an appointment on that resource with reserved capacity `4` 5. Try to reduce the resource capacity 6. Cancel the appointment 7. Try again to reduce the resource capacity Current behavior: - Reducing the resource capacity raises: `The capacity used can not be lesser than the capacity reserved` Issue: - Canceled appointments still prevent lowering the resource capacity, even though the booking is no longer active. Cause: - Canceling an appointment only archives the related `calendar.event`, it does not remove the corresponding `appointment.booking.line`. That booking line keeps its previous `capacity_reserved` value, while `capacity_used` is a stored computed field depending on the resource capacity. When the resource capacity is reduced after cancellation, the archived booking line is recomputed with the new lower resource capacity and this violates the SQL constraint Solution: - For inactive booking lines: Set `capacity_used = capacity_reserved` opw-6036053 Forward-Port-Of: odoo/enterprise#112485
This fixes delivery issues for Colombian cities whose postal codes were not formatted correctly, such as Santa Fe de Antioquia. Envia shipping requests now use the correct official postal code format, helping affected deliveries process successfully.
Original PR description
Issue ----- Delivery does not always work from/to some cities in Colombia, like Antioquia. Cause ----- There was an oversight in fix 7654c55 where only 5 digit postal codes taken from the colombian localisation were padded in https://github.com/odoo/enterprise/blob/390acf532e8932fd9b9a708382a5e36cdbb35754/delivery_envia/models/envia_request.py#L726-L727 However, some of the colombian cities listed in `l10n_co_edi/data/res.city.csv` have 4 digit codes (like `SANTA FÉ DE ANTIOQUIA`, code `5042`). https://github.com/odoo/enterprise/blob/7cceddaf086d849b8e2121e1023ef3479397534f/l10n_co_edi/data/res.city.csv#L12 These 4 digit codes have to be right-padded to 5 characters before the left-padding to match the official colombian zip codes. See colombian gov official document (PDF download) where the code is actually `05042`. https://www.dane.gov.co/files/censo2005/provincias/subregiones.pdf ----- Ticket: opw-6248252 Forward-Port-Of: odoo/enterprise#120164
This update prevents delivery tracking from failing when EasyPost returns an empty tracker value. Users can continue working without seeing an error while the system falls back safely when tracking link data is unavailable.
Original PR description
The PR https://github.com/odoo/enterprise/pull/111833 handled the specific case when the tracker data is missing from the EasyPost response, however in certain cases `tracker` key exists, but it has a `None` value, which leads to a traceback when trying to access the stock move:
```
File "/home/odoo/src/enterprise/18.0/delivery_easypost/models/easypost_request.py", line 392, in get_tracking_link
public_url = shipment.get('tracker', {}).get('public_url')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'get'
```
This commit provides a fallback to avoid getting the error from the side of the user.
opw-6270242
Forward-Port-Of: odoo/enterprise#119400Users can now create an expense from a document even if their account is not linked to an employee. They still need permission to create expenses for another employee, so the change removes an unnecessary blocker while keeping access controls in place.
Original PR description
Removes the constraint saying a user has to be linked to an employee to create an expense from a document. In this case, the user still needs the rights to create an expense for another employee. task-6237021 Forward-Port-Of: odoo/enterprise#118955
This fixes an issue in the Documents app where the selection bar could remain visible but stop working after moving between folders that contain the same file. Users can now continue selecting, clearing, or adding documents without switching views or clicking elsewhere to reset the page.
Original PR description
**Steps to reproduce:** - Install Documents app - Go to a folder and select a file - SelectionBox appears at the top - Click on another folder containing the same file (e.g. "All") - Box is still…
**Steps to reproduce:** - Install Documents app - Go to a folder and select a file - SelectionBox appears at the top - Click on another folder containing the same file (e.g. "All") - Box is still there, but interactions are broken (can't clear/add) - Going to another view type (kanban/list) or clicking elsewhere reset it **Issue:** `SelectionBox` doesn't update itself on folder changes, but `_reapplySelection` still tries to re-select the relevant records when switching folders (it was probably done to make it work when switching views, as it re-mount the component). **Fix:** Add current selected category to the `t-key` to trigger a re-rendering of the `SelectionBox` using a state with the current folder id. In 18.3 it was partially fixed in the same way by using the `targetRecords.length` in [1]. Feels like a hack (and we add a state just for this) but it ensures the component has the proper data for the current folder. We could also disable it on load for now, and we should make sure people don't accidentally use the state as the actual folder value... [1] https://github.com/odoo/enterprise/commit/8baaad621a1555680cd77ab514f626137b5b584d (Also a module update is needed to apply the fix due to the xml changes, so it might be better to apply it in master) opw-6348049
The Peru Profit and Loss report now includes Other Operating Income when calculating gross profit. This ensures gross profit, operating profit, pre-tax result, and net profit reflect those income entries accurately for financial reporting.
Original PR description
**Steps to reproduce:** 1. Install `l10n_pe` and switching to the Peru company. 2. Create and post a journal entry with a line on account 7520000 (Other Operating Income). 3. Open the Profit and Loss report (PE). 4. The amount appears correctly under "Other operating income" (`PE_PNL_A_5`). 5. "Gross profit", "Operating profit" , "Result before taxes" and "Net Profit" do not change when this amount is added or removed. **Issue:** The "Other operating income" line is excluded from the Gross Profit calculation, and consequently from Operating Profit and every downstream total in the PE Profit and Loss report. **Why this happens:** Gross Profit (`PE_PNL_A_4`) balance expression uses the aggregation with formula `PE_PNL_A.balance - PE_PNL_A_3.balance`, which doesn't include `PE_PNL_A_5.balance` as a term opw-6283907 Forward-Port-Of: odoo/enterprise#123063
Opening the user or resource restriction fields on appointment slots no longer causes an error. The broken filter was removed because it did not limit choices and only prevented users from editing the form reliably.
Original PR description
Clicking the "Restrict to User" or "Restrict to Resources" field on a slot crashed with:
invalid input syntax for type integer: "appointment_type_id.staff_user_ids"
The field domain was a quoted string instead of a list, so it was passed through as a literal value. Remove the domain: it never filtered anything and only broke the form.
opw-6349497
Forward-Port-Of: odoo/enterprise#122651Grid view pop-up lists now display the user-friendly label for grouped selection fields instead of internal technical values. This makes drilled-down list titles clearer and easier for users to understand, especially for labels like "Non Billable".
Original PR description
When grouping a grid view by a selection field and clicking on the cell magnifier, the list title showed the technical name (e.g. non_billable) instead of the display name (e.g. "Non Billable"). This commit adds a condition specifically for selection fields, ensuring that their display names are used. task-5980035 Forward-Port-Of: odoo/enterprise#122303 Forward-Port-Of: odoo/enterprise#120894
Fixed the French Intrastat export warnings so their internal links open only the journal entries that actually have missing required information. This helps users correct export issues faster and avoids confusion from being sent to unrelated accounting entries.
Original PR description
Steps to reproduce: 1. Have a French company with intrastat report module installed 2. Create and validate a bill to another EU country, without filling out at least one of the required intrastat fields 3. Go to the intrastat report, and export it as XML DEBWEB2 4. In the export wizard, click on the internal links on the warning messages Issues: 1. In the Intrastat report in French localization, when there are missing values detected in the export, the Export Wizard shows internal links that lead to every journal entries - instead of showing only the relevant entries. The warning banner on the report uses the action action_invalid_code_moves which has a domain to limit what is shown on the view form. However in the method _fill_value_errors there was no domain. opw-6215339 Forward-Port-Of: odoo/enterprise#117997
Blank US checks now include the same payment stub lines as pre-printed checks, making them easier to read and reconcile. The check layout was also adjusted so the bottom section prints on a single page instead of spilling onto a second page.
Original PR description
See individual commits. task-6359599 Forward-Port-Of: odoo/enterprise#123144
3 changes
Resolved issues and error corrections
Canceled appointment bookings will no longer prevent staff from reducing a resource's capacity. This avoids an error after cancellations and keeps resource capacity management aligned with active bookings only.
Original PR description
Steps to reproduce: 1. Install `appointment` 2. Create an appointment type using resources 3. Create a resource with capacity `4` 4. Book an appointment on that resource with reserved capacity `4` 5.…
Steps to reproduce: 1. Install `appointment` 2. Create an appointment type using resources 3. Create a resource with capacity `4` 4. Book an appointment on that resource with reserved capacity `4` 5. Try to reduce the resource capacity 6. Cancel the appointment 7. Try again to reduce the resource capacity Current behavior: - Reducing the resource capacity raises: `The capacity used can not be lesser than the capacity reserved` Issue: - Canceled appointments still prevent lowering the resource capacity, even though the booking is no longer active. Cause: - Canceling an appointment only archives the related `calendar.event`, it does not remove the corresponding `appointment.booking.line`. That booking line keeps its previous `capacity_reserved` value, while `capacity_used` is a stored computed field depending on the resource capacity. When the resource capacity is reduced after cancellation, the archived booking line is recomputed with the new lower resource capacity and this violates the SQL constraint Solution: - For inactive booking lines: Set `capacity_used = capacity_reserved` opw-6036053 Forward-Port-Of: odoo/enterprise#112485
Twitter/X social stream posts now save the reply count provided by the platform's API. This ensures comments are shown alongside other engagement metrics, giving users a more complete view of tweet performance.
Original PR description
Twitter/X tweet metrics returned by the API include the number of replies in the `public_metrics.reply_count` field. This commit stores that value on social stream posts so the comments count can be displayed alongside other engagement metrics. API Documentation: https://docs.x.com/x-api/fundamentals/metrics#post-metrics Task-6251172 Forward-Port-Of: odoo/enterprise#120182
The scheduled update for Mexican electronic invoices now correctly runs again when there are still documents waiting to be checked. This prevents invoices from being left unprocessed when updates are handled in smaller batches.
Original PR description
Steps to reproduce ----------------- - Install l10n_mx_edi; - Switch to the mexican company; - Create 3 invoices for the mexican company (you will need to set an UNSPSC code on the products); - Send them to CFDI; - Go to the scheduled action "Automatic update of state on the SAT" and add "batch_size=2" to the method's parameters; - Manually run the cron; - Only two invoices will be updated, the cron is not retriggered to process the remianing one. Why is it hapening ------------------ We set a limit of batch_size + 1 in the search method, and the cron is retriggered if and only if the number of documents fetched is equal to the batch size, meaning there is no more documents to fetch. This should be triggered if we fetched more documents than the batch size. opw-6328118 Forward-Port-Of: odoo/enterprise#122659
5 changes
Enhancements to existing features
Attachment deletion is now much faster for Chilean electronic invoicing and stock documents. This reduces delays in databases with large numbers of invoices or deliveries, improving day-to-day performance without changing business workflows.
Original PR description
## The problem Deleting attachments checks foreign key triggers. Lookups in `account_move` and `stock_picking` tables for `ir_attachment` related fields coming from `l10n_cl_edi` overrides were slow due to missing indexes. ## The solution Added needed indexes to optimize triggers' lookups. ## Benchmark Time benchmark (deleting attachments from a customer database with 224K account moves and 204K stock pickings): |# of rows|Time (Before)|Time (After)| |----------|--------------|-------------| 100 | 29s | 16ms 1000 | 285s | 300ms OPW-6331845 Forward-Port-Of: odoo/enterprise#123252
Resolved issues and error corrections
The Peru Profit and Loss report now includes Other Operating Income when calculating gross profit and related totals. This ensures key profitability figures such as operating profit, result before taxes, and net profit reflect all relevant income accurately.
Original PR description
**Steps to reproduce:** 1. Install `l10n_pe` and switching to the Peru company. 2. Create and post a journal entry with a line on account 7520000 (Other Operating Income). 3. Open the Profit and Loss report (PE). 4. The amount appears correctly under "Other operating income" (`PE_PNL_A_5`). 5. "Gross profit", "Operating profit" , "Result before taxes" and "Net Profit" do not change when this amount is added or removed. **Issue:** The "Other operating income" line is excluded from the Gross Profit calculation, and consequently from Operating Profit and every downstream total in the PE Profit and Loss report. **Why this happens:** Gross Profit (`PE_PNL_A_4`) balance expression uses the aggregation with formula `PE_PNL_A.balance - PE_PNL_A_3.balance`, which doesn't include `PE_PNL_A_5.balance` as a term opw-6283907 Forward-Port-Of: odoo/enterprise#123063
This fix prevents delivery tracking from failing when EasyPost returns an empty tracker value. Users can continue working without seeing an error when tracking information is incomplete or unavailable.
Original PR description
The PR https://github.com/odoo/enterprise/pull/111833 handled the specific case when the tracker data is missing from the EasyPost response, however in certain cases `tracker` key exists, but it has a `None` value, which leads to a traceback when trying to access the stock move:
```
File "/home/odoo/src/enterprise/18.0/delivery_easypost/models/easypost_request.py", line 392, in get_tracking_link
public_url = shipment.get('tracker', {}).get('public_url')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'get'
```
This commit provides a fallback to avoid getting the error from the side of the user.
opw-6270242
Forward-Port-Of: odoo/enterprise#119400Field Service users can now create and design a worksheet template from a task without losing the selected template afterward. This prevents extra manual re-selection and makes the worksheet setup flow more reliable.
Original PR description
[REF] worksheet: changes to widget to be made extendable Made changes to OpenStudioWidget to be made extensible across multiple modules. --- [FIX] industry_fsm_report: fixed onthe fly creation of worksheet not linked Steps to reproduce: - Open Field Service - Go to any task and create a work sheet on the fly - In the wizard click on Design Template - Close the studio and go back to task Issue: - You can see that the newly created worksheet is not set Cause: - The reason for not getting set is due to fact that worksheet template is created after we click on Design Template which is a widget of view_widgets. - Therefore the field doesn't get dirty and doesn't get saved. Solution: - Using bus to save the worksheet changes in Project record. task-3871593
This fix prevents an error when users propose adding a step from the Shop Floor for manufacturing orders whose bill of materials contains very similar operations. It helps production teams submit improvement suggestions reliably without being blocked by a system traceback.
Original PR description
[FIX] mrp_workorder_plm: avoid singleton traceback when matching BOM operations **Issue** Adding a step in a manufacturing order with two operations that are too similar could lead to a traceback…
[FIX] mrp_workorder_plm: avoid singleton traceback when matching BOM operations **Issue** Adding a step in a manufacturing order with two operations that are too similar could lead to a traceback when proposing an improvement from the Shop Floor. **Steps to reproduce** - Install the Product Lifecycle Management app. - Create a BOM for any product with two operations that: - Have the same name and work center - Have no variant - Create and confirm a Manufacturing Order for that product. - Open the Shop Floor view. - Click the gear icon -> Update Instructions -> Improvement Suggestion -> Add a Step -> Propose a Change. -> A traceback occurs **Cause** When adding a step: https://github.com/odoo/enterprise/blob/1c02615b73a2b4fb9f1ab91167a3c8d7c9ac824c/mrp_workorder_plm/models/mrp_workorder.py#L10 It tries to find the corresponding operation in the ECO's new BoM. This relies on `_get_sync_values()`: https://github.com/odoo/enterprise/blob/1c02615b73a2b4fb9f1ab91167a3c8d7c9ac824c/mrp_plm/models/mrp_routing.py#L9-L13 Because two operations share the same name, work center, and no variant, both match the filter: https://github.com/odoo/enterprise/blob/1c02615b73a2b4fb9f1ab91167a3c8d7c9ac824c/mrp_workorder_plm/models/mrp_workorder.py#L39 This results in a singleton error when accessing `operation.id`: https://github.com/odoo/enterprise/blob/1c02615b73a2b4fb9f1ab91167a3c8d7c9ac824c/mrp_workorder_plm/models/mrp_workorder.py#L42 opw-6241880
2 changes
Resolved issues and error corrections
The Argentine VAT Book ZIP export now handles foreign partners marked as overseas providers without blocking the process. This prevents export errors and helps accounting teams complete VAT reporting for foreign transactions more reliably.
Original PR description
Steps to reproduce: - Create a partner with: - State: Ireland - Identification Number: Foreign ID 55000004153 - ARCA Responsibility Type: Proveedor del Exterior - Create an invoice for the partner - Accounting > Reporting > Tax report - Select Report: VAT Book (AR), Tax Type: Sales - Click on gear icon > VAT Book (ZIP) Issue: Action will be blocked with error "No VAT configured for partner [58] <partner>" Analysis: Partners with ARCA responsibility type 'Proveedor del Exterior' (code 8) and a ForeignID identification type, causes a UserError when exporting the VAT Book (ZIP). Code 8 (foreign provider) is the purchase-side counterpart of code 9 (foreign customer), which already fell back to the country-level VAT. Extend the existing fallback branch to cover both codes. opw-6316008
Canadian check printing now hides check numbers on payment stubs when pre-numbered checks are used. This keeps the printed stubs consistent with the checks themselves and avoids displaying duplicate or misleading numbers.
Original PR description
The check itself respected the check_manual_sequencing field, but the stubs did not. Hide the numbers on stubs as well, exactly like on US checks. task-6343701