Wednesday, April 1, 2026
41 changes · saas-19.2
Enhancements to existing features
The sickness relapse checkbox in Belgian payroll processing now defaults to unchecked instead of checked. This change reflects the updated 56-day sickness period policy, where relapse situations are less common and the system now assumes "no relapse" by default, reducing manual corrections needed by HR staff.
Original PR description
Because the sickness period is now 56 days, the heuristic has switched to "mostly always a no," so the relapse checkbox is unchecked by default. Task: 6081595
The payroll system now hides worked day details from the payslip summary widget when they're not needed for certain pay structures, such as bonus-only payments. This change reduces clutter and confusion for pay officers by dynamically showing only relevant information based on the pay structure type.
Original PR description
In order to improve the experience of pay officers and prevent any confusion, some pay structures will not require the display of individual work day lines such as bonuses. Accordingly, the worked day widgets will be dynamic. Task: 6030842
Resolved issues and error corrections
Fixed a bug that caused the AI email composer feature to crash when users selected multiple records before composing an email. The issue occurred because the system couldn't properly handle multiple record IDs. Now the AI feature only activates when a single record is selected, preventing the error and improving the user experience.
Original PR description
Currently an exception is generated when the user tries to click the AI icon in the email composer with multiple records. Steps to produce an error: - Install the `crm` module with the demo data - Go…
Currently an exception is generated when the user tries to click the AI icon in the email composer with multiple records. Steps to produce an error: - Install the `crm` module with the demo data - Go to the CRM list view and select multiple records - Click in `Email` from action > click the `AI` icon on the email composer. Error: `TypeError: int() argument must be a string, a bytes-like object or a real ...` This error is generated because when retrieving the `originalRecordId` from the line [1], the code attempts to remove the first and last characters of a string representation of a list. In the single-selection case, the value is "[4]", so slicing off `[` and `]` correctly yields "4". However, when the user selects multiple records, the value becomes "[4, 5]". Slicing the first and last characters in this case produces "4, 5", and passing this string to Number() results in NaN. As a result, `record_id` becomes `None` when calling `create_ai_draft_channel` method, and passing this None value to int() subsequently raises an error. This commit fixes the issue by assigning `recordId` and `recordModel` only when a single record exists. The record IDs are parsed from their string representation using `JSON.parse`, and the first ID is returned when the list contains exactly one element, or false otherwise. sentry-7201070069 Forward-Port-Of: odoo/enterprise#104873
This fix resolves an issue where the IP (Individual Pension) salary rule was not appearing on employee payslips in the Belgian payroll system. The underlying calculation has been corrected to properly include the IP field, ensuring employees can now see this important compensation component on their payslip documents.
Original PR description
-**Issue**: The IP salary rule was not visible on payslip. -**Fix**: Computation has been adjusted to include the correct field. Forward-Port-Of: odoo/enterprise#110936
This update reverts a previous change to the HR Contract Salary module that affected email functionality. The revert addresses issues introduced in an earlier update and restores the system to a more stable state for salary configuration and applicant management.
Original PR description
Revert https://github.com/odoo/enterprise/pull/111815 Forward-Port-Of: odoo/enterprise#112632
Demo Stripe accounts were not automatically becoming verified when users clicked Connect, remaining in a restricted state. This fix addresses a verification document requirement that was blocking the demo flow. Users can now properly test the Stripe expense feature without manual workarounds.
Original PR description
Step to reproduce: - Click on Connect (demo) - The accounts stay Restricted (even after 5m) This is due to the requirement `company.verification.document` which is present as a pending_verification even when no document is given. Giving it a document such as `file_identity_document_success` which should "marks that document requirement as satisfied" also doesn't work. A work-around is to set the business type to individual since they dont have this requirement. This is a temporary fix, as we want to keep a flow similar to what would have been done in reality. But this is preventing users from testing the feature in 19.0 up to master. Forward-Port-Of: odoo/enterprise#112612
This fix corrects how payment installments are ordered in Peru's electronic invoicing (EDI) documents. Previously, when invoices had multiple payment installments with different due dates, they could appear in the wrong order in the XML file if they weren't created in chronological sequence. Now installments are automatically sorted by their due date, ensuring the correct payment schedule is always reflected in the electronic invoice.
Original PR description
**Steps to reproduce:** * Install `l10n_pe_edi`. * Set SUNAT as the signature provider. * Create a customer invoice. * Select a payment term with multiple installments (e.g. 50% (15 days) / 30% (10…
**Steps to reproduce:**
* Install `l10n_pe_edi`.
* Set SUNAT as the signature provider.
* Create a customer invoice.
* Select a payment term with multiple installments (e.g. 50% (15 days) / 30% (10 days) / 20% (5 days)).
* Confirm the invoice and generate the EDI standard UBL document.
**Observed behavior:**
* The XML `<cac:PaymentTerms>` instalment nodes (Cuota001, Cuota002, etc.) are ordered according to the database insertion order of the receivable lines.
* If the payment term lines are created in a non-chronological order, the installments in the XML mix up the `Cuota` ID, amount, and `PaymentDueDate`.
**Cause:**
* `_add_invoice_payment_terms_nodes` iterates over `invoice.line_ids` to generate the installments, but does not sort the receivable account lines by their `date_maturity`.
**Fix:**
* Explicitly call `.sorted('date_maturity')` on the receivable lines before generating the `invoice_date_due_vals_list`.
opw-6035589
Forward-Port-Of: odoo/enterprise#112162This fix resolves crashes that occurred when printing vendor bills with Colombian DIAN support documents, either when duplicating bills or before sending them to DIAN. The system now properly handles cases where DIAN document information hasn't been created yet, displaying a placeholder message instead of failing.
Original PR description
**Steps to reproduce:** * Install the **l10n_co_dian** module. * Enable DIAN 2.1 operation mode: **Support Documents** in settings. * Create a vendor bill using a **DIAN support document** journal. * Confirm the vendor bill. * Click **Print**. **Observed behavior:** * Printing fails with `ValueError: can only parse strings`. **Cause:** * The report template unconditionally called `_l10n_co_dian_get_extra_invoice_report_values()`, which parses `l10n_co_dian_attachment_id.raw` via `etree.fromstring()`. * On duplicated bills, `l10n_co_dian_document_ids` (and thus the computed `l10n_co_dian_attachment_id`) is empty, so `.raw` is `False`. **Fix:** * Wrap the QR code / CUFE / signing section in the report template with `t-if='o.l10n_co_dian_attachment_id'` so it is only rendered when the DIAN attachment exists. opw-5930173 Forward-Port-Of: odoo/enterprise#109912 Forward-Port-Of: odoo/enterprise#108645
A bug was fixed where products were being incorrectly deleted when a product template had only one product variant. This ensures that products are properly retained during website generation, preventing accidental data loss.
Original PR description
product.product records were being removed when they should not have been in the case that the product.template only has one product.product record. This commit fixes this issue by accounting for the case where there is only one product.product record. Forward-Port-Of: odoo/enterprise#112519
This fix resolves a system error that occurred when creating employee contracts with work schedules that have zero working hours. Previously, the system would crash when trying to calculate hourly wages in this scenario. The fix prevents this error, allowing users to create contracts even with empty work schedules.
Original PR description
When a working schedule has 0 working hours, creating a contract raises a traceback during hourly wage computation. Steps to reproduce the error: - Install ``l10n_au_hr_payroll`` module - Switch to ``My Australian Company`` - Create a working schedule without any working hours - Create an employee and assign this working schedule > Save - Click on Contracts smart button Traceback: ```py ZeroDivisionError: float division by zero ``` https://github.com/odoo/enterprise/blob/bd746aa43f549c4f7813a849e00447b55e084f21/l10n_au_hr_payroll/models/hr_contract.py#L113-L115 The hourly wage is computed using the working schedule’s hours per day. When this value is 0, it results in the above traceback. sentry-7355577930 Forward-Port-Of: odoo/enterprise#112450 Forward-Port-Of: odoo/enterprise#111629
This fix resolves an issue where AI-powered conversations with agents were broken in the AI app. The update corrects how the system identifies conversation participants, preventing the app from incorrectly treating conversations as self-chats, which was causing unwanted side effects like out-of-office banners appearing. Agent workflows that depend on proper participant identification now work correctly.
Original PR description
Override AI thread correspondent computation to keep the base behavior but clear the correspondent when it resolves to the current user in ai_composer/ai_chat. This avoids self-chat fallback side effects (like OOO banner) while preserving agent flows that rely on a real non-self correspondent. source of this crash: https://github.com/odoo/enterprise/pull/108217 Forward-Port-Of: odoo/enterprise#112392 Forward-Port-Of: odoo/enterprise#111736
This fix corrects how shifts are split when using the daily view in the planning schedule. Previously, splitting a shift within a single day would incorrectly snap the resulting shifts to standard working hours (8am-5pm), creating duplicate time entries outside the original shift. Now, shifts split within a day are properly divided at the exact requested time without adding extra hours.
Original PR description
Since odoo/enterprise#69963, splitting a shift in Gantt view snaps the resulting shifts to the standard working hours (e.g., 08:00 to 17:00). This behavior makes sense when splitting a multi-day shift across a day off, where we want to generate a full working day for the day off as well. However, when splitting a shift within a single day (day scale), this behavior is not wanted, otherwise creating two shifts as follows: 1. A shift from [Original Start] to 5pm 2. A shift from 8am to [Original End] Clearly, the shift is duplicated with the wrong timeframes and adding time outside the original shift's hours. This commits ensures that, when splitting a shift in hours (i.e., when using `day` view scale), the shift is appropriately split at the time requested. For instance: 1. A shift from [Original Start] to [Split Time] 2. A shift from [Split Time] to [Original End] task-5387243 Forward-Port-Of: odoo/enterprise#105991
This fix resolves an issue in Web Studio where invisible field attributes were being lost when users added group restrictions to fields. Previously, when a field was restricted to certain user groups, the original invisible condition would disappear from the editor, even though it was still applied to the actual view. Now the system properly preserves these invisible attributes so users can see and edit them correctly.
Original PR description
Steps to reproduce ================== - Install contacts,web_studio - Login as admin - Go to contacts - Open any record - Open studio - Click on any field - Add the "Role / Portal" group - Toggle the…
Steps to reproduce ================== - Install contacts,web_studio - Login as admin - Go to contacts - Open any record - Open studio - Click on any field - Add the "Role / Portal" group - Toggle the "Show invisible Elements" checkbox - Click on the same field => The field is marked as invisible - Add an invisible condition => The invisible condition is lost (but still applied on the view) Cause of the issue ================== In studio, when fetching the view, the invisible attribute is set to True when the user does not have access to the field (when he is not part of the groups). The goal is to make the field invisible in studio unless the "Show invisible Elements" is toggled. But this causes the actual value of the invisible attribute to be lost. Note that this also applies to the column_invisible attribute. Solution ======== If an invisible/column_invisible attribute is present on the nodes with missing access, we copy the actual value to the `actual_invisible` attribute. We then use that value in the editor, when present. opw-6026971 Forward-Port-Of: odoo/enterprise#112084 Forward-Port-Of: odoo/enterprise#111299
This fix ensures that when Provident Fund (PF) is disabled in payroll settings, it no longer appears in the Salary Configurator tool. Previously, the disabled PF benefit could still be displayed and cause errors when users tried to update their salary package. Now the system properly hides PF and prevents related technical errors.
Original PR description
Before: - PF toggle disabled in payroll settings, but “Provident Fund” could still appear in Salary Configurator (Extra Benefits). - Hiding PF from displayed values could make `/salary_package/update_salary` crash with missing `l10n_in_pf_employee_amount`. After: - When `l10n_in_provident_fund` is disabled, PF benefit is filtered out from `_get_benefits_values`. - Empty benefit types are removed, so “Extra Benefits” no longer shows if it only contained PF. - PF initial value is dropped from payload values. - Missing PF value is defaulted to `0.0` in `_get_new_version_values`, preventing update/submit errors. task-6008086
This update fixes how annual leave provisions are calculated in Turkish payroll. The provision now applies only to active employees after their first year, uses clearer rate displays, and is properly included in net salary calculations. This ensures more accurate and transparent payroll processing for Turkish operations.
Original PR description
## Before: - Annual Leave Provision showed a confusing rate value. - It could be triggered too early around the first-year period. - “Excluded from Net” was checked. ## After: - Annual Leave Provision is applied only for active employees after one year. - The amount is based on monthly working days, while the displayed rate stays clear at 100%. - “Excluded from Net” is unchecked. Task-6033568
This fix resolves a system crash that occurred when confirming sales orders containing both regular dropship products and subscription-based dropship products. The issue was caused by inconsistent date format handling in the purchase order system. By standardizing how dates are processed, the system now correctly handles mixed product types without errors.
Original PR description
**Steps to reproduce:** * Install *sale_management*, *sale_subscription*, and *stock* modules. * Go to *Settings* and enable *Dropshipping*. * Create two products: * One *normal dropship* product. *…
**Steps to reproduce:**
* Install *sale_management*, *sale_subscription*, and *stock* modules.
* Go to *Settings* and enable *Dropshipping*.
* Create two products:
* One *normal dropship* product.
* One *dropship + subscription* product.
* Create a *Quotation*.
Add both products to the order.
* Confirm the quotation.
**Observed behavior:**
* A traceback occurs during confirmation:
File '/home/odoo/workspace/odoo19/odoo/addons/purchase_stock/models/stock_rule.py', line 159, in _run_buy
date_planned = po.date_planned or min(v['date_planned'] for v in po_line_values)
TypeError: can't compare datetime.datetime to datetime.date
**Cause:**
* In *_run_buy*, the system computes the earliest *date_planned* using:
https://github.com/odoo/odoo/blob/4056fa8036ddad11c898cd775b7fa21ba4f8a5de/addons/purchase_stock/models/stock_rule.py#L159
* Subscription products set *date_planned* as *datetime.date*,
https://github.com/odoo/enterprise/blob/a88c64a224805d95b60a20c502428897655dba53/sale_subscription_stock/models/sale_order_line.py#L153
`current_period_start = self.order_id.last_invoice_date or
self.order_id.start_date or fields.Date.today()`
* while normal products set *date_planned* as *datetime.datetime*.
https://github.com/odoo/odoo/blob/4056fa8036ddad11c898cd775b7fa21ba4f8a5de/addons/sale_stock/models/sale_order_line.py#L286
before min() It call `_prepare_purchase_order_line_from_procurement`,
the value is assigned directly from `values.get('date_planned')`, and conversion
with `fields.Datetime.to_datetime()` only happens conditionally:
https://github.com/odoo/odoo/blob/98e6e929bf8e0c34ec77fb9d07ef253e0abf681c/addons/purchase_stock/models/purchase_order_line.py#L347-L351
As a result, some values remain `date` while others are `datetime`,
* When both are present in *po_line_values*, Python cannot compare the
two types, causing the crash.
**Fix:**
So by moving the `fields.Datetime.to_datetime()` cast to the initial
assignment of `res['date_planned']`, so it is always a `datetime`
regardless of the source, and removing the now-redundant cast inside
the `if` block.
---
opw-6034079
---
Description of the issue/feature this PR addresses:
Current behavior before PR:
Desired behavior after PR is merged:
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#255063This update fixes an error that occurred when users tried to view custom snippets they had created from map components. The issue happened because the system couldn't find the original map snippet when certain features were disabled. We've also improved the naming of map snippets to make it clearer which one is the Google Map option, reducing confusion when creating custom snippets.
Original PR description
Steps to reproduce: 1. Go to the website editor (ensure developer mode is off) 2. Drag and drop a Map snippet onto the page 3. Click on the newly placed snippet and save it as a custom snippet 4.…
Steps to reproduce: 1. Go to the website editor (ensure developer mode is off) 2. Drag and drop a Map snippet onto the page 3. Click on the newly placed snippet and save it as a custom snippet 4. Enable developer mode and refresh the website editor 5. Add a new Google Map snippet to the page a. The Google Map snippet is the one where the icon shows a map with a pin on the **left side**. 6. In the wizard, enter your valid API key and click Save a. Alternatively, you can use Odoo inspector to write any string into the `google_maps_api_key` field of the `website` model to simulate the above 7. Disable developer mode and refresh the website editor 8. Click one of the categories in the editor side panel to open the snippets browser 9. Click into the 'Custom' snippets category 10. Observe the error Depending on whether or not you have a Google Maps API key configured on your website, either the `s_map` or `s_google_map` base snippet will be disabled/hidden. When a user has created custom snippets out of the disabled base snippet, you will recieve the error mentioned above when the snippet browser attempts to load in these custom snippets, as it will be unable to load the base snippet. To fix this, we check if an original snippet was found when loading in a custom snippet. If not, we will not load in the custom snippet to avoid confusion. This error does not occur in Developer Mode, as both base snippets are always enabled in this case. Aditionally, we also clarify which snippet is the Google Map snippet to avoid confusion for the user when creating custom snippets. opw-5933787 Forward-Port-Of: odoo/odoo#256847 Forward-Port-Of: odoo/odoo#250236
This fix resolves an issue where invoice delivery notifications (webhooks) were being blocked with access errors when sent between user accounts. The system was incorrectly applying access restrictions to webhook processing, preventing invoices from being properly delivered. This update restores the ability to send and receive invoices through the automated notification system.
Original PR description
- Register your user (on a db that allows webhook). - On another registered account, send an invoice to the first one. => A webhook call has been made, resulting into a 403 Forbidden. Following some changes on access rights checks, the webhook don't work. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#256607
This fix corrects an issue where branch companies using their parent company's VAT for PEPPOL registration were incorrectly shown a participation role setting in the configuration. Since branches should only be able to send documents (not receive), this setting is now hidden from the user interface to prevent confusion and ensure proper system behavior.
Original PR description
In v19.0 -> master, if a branch company used the VAT of the parent company to register for peppol, the peppol participation role would be shown in the configuration settings, with a default value being "Sending and receiving", And since branches in this case should be able to ONLY send, the setting shouldnt be visibile to the user. task-none Forward-Port-Of: odoo/odoo#256820
This fix restores the ability to sort grouped lists by date and datetime fields in the Accounting module. Users can now click on the Date column header in grouped views (like Deferred Entries) to sort the data, which was previously broken. This improves the usability of financial reporting and data organization features.
Original PR description
Steps to reproduce 1. Open Accounting > Customers > Invoices and create an invoice with deferred dates. 2. Open the invoice and click the Deferred Entries smart button. 3. In the grouped list view,…
Steps to reproduce 1. Open Accounting > Customers > Invoices and create an invoice with deferred dates. 2. Open the invoice and click the Deferred Entries smart button. 3. In the grouped list view, click the Date column header to sort. 4. Nothing happens. Issue The [IMP] web: Grouped kanban/list in a single RPC (https://github.com/odoo-dev/odoo/commit/26c37c9c070107f8bd753cb8a6d8343384fdd7bf) refactor introduced a regression. _get_read_group_order() [1] iterated over the provided aggregates list to build the ORDER BY clause. Fields with an aggregator attribute that are not included in the aggregates list (e.g. date fields, which getAggregateSpecifications() excludes) were silently dropped from ORDER BY. [1] https://github.com/odoo/odoo/blob/26c37c9c070107f8bd753cb8a6d8343384fdd7bf/addons/web/models/models.py#L496-L514 Solution Add a fallback in _get_read_group_order() so that when fname is neither a groupby field nor present in the provided aggregates list, the method checks field.aggregator directly and appends `fname:aggregator direction` (e.g. `date:min ASC`) to the ORDER BY string. The ORM's _read_group_orderby() already accepts such specs in ORDER BY even without them being in SELECT. opw-6044059 Forward-Port-Of: odoo/odoo#256592
This fix resolves an issue where the coupon code entry form was being hidden after a customer entered a promotional code in their shopping cart. The form will now remain visible and functional when a pricelist with an E-commerce Promotional Code is active, allowing customers to continue using coupon codes as intended.
Original PR description
Issue: --- If the current pricelist has `E-commerce Promotional Code` set, the coupon form is hidden. Steps to reproduce: --- 1- Create a pricelist and set `E-commerce Promotional Code`. 2- Navigate to the cart in website. 3- Enter pricelist code in the coupon form. Result: Once the code is entered, pricelist is changed and the coupon form is hidden. Cause: --- This is done intentionally on #23713. However, it seems it's not relevant anymore. Fix: --- We can remove `force_coupon` condition from `reduction_code` template. However `force_coupon `is also used inside xpath expression in `reduction_coupon_code` template. In order not to break stable we can keep `t-set="force_coupon"` inside the `reduction_code` template, and remove it on master. opw-5977474 Forward-Port-Of: odoo/odoo#256789 Forward-Port-Of: odoo/odoo#252518
The Bluetooth caliper driver was sending measurement readings without a success status flag, causing the system to incorrectly show false disconnection warnings. This fix ensures that successful measurements are properly marked as complete, eliminating the misleading alerts and improving the reliability of Bluetooth device readings.
Original PR description
When a measurement was successfully read via Bluetooth, the driver broadcasted the `value` without the `status` flag. The frontend JavaScript received the `value` but rejected the payload because the `status` was not explicitly marked as successful, leading to a fake disconnection warning. opw-5473691 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#256860
This fix resolves a bug that prevented purchase orders from being confirmed in certain cases. A previous system update changed the name of a technical field from 'product_uom' to 'product_uom_id', but one part of the code was missed. This update corrects that reference so purchase orders can be confirmed without errors.
Original PR description
A big refactor of UOM in saas-18.1 (https://github.com/odoo/odoo/pull/184131) accidentally left a reference to the `product_uom` field on `purchase.order.line`. But that field was renamed to `product_uom_id` in https://github.com/odoo/odoo/pull/186250. In rare edge-cases, if we it `supplierinfo['product_uom_id'] = line.product_uom.id` while confirming a purchase order, it will block it because of a traceback: ``` supplierinfo['product_uom_id'] = line.product_uom.id ^^^^^^^^^^^^^^^^ AttributeError: 'purchase.order.line' object has no attribute 'product_uom'. Did you mean: 'product_id'? ``` This PR fixes the issues by accounting for the field rename. OPW-6068125 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#256768 Forward-Port-Of: odoo/odoo#256416
This fix ensures that archived accounts are properly checked when creating unaffected earnings accounts, preventing validation errors during system upgrades. Previously, the system would fail when an archived account had the same code as a new unaffected earnings account, which was causing upgrade failures for companies using the Saudi Arabia localization module.
Original PR description
Archived accounts are also searched when looking for duplicate codes since https://github.com/odoo/odoo/commit/cd8d9718427e48aaa79be21f9a08e89b79b573f9 We need to check archived accounts as well when creating the unaffected earnings account, to avoid triggering the validation if an archived account has the same code. This is failing on upgrades with `l10n_sa` installed where the account `sa_account_999999` has been archived. Forward-Port-Of: odoo/odoo#256112
This fix resolves an issue where replacing the contact form on the /contactus page would cause submission errors. The problem occurred because the system was using email settings from the old form configuration even after the form was deleted and recreated. The fix ensures that email settings are only applied when the original form is still present, preventing mismatches during form submission.
Original PR description
Problem: There is a data-for span in the /contactus page that sets specific values on the page's form. If this form is deleted and a new form is added, an error will occur when trying to submit the new form. This is because the value set for website_form_signature will use the email set in the data-for, which will not match with the new form. Purpose: Modify the code that sets the website_form_signature value to ensure that the original form is present as well if using the data-for values. Steps to Reproduce in Runbot: 1. Use the Website Editor to delete the form on the /contactus page and create a new one. 2. Attempt to submit the new form. opw-5956030 Forward-Port-Of: odoo/odoo#253410