Daily updates from Odoo
Navigate
Branch
Thursday, August 20, 2026
112 changes
12 changes
Resolved issues and error corrections
Swiss payroll now counts full-day absences correctly for employees without a fixed working schedule. This prevents one-day accident leave from being treated as two days, helping avoid incorrect wage reductions or overstated accident salary amounts.
Original PR description
Issue: Swiss payslips count one extra absence day for employees without a working schedule. A one day accident leave can therefore be prorated as two days, reducing the regular wage and overstating…
Issue: Swiss payslips count one extra absence day for employees without a working schedule. A one day accident leave can therefore be prorated as two days, reducing the regular wage and overstating the accident salary. Steps to reproduce: * Install Swiss Payroll. * Configure a monthly employee without a working schedule. * Assign one day of accident time off. * Generate the payslip for that month. Cause: The Swiss wage computation derives absence boundaries from the date portion of the leave's UTC datetimes: https://github.com/odoo/enterprise/blob/16c29e1bab34b5bcb2b001477d928ec5eb294a97/l10n_ch_hr_payroll/models/hr_payslip.py#L313-L330 A fully flexible employee's full day leave starts at local midnight. In timezones ahead of UTC, that start is stored on the previous UTC date, so the inclusive calendar day computation adds an extra day. Solution: We need to use the requested time off dates for both payslip range filtering and absence proration. These fields preserve the calendar days selected by the user independently of timezone conversion, while leaving the UTC datetimes and half-day handling unchanged. opw-6435086 Forward-Port-Of: odoo/enterprise#127849 Forward-Port-Of: odoo/enterprise#127513
Database neutralization for TikTok Shop now keeps each shop record unique while removing real shop references. This prevents cleanup failures when multiple active TikTok shops exist, making test or sanitized database preparation more reliable.
Original PR description
Steps to produce: --- - Install sale_tiktok module. - Create two active tiktok.shop records. - Run the database neutralization command. Issue: --- - Neutralization fails with a PostgreSQL error:…
Steps to produce: --- - Install sale_tiktok module. - Create two active tiktok.shop records. - Run the database neutralization command. Issue: --- - Neutralization fails with a PostgreSQL error: ```py duplicate key value violates unique constraint tiktok_shop_unique_active_shop` DETAIL: Key (tiktok_shop_ref)=(1) already exists. ``` Root cause: --- - At [1], we are setting `tiktok_shop_ref = 1` for all `tiktok_shop` records. Because `tiktok_shop` enforces a partial unique constraint on `tiktok_shop_ref` for active shops [2], setting the same reference value `1` on multiple active shops violates this constraint. Solution: --- - Update sql to assign a row-unique string to each shop. This strips the real shop reference while maintaining uniqueness across active shop records so neutralization completes cleanly. [1]https://github.com/odoo/enterprise/blob/85754b0354b76da8b4d87a3a81dd19679ed35d15/sale_tiktok/data/neutralize.sql#L1-L8 [2]https://github.com/odoo/enterprise/blob/85754b0354b76da8b4d87a3a81dd19679ed35d15/sale_tiktok/models/tiktok_shop.py#L136-L139 opw-6451715 --- Forward-Port-Of: odoo/enterprise#127532
The product catalog opened from Field Service tasks now gives more space to the unit of measure column. This improves readability and keeps the Enterprise interface aligned with the related Community update.
Original PR description
Steps to produce: --- - Install `Field service` module. - Create a task and open it. - From the task open the catalog from smart button. Update the Product Catalog UI to match the Community PR changes. community PR: https://github.com/odoo/odoo/pull/267118 opw-6253382 --- Forward-Port-Of: odoo/enterprise#128146 Forward-Port-Of: odoo/enterprise#121139
This fix prevents completed restaurant POS orders from showing again on customer-facing preparation status screens. It restores the correct order filtering so staff and customers see only orders that are still active or ready, reducing confusion during service.
Original PR description
**Steps to reproduce** * Install the `pos_order_tracking_display` module with demo data. * Open the restaurant POS, preparation display, and status screen in separate tabs. * From the POS, send an…
**Steps to reproduce**
* Install the `pos_order_tracking_display` module with demo data.
* Open the restaurant POS, preparation display, and status screen in separate tabs.
* From the POS, send an order to the kitchen.
* In the preparation display, mark the order as **Ready**.
* Verify that the order moves to the **Ready** stage on the status screen.
* In the preparation display, mark the order as **Completed**.
**Observation**
* The completed order moves back to the **Almost There** stage on the status
screen.
* Completed orders should no longer be displayed.
**Cause**
The order stage shown in the preparation display is determined by `_get_pos_orders`. Previously, order lines were retrieved through `_get_open_orderlines_in_display`, which excluded completed orders.
After the refactor, order lines are fetched using `get_preparation_display_orders_domain`, which returns completed order lines as well.
Orders are then split into two groups:
* Orders in the **Ready** stage are displayed as **Ready**.
* All other orders are displayed as **Almost There**.
As a result, completed orders incorrectly appear under **Almost There**.
Additionally, `get_preparation_display_orders_domain` contains an incorrect domain introduced by commit https://github.com/odoo/enterprise/commit/8491f7a74363f7de4fd542e0de0b2f06f00f01ae:
* `last_stage_id` is treated as a string literal:
`('stage_id', '=', 'last_stage_id')`
which always evaluates to `False`.
* The `todo` condition is also inverted.
**Fix**
This commit fixes two issues:
* Exclude completed order lines from the preparation display.
* Restore the correct domain logic by replacing the faulty condition with the
simpler equivalent:
```
'|', ('todo', '=', True), ('stage_id', '!=', last_stage_id)
```
This restores the original behavior while keeping the domain easier.
opw-6423519The cart no longer tries to show rental dates when a rental product order has been converted into a regular sales order. This prevents customers from seeing an error page after the rental period is removed, keeping checkout accessible.
Original PR description
Currently, an error occurs when a user adds a rental product to the cart, opens the corresponding sales order, removes the rental period, and then opens the cart again. Steps to replicate: - Install…
Currently, an error occurs when a user adds a rental product to the cart, opens the corresponding sales order, removes the rental period, and then opens the cart again. Steps to replicate: - Install `website_sale_renting` with demo. - Open website > shop > add the product named `Projector`. - Click Ecommerce in the menu bar > Orders . - Remove the `Confirmed` filter > Click on the top order (should be containing the projector product.) - Remove the `Rental Period` and go to the cart. Error: ``` QWebError: Error while rendering the template: AttributeError: 'bool' object has no attribute 'time' Template: website_sale.shorter_cart_summary ``` Cause: - When the user removes the rental period (`rental_start_date` and `rental_end_date`), both fields are set to `False`. When the cart is opened again, these values trigger the error in [line]. - Since the rental period has been removed from the order, the order is converted to a regular Sales Order (see [PR] and its [task]). Therefore, the Rental Period should no longer be displayed. Solution: - Use `is_rental_order` to determine whether to render the rental period instead of `has_rentable_lines`, since `has_rentable_lines `only checks whether the product is rentable [1], which is determined by the product's `rental_periodicity` [2]. - `is_rental_order` is a better check here because it indicates whether the rental period is actually defined on the order [3]. [line]: https://github.com/odoo/enterprise/blob/7c80c9ffa9e7812267f2ac285e3a3fc5ca501814/website_sale_renting/views/templates.xml#L207 [task]: https://www.odoo.com/odoo/all-tasks/6003684 [PR]: https://github.com/odoo/enterprise/pull/106381/commits/56ec41d81f7536f047a1586a12ea6f6e8414b844 [1]: https://github.com/odoo/enterprise/blob/f23ef9c604d8ce6be152d5e5bf6f72bd68b31451/sale_renting/models/sale_order.py#L140-L143 [2]: https://github.com/odoo/enterprise/blob/f23ef9c604d8ce6be152d5e5bf6f72bd68b31451/sale_renting/models/sale_order_line.py#L61-L64 [3]: https://github.com/odoo/enterprise/blob/f23ef9c604d8ce6be152d5e5bf6f72bd68b31451/sale_renting/models/sale_order.py#L135-L138 sentry-7663524549 Forward-Port-Of: odoo/enterprise#128084
Users now see a clear message if the required Belgian POS Blackbox self-order module is missing. The message explains what needs to be installed, helping staff resolve the opening issue without needing technical troubleshooting.
Original PR description
Replace the bare ValidationError with a user-friendly UserError that explains how to install the required 'l10n_be_pos_blackbox_self_order' module. Task-6388185 Forward-Port-Of: odoo/enterprise#124513
The Indian payroll contract validation message now reflects the employee's actual pay schedule instead of always referring to monthly wages. This reduces confusion when allowances exceed wages and helps users understand the issue in the right payroll context.
Original PR description
**Steps to reproduce:** - Create an indian employee. - Put total allowance `(basic salary + HRA + standard ALW + Perf bonus + travel ALW) > wage` - We will get validation error in employee stating that allowance sum can't be greater than wage. **Before:** - We were always showing monthly wage in the validation error, which was confusing to the end user. **After:** - We will use field `version.shedule_pay` to show dynamic validation error message. Task: [6449791](https://www.odoo.com/odoo/project/1251/tasks/6449791) Forward-Port-Of: odoo/enterprise#127477
The SEPA Direct Debit export now includes a required scheme name field in the initiating party information. This helps ensure payment files are accepted by banks that require it, such as Nordea in Sweden, reducing failed or rejected direct debit submissions.
Original PR description
We are missing a SchmeNm node in the InitgPty node. This is mandatory for Nordea in Sweden at least. Such as: ```xml <SchmeNm> <Cd>CUST</Cd> </SchmeNm> ``` task-6385960 Forward-Port-Of: odoo/enterprise#124693
This fix prevents Mexican payroll processing from failing when a company does not have a VAT/tax ID recorded. Payslip warning checks now handle missing company tax information safely, improving reliability for affected payroll users.
Original PR description
`res.company.vat` is not required and can be `False`. Guard the `len()` call so `_issue_mx_warnings` doesn't crash on payslips for companies without a VAT set.
```py
File "/home/odoo/src/enterprise/saas-19.3/hr_payroll/models/hr_payslip.py", line 1936, in _compute_issues
issues = generate_issue(slip, context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/enterprise/saas-19.3/l10n_mx_hr_payroll_account_edi/models/hr_payslip.py", line 235, in _issue_mx_warnings
if not slip.company_id.l10n_mx_curp and slip._l10n_mx_is_curp_needed():
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/enterprise/saas-19.3/l10n_mx_hr_payroll_account_edi/models/hr_payslip.py", line 325, in _l10n_mx_is_curp_needed
or len(self.company_id.vat) == 13
^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: object of type 'bool' has no len()
```
Forward-Port-Of: odoo/enterprise#128216Fixes an issue where a customer manually set on a planning shift could be removed when the employee signed in or completed the shift. This keeps field service planning records aligned with the user's chosen customer instead of unexpectedly reverting to the sales order customer.
Original PR description
Before this commit, when `sale_planning` module is installed after `planning_field_service` and the user sets a customer onto a shift, the customer could be removed when the user signs in or complete the shift. This issue is because `sale_planning` module defined `partner_id` field as a related field `related="sale_order_id.partner"` and `planning_field_service` module stores the field and so the field will always follows the partner set on the SO linked even if the user sets a customer on the shift. This commit removes the related attribute to replace it by a compute and a search method to have the exact same behavior but the search method will be short-circuited if the partner_id field is stored. task-5264800 Forward-Port-Of: odoo/enterprise#122034
Users now receive a clear notification if they try to add or edit a dynamic field before choosing where it applies. The editor also avoids crashing when a previously saved field is no longer valid for the selected model, improving reliability while editing content.
Original PR description
The dynamic field editor assumes that an `Applies To` model is always selected and that existing dynamic fields are always valid for the current model. As a result, trying to insert or edit a dynamic field without selecting a model raised an error. Editing an existing dynamic field after changing the selected model could also crash the field selector when the stored field path was no longer valid. Show a notification when users try to insert or edit a dynamic field without selecting a model, and handle invalid field paths when initializing the field selector to avoid UI crashes. Task-6365420 Forward-Port-Of: odoo/odoo#278544
This fix restores the missing delete option on employee records in the Timesheets area. It helps users manage employee records as expected and removes a small workflow blocker.
Original PR description
task-6468432 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#282781
15 changes
Enhancements to existing features
Portal task searches now avoid a slow lookup pattern that forced the system to scan all tasks. Users should see much quicker results when searching task lists, especially in large databases.
Original PR description
The portal task list searched titles with
['|', ('name', 'ilike', search), ('id', 'ilike', search)]. Applying ilike to the integer id casts it to text, which no index can serve, and OR-ing that branch with the title prevents the trigram index on name from being used at all, so every search fell back to a full scan of project_task. The id branch is now added only when the term is numeric, as an equality on the primary key, keeping the title lookup on its trigram index.
Benchmark on 200k tasks, PostgreSQL EXPLAIN ANALYZE, selective term matching 5 rows, median of 3 runs:
before Parallel Seq Scan on project_task ~150 ms
after Bitmap Index Scan (name gin_trgm_ops) ~0.5 ms
opw-5478903
Forward-Port-Of: odoo/odoo#278548Accounting validation errors now include more useful details, such as the affected account code or journal entry reference. This makes FEC imports and related accounting troubleshooting faster because users can identify the problematic record more easily.
Original PR description
Some generic validation errors raised by core account models lack enough context to identify which record caused the issue, making FEC imports harder to troubleshoot. This commit improves the two…
Some generic validation errors raised by core account models lack enough context to identify which record caused the issue, making FEC imports harder to troubleshoot. This commit improves the two error cases identified for this use case: - `account.account._check_account_code` now includes the invalid account code in the error message. - `account.move.write` now includes the move name/reference and displays human-readable field labels instead of technical field names when attempting to modify read-only fields on posted entries. Although motivated by FEC import, these are generic core validations, so the improvements are implemented at the source to benefit all callers rather than only the FEC import flow. Enrichment is scoped to the two cases above, other constraints/errors across these models are intentionally left unchanged for now, since editing core error messages more broadly should be done deliberately and on a case-by-case basis, not as a blanket rewrite task-5346068 Forward-Port-Of: odoo/odoo#282840 Forward-Port-Of: odoo/odoo#281746
Resolved issues and error corrections
When users try to archive an accounting journal that still has draft entries, the error message now points them to the correct place where those entries can be found and resolved. The journal form button was also renamed so it accurately reflects that it opens journal items, reducing confusion and helping users complete the archive process.
Original PR description
> Replaces https://github.com/odoo/odoo/pull/282286, which GitHub closed automatically after a bad force-push on my side: the branch was pushed from a shallow clone and its head lost its parent…
> Replaces https://github.com/odoo/odoo/pull/282286, which GitHub closed automatically after a bad force-push on my side: the branch was pushed from a shallow clone and its head lost its parent commit, leaving no common ancestor with 18.0. A PR in that state cannot be reopened, so this one continues from a clean branch with the exact same change. The review discussion is in that PR, and the rename asked for there is included here. ### Steps to reproduce 1. Go to `Accounting > Customers > Invoices` and create an invoice on a given journal, leaving it in draft. For the clearest case, leave it with no invoice line. 2. Go to `Accounting > Configuration > Journals`, open that journal and archive it. 3. `_check_auto_post_draft_entries` raises: *"You can not archive a journal containing draft journal entries. To proceed: 1/ click on the top-right button 'Journal Entries' from this journal form 2/ then filter on 'Draft' entries 3/ select them all and post or delete them through the action menu"*. 4. Follow those steps: click the `Journal Entries` smart button on the journal form. ### Current behaviour The list comes up empty, so the user concludes the error message is wrong, while the draft entries do exist. The instructions cannot be followed: - The smart button opens `action_account_moves_all_a`, which is named **"Journal Items"** and targets **`account.move.line`**, not `account.move`. The label of the button and the name of the action it opens do not match. - That action defaults to `search_default_posted: 1`, so no draft record is listed. - Draft entries with **no line at all** — commonly created through the incoming mail alias of a journal — have no `account.move.line`, so they stay invisible in that view even after switching the filter. - The action menu of a move line list offers no way to post or delete the entries, and the action sets `create: 0`. - The filter is labelled **"Unposted"**, not "Draft". The offending entries are only reachable through `Accounting > Accounting > Journal Entries`, filtering by journal and by "Unposted". ### Expected behaviour The error should point to a view where the records blocking the archiving are actually listed and actionable. ### This PR Two changes, the validation itself is unchanged: - The error message now points to `Accounting > Accounting > Journal Entries` and uses the real filter name, "Unposted". - The smart button of the journal form is renamed to **"Journal Items"**, so its label matches the action it opens and no longer suggests it lists journal entries. This was asked for in the review of the previous PR. Targeted at 18.0 because that is where the misleading message is being hit in practice; it is identical on 19.0 and master. If a translatable string change does not qualify for the stable series, tell me and I will retarget to master. Forward-Port-Of: odoo/odoo#282956
Italian electronic invoicing now handles simplified invoices more reliably, including the correct virtual stamp duty information. It also prevents simplified invoices from being used for public administration or non-domestic customers, reducing compliance errors.
Original PR description
- Added the BolloVirtuale in the Simplified invoice template - Now it's possible to force the Simplified format on exported invoice when the `l10n_it_document_type` is set to a simplified one - Factored the Italian partner recognition (_l10n_it_edi_is_italian) - Added a check on the invoice, no simplified format for non-domestic / PA partners Task [link](https://www.odoo.com/odoo/project.task/6226436) task-6226436 Forward-Port-Of: odoo/odoo#283154 Forward-Port-Of: odoo/odoo#274493
This update corrects the title styling shown in website theme preset previews. It ensures business users see an accurate preview when choosing or configuring a website theme, avoiding misleading title sizes.
Original PR description
When the conflict of the forward port [1] was resolved, an error was introduced when the class `fs-4` was replaced by `fs-h4`. This commit fixes the class. [1] https://github.com/odoo/odoo/pull/279324 Forward-Port-Of: odoo/odoo#281995
Fixed an issue where creating a new analytic distribution model while updating multiple journal items would close the dialog before users could complete it. This ensures accounting users can finish and save new distribution models during mass edits without interruption.
Original PR description
When mass-editing the Analytic Distribution field on several records at once, and creating a new distribution at once, will close the creation dialog before the user could fill it in. Steps to reproduce: - Enable Analytic Accounting - Open Accounting > Journal Items - Enable the Analytic Distribution column - Select 2 journal items and click on the Analytic Distribution - Click on 'Update', fill a distribution, then click "New Model" - Confirm the multi-edit update Issue: The create Analytic Distribution model dialog closes on its own instead of staying open, so the model can never be saved. Analysis: After https://github.com/odoo/odoo/commit/12a61fa5ab7c56a42020c50c683df8ed52f1fb01, in multi-edit, save() ends reloading the list, unmounting the AnalyticDistribution widget, that closes the model dialog it just opened. opw-6405219 Forward-Port-Of: odoo/odoo#281141
French customers with a valid SIREN or SIRET but no VAT number are now correctly treated as business customers for e-Invoicing. This prevents eligible invoices from losing the French e-Invoicing sending option simply because the VAT number is missing.
Original PR description
**Steps to reproduce:** - Install module `l10n_fr_pdp` and configure French e-Invoicing. - Create a customer has a valid SIREN/SIRET (company_registry) but no VAT number. - Create an invoice for the customer and confirm the invoice. - Check the available sending methods. **Observed Behavior:** The French E-Invoicing option is disabled because the customer is identified as a B2C partner when no VAT number is set. **Cause**: The B2C detection relies on the partner's VAT number instead of its SIREN/SIRET. As a result, French companies without a VAT number but with a valid SIREN are classified as B2C. **Fix**: Determine whether a partner is B2C based on the presence of a valid SIREN/SIRET (derived from `company_registry`) instead of the VAT number. This correctly identifies French business partners that are eligible for French e-Invoicing even when they do not have a VAT number configured. opw-6357756 Forward-Port-Of: odoo/odoo#278060
Swiss payroll now counts full-day flexible absences using the dates employees requested, avoiding timezone-related extra days. This prevents one-day accident leave from being treated as two days, helping keep regular wage and accident salary calculations accurate.
Original PR description
Issue: Swiss payslips count one extra absence day for employees without a working schedule. A one day accident leave can therefore be prorated as two days, reducing the regular wage and overstating…
Issue: Swiss payslips count one extra absence day for employees without a working schedule. A one day accident leave can therefore be prorated as two days, reducing the regular wage and overstating the accident salary. Steps to reproduce: * Install Swiss Payroll. * Configure a monthly employee without a working schedule. * Assign one day of accident time off. * Generate the payslip for that month. Cause: The Swiss wage computation derives absence boundaries from the date portion of the leave's UTC datetimes: https://github.com/odoo/enterprise/blob/16c29e1bab34b5bcb2b001477d928ec5eb294a97/l10n_ch_hr_payroll/models/hr_payslip.py#L313-L330 A fully flexible employee's full day leave starts at local midnight. In timezones ahead of UTC, that start is stored on the previous UTC date, so the inclusive calendar day computation adds an extra day. Solution: We need to use the requested time off dates for both payslip range filtering and absence proration. These fields preserve the calendar days selected by the user independently of timezone conversion, while leaving the UTC datetimes and half-day handling unchanged. opw-6435086 Forward-Port-Of: odoo/enterprise#127849 Forward-Port-Of: odoo/enterprise#127513
The product catalog opened from field service tasks now gives more space to the unit of measure column. This makes product information easier to read and aligns the Enterprise interface with the related Community update.
Original PR description
Steps to produce: --- - Install `Field service` module. - Create a task and open it. - From the task open the catalog from smart button. Update the Product Catalog UI to match the Community PR changes. community PR: https://github.com/odoo/odoo/pull/267118 opw-6253382 --- Forward-Port-Of: odoo/enterprise#128146 Forward-Port-Of: odoo/enterprise#121139
Fixed an issue that could stop database neutralization when multiple active TikTok shops existed. The process now removes real shop references while keeping each record unique, allowing neutralization to complete reliably.
Original PR description
Steps to produce: --- - Install sale_tiktok module. - Create two active tiktok.shop records. - Run the database neutralization command. Issue: --- - Neutralization fails with a PostgreSQL error:…
Steps to produce: --- - Install sale_tiktok module. - Create two active tiktok.shop records. - Run the database neutralization command. Issue: --- - Neutralization fails with a PostgreSQL error: ```py duplicate key value violates unique constraint tiktok_shop_unique_active_shop` DETAIL: Key (tiktok_shop_ref)=(1) already exists. ``` Root cause: --- - At [1], we are setting `tiktok_shop_ref = 1` for all `tiktok_shop` records. Because `tiktok_shop` enforces a partial unique constraint on `tiktok_shop_ref` for active shops [2], setting the same reference value `1` on multiple active shops violates this constraint. Solution: --- - Update sql to assign a row-unique string to each shop. This strips the real shop reference while maintaining uniqueness across active shop records so neutralization completes cleanly. [1]https://github.com/odoo/enterprise/blob/85754b0354b76da8b4d87a3a81dd19679ed35d15/sale_tiktok/data/neutralize.sql#L1-L8 [2]https://github.com/odoo/enterprise/blob/85754b0354b76da8b4d87a3a81dd19679ed35d15/sale_tiktok/models/tiktok_shop.py#L136-L139 opw-6451715 --- Forward-Port-Of: odoo/enterprise#127532
The project forecast button has been moved back to its earlier location after a recent layout change. This restores the familiar interface for users and reduces confusion when accessing project forecasting.
Original PR description
Reverting the position of the project forecast button to the previous one, which was changed in the recent changes. Apply changes up to saas-19.4
Validation messages on Indian employee contracts now reflect the employee's actual pay schedule instead of always referring to a monthly wage. This reduces confusion when allowances exceed wages for contracts with different pay frequencies.
Original PR description
**Steps to reproduce:** - Create an indian employee. - Put total allowance `(basic salary + HRA + standard ALW + Perf bonus + travel ALW) > wage` - We will get validation error in employee stating that allowance sum can't be greater than wage. **Before:** - We were always showing monthly wage in the validation error, which was confusing to the end user. **After:** - We will use field `version.shedule_pay` to show dynamic validation error message. Task: [6449791](https://www.odoo.com/odoo/project/1251/tasks/6449791) Forward-Port-Of: odoo/enterprise#127477
SEPA direct debit payment files now include a required scheme name field for the initiating party. This helps ensure files are accepted by banks that require it, such as Nordea in Sweden, reducing payment processing failures.
Original PR description
We are missing a SchmeNm node in the InitgPty node. This is mandatory for Nordea in Sweden at least. Such as: ```xml <SchmeNm> <Cd>CUST</Cd> </SchmeNm> ``` task-6385960 Forward-Port-Of: odoo/enterprise#124693
This fix prevents Mexican payroll processing from failing when a company has no VAT number entered. Payslips can now show the relevant warnings instead of crashing, helping payroll teams continue their work without interruption.
Original PR description
`res.company.vat` is not required and can be `False`. Guard the `len()` call so `_issue_mx_warnings` doesn't crash on payslips for companies without a VAT set.
```py
File "/home/odoo/src/enterprise/saas-19.3/hr_payroll/models/hr_payslip.py", line 1936, in _compute_issues
issues = generate_issue(slip, context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/enterprise/saas-19.3/l10n_mx_hr_payroll_account_edi/models/hr_payslip.py", line 235, in _issue_mx_warnings
if not slip.company_id.l10n_mx_curp and slip._l10n_mx_is_curp_needed():
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/enterprise/saas-19.3/l10n_mx_hr_payroll_account_edi/models/hr_payslip.py", line 325, in _l10n_mx_is_curp_needed
or len(self.company_id.vat) == 13
^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: object of type 'bool' has no len()
```
Forward-Port-Of: odoo/enterprise#128216Users now get a clear notification if they try to add or edit a dynamic field before choosing where it applies. The editor also handles outdated field selections more safely, reducing interruptions when templates or models change.
Original PR description
The dynamic field editor assumes that an `Applies To` model is always selected and that existing dynamic fields are always valid for the current model. As a result, trying to insert or edit a dynamic field without selecting a model raised an error. Editing an existing dynamic field after changing the selected model could also crash the field selector when the stored field path was no longer valid. Show a notification when users try to insert or edit a dynamic field without selecting a model, and handle invalid field paths when initializing the field selector to avoid UI crashes. Task-6365420 Forward-Port-Of: odoo/odoo#278544
6 changes
Resolved issues and error corrections
DIN 5008 PDF reports now consistently show dates in the expected day.month.year format for Germany, Austria, and Switzerland, regardless of the user's language settings. This prevents customer-facing documents such as invoices, quotations, purchase orders, follow-ups, and field service reports from displaying confusing or incorrect local date formats.
Original PR description
* = din5008_account_followup, din5008_industry_fsm **Steps to reproduce:** * Install the **Germany - Accounting** (`l10n_de`) module (which pulls in `l10n_din5008`) * Set the document layout to **DIN…
* = din5008_account_followup, din5008_industry_fsm
**Steps to reproduce:**
* Install the **Germany - Accounting** (`l10n_de`) module (which pulls in `l10n_din5008`)
* Set the document layout to **DIN 5008** and generate any PDF report (invoice, quotation, purchase order, etc.).
**Observed behavior (date format):**
* All dates in the information block (Invoice Date, Due Date, Delivery Date, Order Date, etc.) are rendered in `yyyy-mm-dd` format instead of the expected `dd.MM.yyyy` format used in DE, AT, and CH.
**Cause (date format):**
* All `t-options="{'widget': 'date'}"` directives across the DIN 5008 template family rely on the active user's language locale for date formatting. If the user language is not `de_DE`, dates render in the locale's default format (e.g. `yyyy-mm-dd` for `en_US`).
**Fix (date format):**
* Add `'format': 'dd.MM.yyyy'` explicitly to all `t-options` date widgets across all DIN 5008 report templates (`l10n_din5008`, `l10n_din5008_sale`, `l10n_din5008_purchase`, `l10n_din5008_sale_subscription`, `l10n_din5008_repair`, `l10n_din5008_account_followup`, `l10n_din5008_industry_fsm`).
* This is correct for all three countries using DIN 5008 (DE, AT, CH), which all follow the `dd.MM.yyyy` convention.
opw-6392649
Forward-Port-Of: odoo/enterprise#128209
Forward-Port-Of: odoo/enterprise#126006This fixes a case where Peruvian electronic invoices could get stuck after SUNAT accepted them but had not yet produced the confirmation document. Odoo now keeps retrying automatically until the confirmation is available, reducing manual follow-up and helping invoices complete reliably.
Original PR description
Steps to reproduce: - Post a Peruvian invoice so it is sent to SUNAT (directly or through Estela/Digiflow). - SUNAT's sendBill call hangs and Odoo's request times out (ReadTimeout / ConnectionError),…
Steps to reproduce: - Post a Peruvian invoice so it is sent to SUNAT (directly or through Estela/Digiflow). - SUNAT's sendBill call hangs and Odoo's request times out (ReadTimeout / ConnectionError), even though SUNAT actually finishes registering the document on its side a moment later. - Odoo retries sending the same invoice (either automatically through the EDI cron, or manually). SUNAT now replies with a "document already exists" SOAP fault (code 1033/4000), since it processed the previous attempt. - Odoo tries to recover from this by fetching the CDR through getStatusCdr, but SUNAT has not finished generating it yet, so the lookup also fails. Cause of the issue: _l10n_pe_edi_post_invoice_web_service() already has recovery logic for error codes 1033/4000: it calls _l10n_pe_edi_retrieve_cdr() to fetch the CDR and treat the invoice as sent. But when that lookup itself fails (CDR not generated yet), the resulting error keeps the 'blocking_level' set to 'error' from the original SOAP fault. Documents with blocking_level 'error' are excluded from the automatic EDI cron retries (see account.edi.document._cron_process_documents_web_services), so the invoice gets stuck needing a manual retry, which can lose the same race against SUNAT again and again. Solution: When the CDR can't be retrieved yet after a 1033/4000 duplicate error, mark the result as 'blocking_level': 'warning' instead of leaving it at 'error'. This keeps the invoice eligible for the automatic EDI cron retries, so Odoo keeps polling SUNAT until the CDR becomes available, instead of requiring manual intervention every time this race is lost. opw-6393231 Forward-Port-Of: odoo/enterprise#125053
ISO 20022 payment files now include the beneficiary's state or province and second address line when available. This helps prevent banks, especially in North America, from rejecting vendor wire transfers because required address details are missing.
Original PR description
The PstlAdr block written into pain.001 files never contains the partner's state/province nor the second street line, even when they are set on the record: _get_all_addr() now returns them, but…
The PstlAdr block written into pain.001 files never contains the partner's state/province nor the second street line, even when they are set on the record: _get_all_addr() now returns them, but _get_PstlAdr() also needs to write them out. Some North American banks reject wire transfers whose beneficiary address lacks the state/province, so those payments fail regardless of how complete the vendor record is. Emit CtrySubDvsn when the address has a state, before Ctry as required by the element order of the PostalAddress schema, and append street2 to the street address line. Steps to reproduce: - Install Accounting and enable a generic ISO 20022 payment method on a bank journal - Create a vendor located in the US or Canada with a complete address, including the state and a second street line - Register a vendor payment, add it to a batch and generate the pain.001 file - The creditor PstlAdr has no state/province, and its street line only carries the first street field: the street2 part is dropped Requires odoo/odoo#282518, which makes _get_all_addr() return the state and street2. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/enterprise#128021 Forward-Port-Of: odoo/enterprise#127958
Database neutralization for TikTok Shop data no longer fails when multiple active shops exist. The cleanup now removes real shop references while keeping each record unique, helping copied or test databases be prepared without interruption.
Original PR description
Steps to produce: --- - Install sale_tiktok module. - Create two active tiktok.shop records. - Run the database neutralization command. Issue: --- - Neutralization fails with a PostgreSQL error:…
Steps to produce: --- - Install sale_tiktok module. - Create two active tiktok.shop records. - Run the database neutralization command. Issue: --- - Neutralization fails with a PostgreSQL error: ```py duplicate key value violates unique constraint tiktok_shop_unique_active_shop` DETAIL: Key (tiktok_shop_ref)=(1) already exists. ``` Root cause: --- - At [1], we are setting `tiktok_shop_ref = 1` for all `tiktok_shop` records. Because `tiktok_shop` enforces a partial unique constraint on `tiktok_shop_ref` for active shops [2], setting the same reference value `1` on multiple active shops violates this constraint. Solution: --- - Update sql to assign a row-unique string to each shop. This strips the real shop reference while maintaining uniqueness across active shop records so neutralization completes cleanly. [1]https://github.com/odoo/enterprise/blob/85754b0354b76da8b4d87a3a81dd19679ed35d15/sale_tiktok/data/neutralize.sql#L1-L8 [2]https://github.com/odoo/enterprise/blob/85754b0354b76da8b4d87a3a81dd19679ed35d15/sale_tiktok/models/tiktok_shop.py#L136-L139 opw-6451715 --- Forward-Port-Of: odoo/enterprise#127532
SEPA direct debit export files now include the required scheme name information for the initiating party. This helps ensure files are accepted by banks such as Nordea in Sweden, reducing payment processing failures.
Original PR description
We are missing a SchmeNm node in the InitgPty node. This is mandatory for Nordea in Sweden at least. Such as: ```xml <SchmeNm> <Cd>CUST</Cd> </SchmeNm> ``` task-6385960 Forward-Port-Of: odoo/enterprise#124693
Fixed an issue in the Timesheet Assistant where selecting a project-only suggestion could unexpectedly reuse a recent task. Timesheets now keep the task field empty when the suggestion has no task, improving accuracy and reducing manual corrections.
Original PR description
Steps to reproduce: - - Open the Timesheet Assistant. - Select a suggestion matched to a project but without a task. - Select a second suggestion from the same project. Issue: - A recently used task is automatically assigned to the timesheet even though the selected suggestion does not contain a task. Cause: - When updating the existing timesheet form, only `project_id` was passed. This triggered the project onchange with the `timesheet_timer_search` context, which automatically selected the most recently used task. Solution: - Always pass `task_id` when updating the form, even when it is empty. This prevents the project from being updated alone and avoids the automatic task assignment. task-6462495
7 changes
Resolved issues and error corrections
When users try to archive an accounting journal that still contains draft entries, the warning now directs them to the correct Journal Entries list where those drafts can be found and handled. The journal form button is also renamed to better reflect that it opens journal items, reducing confusion and helping users resolve the issue without dead ends.
Original PR description
> Replaces https://github.com/odoo/odoo/pull/282286, which GitHub closed automatically after a bad force-push on my side: the branch was pushed from a shallow clone and its head lost its parent…
> Replaces https://github.com/odoo/odoo/pull/282286, which GitHub closed automatically after a bad force-push on my side: the branch was pushed from a shallow clone and its head lost its parent commit, leaving no common ancestor with 18.0. A PR in that state cannot be reopened, so this one continues from a clean branch with the exact same change. The review discussion is in that PR, and the rename asked for there is included here. ### Steps to reproduce 1. Go to `Accounting > Customers > Invoices` and create an invoice on a given journal, leaving it in draft. For the clearest case, leave it with no invoice line. 2. Go to `Accounting > Configuration > Journals`, open that journal and archive it. 3. `_check_auto_post_draft_entries` raises: *"You can not archive a journal containing draft journal entries. To proceed: 1/ click on the top-right button 'Journal Entries' from this journal form 2/ then filter on 'Draft' entries 3/ select them all and post or delete them through the action menu"*. 4. Follow those steps: click the `Journal Entries` smart button on the journal form. ### Current behaviour The list comes up empty, so the user concludes the error message is wrong, while the draft entries do exist. The instructions cannot be followed: - The smart button opens `action_account_moves_all_a`, which is named **"Journal Items"** and targets **`account.move.line`**, not `account.move`. The label of the button and the name of the action it opens do not match. - That action defaults to `search_default_posted: 1`, so no draft record is listed. - Draft entries with **no line at all** — commonly created through the incoming mail alias of a journal — have no `account.move.line`, so they stay invisible in that view even after switching the filter. - The action menu of a move line list offers no way to post or delete the entries, and the action sets `create: 0`. - The filter is labelled **"Unposted"**, not "Draft". The offending entries are only reachable through `Accounting > Accounting > Journal Entries`, filtering by journal and by "Unposted". ### Expected behaviour The error should point to a view where the records blocking the archiving are actually listed and actionable. ### This PR Two changes, the validation itself is unchanged: - The error message now points to `Accounting > Accounting > Journal Entries` and uses the real filter name, "Unposted". - The smart button of the journal form is renamed to **"Journal Items"**, so its label matches the action it opens and no longer suggests it lists journal entries. This was asked for in the review of the previous PR. Targeted at 18.0 because that is where the misleading message is being hit in practice; it is identical on 19.0 and master. If a translatable string change does not qualify for the stable series, tell me and I will retarget to master. Forward-Port-Of: odoo/odoo#282956
Electronic invoices sent through Peppol now use the reference from the actual invoice contact when one is set, rather than incorrectly using the parent company reference. This helps ensure customers receive invoice XML with the correct buyer identifier and reduces Peppol processing or reconciliation issues.
Original PR description
**Steps to reproduce:**
* Set up a French company and configure Peppol E-invoicing.
* Install `account_edi_ubl_cii` module.
* Create a company partner (customer) and set a **Reference** value on the company contact under
**Customer** -> **Settings** -> **Sales and Purchase**.
* Create a child contact under that company and set a different Reference value.
* Create an invoice using the child contact as the invoice partner and confirm the invoice.
* Send it via Peppol.
**Observed Behaviour:**
* The BuyerReference in the generated XML contains the reference of the parent
(commercial partner) Instead of the child contact used on the invoice.
**Cause:**
* The buyer reference was taken from the commercial partner instead of the
invoice partner.
**Fix:**
* Update the condition to use the invoice partner's reference when available;
Otherwise, fall back on the commercial partner's reference.
opw - 6330649
Forward-Port-Of: odoo/odoo#273700French customers with a valid SIREN or SIRET number are now correctly recognized as businesses even if no VAT number is recorded. This keeps the French e-Invoicing option available for eligible invoices and avoids unnecessary manual workarounds.
Original PR description
**Steps to reproduce:** - Install module `l10n_fr_pdp` and configure French e-Invoicing. - Create a customer has a valid SIREN/SIRET (company_registry) but no VAT number. - Create an invoice for the customer and confirm the invoice. - Check the available sending methods. **Observed Behavior:** The French E-Invoicing option is disabled because the customer is identified as a B2C partner when no VAT number is set. **Cause**: The B2C detection relies on the partner's VAT number instead of its SIREN/SIRET. As a result, French companies without a VAT number but with a valid SIREN are classified as B2C. **Fix**: Determine whether a partner is B2C based on the presence of a valid SIREN/SIRET (derived from `company_registry`) instead of the VAT number. This correctly identifies French business partners that are eligible for French e-Invoicing even when they do not have a VAT number configured. opw-6357756 Forward-Port-Of: odoo/odoo#278060
This fixes an issue where creating a new analytic distribution model during a bulk edit would close the dialog before users could finish entering details. Users can now complete and save the new model without losing their work, improving reliability for accounting workflows.
Original PR description
When mass-editing the Analytic Distribution field on several records at once, and creating a new distribution at once, will close the creation dialog before the user could fill it in. Steps to reproduce: - Enable Analytic Accounting - Open Accounting > Journal Items - Enable the Analytic Distribution column - Select 2 journal items and click on the Analytic Distribution - Click on 'Update', fill a distribution, then click "New Model" - Confirm the multi-edit update Issue: The create Analytic Distribution model dialog closes on its own instead of staying open, so the model can never be saved. Analysis: After https://github.com/odoo/odoo/commit/12a61fa5ab7c56a42020c50c683df8ed52f1fb01, in multi-edit, save() ends reloading the list, unmounting the AnalyticDistribution widget, that closes the model dialog it just opened. opw-6405219 Forward-Port-Of: odoo/odoo#281141
This fix adds a required customer scheme identifier to SEPA direct debit payment files. It helps ensure files are accepted by banks that require this information, such as Nordea in Sweden.
Original PR description
We are missing a SchmeNm node in the InitgPty node. This is mandatory for Nordea in Sweden at least. Such as: ```xml <SchmeNm> <Cd>CUST</Cd> </SchmeNm> ``` task-6385960 Forward-Port-Of: odoo/enterprise#124693
Database neutralization for TikTok sales no longer fails when more than one active TikTok shop exists. The cleanup now removes real shop references while keeping each record unique, helping test or anonymized databases be prepared reliably.
Original PR description
Steps to produce: --- - Install sale_tiktok module. - Create two active tiktok.shop records. - Run the database neutralization command. Issue: --- - Neutralization fails with a PostgreSQL error:…
Steps to produce: --- - Install sale_tiktok module. - Create two active tiktok.shop records. - Run the database neutralization command. Issue: --- - Neutralization fails with a PostgreSQL error: ```py duplicate key value violates unique constraint tiktok_shop_unique_active_shop` DETAIL: Key (tiktok_shop_ref)=(1) already exists. ``` Root cause: --- - At [1], we are setting `tiktok_shop_ref = 1` for all `tiktok_shop` records. Because `tiktok_shop` enforces a partial unique constraint on `tiktok_shop_ref` for active shops [2], setting the same reference value `1` on multiple active shops violates this constraint. Solution: --- - Update sql to assign a row-unique string to each shop. This strips the real shop reference while maintaining uniqueness across active shop records so neutralization completes cleanly. [1]https://github.com/odoo/enterprise/blob/85754b0354b76da8b4d87a3a81dd19679ed35d15/sale_tiktok/data/neutralize.sql#L1-L8 [2]https://github.com/odoo/enterprise/blob/85754b0354b76da8b4d87a3a81dd19679ed35d15/sale_tiktok/models/tiktok_shop.py#L136-L139 opw-6451715 --- Forward-Port-Of: odoo/enterprise#127532
WhatsApp template messages with many mixed variable types now place each value in the correct placeholder. This prevents customers from receiving messages with incorrect or mismatched information.
Original PR description
**Issue**:
Sending a WhatsApp template with 10 or more variables can assign values to the wrong placeholders when the body contains mixed variable types, such as free text, field, or user name variables.
Templates containing only free-text variables are not affected.
**Reason**:
Meta consumes template parameters positionally, but for mixed variable types, Odoo built the parameter list using the template variable recordset order.
That order can differ from the numeric placeholder order, notably placing {{10}}, {{11}}, {{12}}... before {{1}}
when sending the message, as the payload parameters are not ordered by their numeric placeholder index.
**Fix:**
Sort body variables by their numeric placeholder index before preparing the Meta payload.
Task-6401501
Forward-Port-Of: odoo/enterprise#1256713 changes
Resolved issues and error corrections
Fixed an issue where reconnecting a Shopee shop with a different API account did not update the linked account in Odoo. This ensures shops stay connected to the correct Shopee credentials after re-authorization, reducing setup errors and manual corrections.
Original PR description
Context: when a user re-authenticate a shop, they might use different shopee.account (API key). Currently Odoo will not change the shopee.account when they re-auth with another shopee.account. Enable a shopee.shop switches to another shopee.account when we run `create_or_update_shop` function. Forward-Port-Of: odoo/enterprise#92446
SEPA direct debit payment files now include a required scheme name in the initiating party section. This improves compatibility with banks such as Nordea in Sweden and helps prevent payment file rejection.
Original PR description
We are missing a SchmeNm node in the InitgPty node. This is mandatory for Nordea in Sweden at least. Such as: ```xml <SchmeNm> <Cd>CUST</Cd> </SchmeNm> ``` task-6385960 Forward-Port-Of: odoo/enterprise#124693
This fixes an issue where WhatsApp message templates with 10 or more mixed variables could send values in the wrong positions. Businesses can rely on larger templates to show the correct customer, user, and text details when messages are sent.
Original PR description
**Issue**:
Sending a WhatsApp template with 10 or more variables can assign values to the wrong placeholders when the body contains mixed variable types, such as free text, field, or user name variables.
Templates containing only free-text variables are not affected.
**Reason**:
Meta consumes template parameters positionally, but for mixed variable types, Odoo built the parameter list using the template variable recordset order.
That order can differ from the numeric placeholder order, notably placing {{10}}, {{11}}, {{12}}... before {{1}}
when sending the message, as the payload parameters are not ordered by their numeric placeholder index.
**Fix:**
Sort body variables by their numeric placeholder index before preparing the Meta payload.
Task-6401501
Forward-Port-Of: odoo/enterprise#1256716 changes
Resolved issues and error corrections
Peppol invoice XML now uses the reference from the actual invoice contact when one is set, rather than incorrectly using the parent company reference. This helps ensure e-invoices carry the right buyer identification and reduces rejection or processing issues for customers with multiple contacts.
Original PR description
**Steps to reproduce:**
* Set up a French company and configure Peppol E-invoicing.
* Install `account_edi_ubl_cii` module.
* Create a company partner (customer) and set a **Reference** value on the company contact under
**Customer** -> **Settings** -> **Sales and Purchase**.
* Create a child contact under that company and set a different Reference value.
* Create an invoice using the child contact as the invoice partner and confirm the invoice.
* Send it via Peppol.
**Observed Behaviour:**
* The BuyerReference in the generated XML contains the reference of the parent
(commercial partner) Instead of the child contact used on the invoice.
**Cause:**
* The buyer reference was taken from the commercial partner instead of the
invoice partner.
**Fix:**
* Update the condition to use the invoice partner's reference when available;
Otherwise, fall back on the commercial partner's reference.
opw - 6330649
Forward-Port-Of: odoo/odoo#273700When a journal cannot be archived because it still contains draft accounting entries, the message now directs users to a list where those drafts can actually be found and handled. The journal form button label was also corrected so users are not sent to the wrong type of accounting list.
Original PR description
> Replaces https://github.com/odoo/odoo/pull/282286, which GitHub closed automatically after a bad force-push on my side: the branch was pushed from a shallow clone and its head lost its parent…
> Replaces https://github.com/odoo/odoo/pull/282286, which GitHub closed automatically after a bad force-push on my side: the branch was pushed from a shallow clone and its head lost its parent commit, leaving no common ancestor with 18.0. A PR in that state cannot be reopened, so this one continues from a clean branch with the exact same change. The review discussion is in that PR, and the rename asked for there is included here. ### Steps to reproduce 1. Go to `Accounting > Customers > Invoices` and create an invoice on a given journal, leaving it in draft. For the clearest case, leave it with no invoice line. 2. Go to `Accounting > Configuration > Journals`, open that journal and archive it. 3. `_check_auto_post_draft_entries` raises: *"You can not archive a journal containing draft journal entries. To proceed: 1/ click on the top-right button 'Journal Entries' from this journal form 2/ then filter on 'Draft' entries 3/ select them all and post or delete them through the action menu"*. 4. Follow those steps: click the `Journal Entries` smart button on the journal form. ### Current behaviour The list comes up empty, so the user concludes the error message is wrong, while the draft entries do exist. The instructions cannot be followed: - The smart button opens `action_account_moves_all_a`, which is named **"Journal Items"** and targets **`account.move.line`**, not `account.move`. The label of the button and the name of the action it opens do not match. - That action defaults to `search_default_posted: 1`, so no draft record is listed. - Draft entries with **no line at all** — commonly created through the incoming mail alias of a journal — have no `account.move.line`, so they stay invisible in that view even after switching the filter. - The action menu of a move line list offers no way to post or delete the entries, and the action sets `create: 0`. - The filter is labelled **"Unposted"**, not "Draft". The offending entries are only reachable through `Accounting > Accounting > Journal Entries`, filtering by journal and by "Unposted". ### Expected behaviour The error should point to a view where the records blocking the archiving are actually listed and actionable. ### This PR Two changes, the validation itself is unchanged: - The error message now points to `Accounting > Accounting > Journal Entries` and uses the real filter name, "Unposted". - The smart button of the journal form is renamed to **"Journal Items"**, so its label matches the action it opens and no longer suggests it lists journal entries. This was asked for in the review of the previous PR. Targeted at 18.0 because that is where the misleading message is being hit in practice; it is identical on 19.0 and master. If a translatable string change does not qualify for the stable series, tell me and I will retarget to master. Forward-Port-Of: odoo/odoo#282956
When a Shopee shop is re-authorized with a different API account, Odoo now correctly links the shop to that new account. This prevents shops from remaining tied to outdated credentials and helps keep sales channel connections working as expected.
Original PR description
Context: when a user re-authenticate a shop, they might use different shopee.account (API key). Currently Odoo will not change the shopee.account when they re-auth with another shopee.account. Enable a shopee.shop switches to another shopee.account when we run `create_or_update_shop` function. Forward-Port-Of: odoo/enterprise#92446
Users can now open links inside spreadsheet cell comments with a regular click, as expected. This fixes a small usability issue that previously forced users to use Ctrl-click or Cmd-click to access those links.
Original PR description
Current behavior before PR: - Clicking a link in a cell comment did not work. A left click was blocked, while Ctrl+click (or Cmd+click) opened the link in a new tab. - This was caused by `t-on-click.prevent` on the comment thread and popover. It was originally added because the scroller service used the URL hash to scroll to anchors, which was removed in https://github.com/odoo/odoo/commit/711e9c9f24818714129f55283e2df64503d93605 Desired behavior after PR is merged: - `t-on-click.prevent` is removed and links in cell comments can be opened normally with both left click and Ctrl+click (Cmd+click on macOS). Task: [6448651](https://www.odoo.com/odoo/project/2328/tasks/6448651) Forward-Port-Of: odoo/enterprise#127473
WhatsApp messages using templates with many mixed variables now send each value to the correct placeholder. This prevents customer messages from showing incorrect details when templates include 10 or more variables.
Original PR description
**Issue**:
Sending a WhatsApp template with 10 or more variables can assign values to the wrong placeholders when the body contains mixed variable types, such as free text, field, or user name variables.
Templates containing only free-text variables are not affected.
**Reason**:
Meta consumes template parameters positionally, but for mixed variable types, Odoo built the parameter list using the template variable recordset order.
That order can differ from the numeric placeholder order, notably placing {{10}}, {{11}}, {{12}}... before {{1}}
when sending the message, as the payload parameters are not ordered by their numeric placeholder index.
**Fix:**
Sort body variables by their numeric placeholder index before preparing the Meta payload.
Task-6401501
Forward-Port-Of: odoo/enterprise#125671This fix adds a required scheme name field to SEPA direct debit payment files. It helps ensure files are accepted by banks that require this information, such as Nordea in Sweden, reducing payment processing failures.
Original PR description
We are missing a SchmeNm node in the InitgPty node. This is mandatory for Nordea in Sweden at least. Such as: ```xml <SchmeNm> <Cd>CUST</Cd> </SchmeNm> ``` task-6385960 Forward-Port-Of: odoo/enterprise#124693
30 changes
Enhancements to existing features
Point of Sale configurations now load the required products for due-payment settlement and UrbanPiper delivery integration more reliably. This helps ensure the right products are available when POS sessions are created or loaded, reducing setup friction and operational errors.
Original PR description
*=pos_urban_piper Following this commit: ==== - Load pos_settle_due products when creating or loading a POS config. - Load urbanPiper products when at least one configuration has urbanPiper enabled task-6171250 Related PR : https://github.com/odoo/odoo/pull/262669
Saudi payroll now splits sick leave at the time leave is created, instead of waiting until payslip calculation. This makes leave handling more consistent across countries and improves payroll accuracy by using standardized work-day calculations and rate-based unpaid entries.
Original PR description
Purpose: move the logic of handling SA sick leave split from payslip computation to automatic split during leave creation - refactored the sick leave split logic from `l10n_be_hr_payroll` and `l10n_lu_hr_payroll` to a standardized logic in `hr_holidays` with the ability to split leaves using calendar days or worked days - added the logic for SA sick leave split during leave creation - changed hardcoded unpaid work entries to use amount rate - adapted the use of the method `_number_of_workdays` to use standard `_get_work_days_data_batch` task-id: 6379346
Brazilian shipments sent through Envia.com can now include the required NF-e access key, helping carriers receive the fiscal information they need. The system automatically looks for the linked invoice from packages, pickings, or the sale order, and if none is ready it lets the warehouse validation finish while prompting the user to link and validate an invoice before sending to the shipper.
Original PR description
In Brazil, if you are using Envia.com or other delivery providers you need to make sure you are sending the NF-e Access Key on shipment generation to make sure that the freight company has the right data. In the normal flow: Sale Order -> Invoice -> Picking, the related invoice is automatically attached to the picking so the customer doesn't have to do anything. Priority is invoice on individual package, invoice attached to the picking, invoice attached to the sale order as a final fallback. If all three are missing and not sent to the government yet, the picking will validate fully, but not automatically send to Envia.com. It will instead post to the chatter that an invoice needs to be validated and linked properly to the record before hitting Send to Shipper. task-6120965
Updating a company’s return reminder day now recalculates deadlines only for open account returns that are affected. This preserves the same business behavior while reducing unnecessary processing as the number of returns grows.
Original PR description
In this commit: - Remove the 'company_id.account_return_reminder_day' dependency from the '_compute_deadline' compute method. - Avoid triggering the compute method for all related account returns whenever 'account_return_reminder_day' is updated, as this becomes increasingly expensive when the number of records grows. - Override 'res.company.write()' to detect changes to 'account_return_reminder_day'. - Manually trigger '_compute_deadline()' only for non-completed account returns that are actually affected by the change, reducing unnecessary recomputations while preserving the existing behavior. task-[6296822](https://www.odoo.com/odoo/project/967/tasks/6296822)
Odoo now checks whether a payment or batch payment exceeds the maximum amount allowed by the connected financial institution before initiating it. This helps prevent failed payment attempts and gives users earlier feedback when a bank-imposed limit applies.
Original PR description
Before trying to initiate payments through Odoo/Odoofin, we should check that the total amount for the (batch) payment does not exceed the maximum payment amount allowed by the institution (some Powens institutions introduced that limit). task-6310729 Forward-Port-Of: odoo/enterprise#127110 Forward-Port-Of: odoo/enterprise#121513
Cash journal users can now choose an account directly when quickly creating bank statement lines, reducing extra reconciliation steps. The update also strengthens cash statement posting and deletion rules so records stay consistent and compliant when journals are secured.
Original PR description
This commit will add the possibility to add an account on the quick create view of a bank statement line when being on a cash journal that when selected will do a set account on the statement line created with the account selected no task id
Payroll will no longer automatically set the current driver of a company car based on benefits alone. Instead, employees are marked as future drivers when they choose or receive a car, preventing already reserved cars from being offered again and reducing unnecessary administrative tasks.
Original PR description
. Remove the auto-assignment of the Driver based on the payroll benefits. . If an employee signs a contract and selects the car or the car gets added to the employee's benefits, he should become the car's future driver. . Don't offer in the salary configurator cars for which the future driver is filled. . Don't generate a task every time the payroll officer assigns a new driver to the car . Add the corresponding tests task-6425360 Forward-Port-Of: odoo/enterprise#127390 Forward-Port-Of: odoo/enterprise#126016
Bulgarian companies can now have required monthly VAT reporting files generated automatically when validating a VAT return. The change adds the General Ledger SAF-T file plus purchase and sales reports to the return attachments, reducing manual work and helping with local compliance.
Original PR description
Bulgaria made it mandatory for large companies to present a monthly file
to report their VAT to the administration. To streamline that process,
when the VAT return is validated and PDF is added to the attachments,
the monthly General Ledger SAF-T file, the POKUPKI Purchase Report and
PRODAGBI Sale Report are produced and added as well.
Simplify the report file download error wizard's visuals and descriptions to improve readability.
task-6007963
Forward-Port-Of: odoo/enterprise#127736
Forward-Port-Of: odoo/enterprise#118326Budget report loading has been optimized to avoid inefficient record matching that could make reports unusably slow on larger databases. This should significantly reduce wait times for users opening budget reports, especially when many analytic lines and budget lines are involved.
Original PR description
**Description:** While loading the budget report, the bad queries are created by ```def _get_aal_query()``` and ```def _get_pol_query()``` function, makes the budget report unusable. **Root cause:**…
**Description:**
While loading the budget report, the bad queries are created by
```def _get_aal_query()``` and ```def _get_pol_query()``` function, makes
the budget report unusable.
**Root cause:**
Instead of doing a hash join while searching the record,
the OR statement in the Left Join in the condition
```(%(bl)s IS NULL OR %(a)s = %(bl)s)```
creates a nested for loop that compares everything single aal to bl,
this causes a significant performance issue as the number of the
number of check will be the the number aal * bl,
if a database has a 70k aal and 20k bl, both numbers are not large
but it will cause a 70k * 20k search which is more than a billion.
**Fix**:
There are some refactors made in this PR.
_First_, separate out the Q1.
In order to find the aal that has no bl connects to it.
Doing a search to find the aals that have bl and then subtract them from all aals.
_Second_, Instead of doing a nested loop for by using
```(%(bl)s IS NULL OR %(a)s = %(bl)s)```,
originally we will have do something like
```
JOIN budget_line bl
ON (bl.x_plan2_id IS NULL OR aal.x_plan2_id = bl.x_plan2_id)
AND (bl.x_plan3_id IS NULL OR aal.x_plan3_id = bl.x_plan3_id)
AND (bl.x_plan4_id IS NULL OR aal.x_plan4_id = bl.x_plan4_id)
```
Assuming each bl has three plans ```x_plan2_id```, ```x_plan3_id```, ```x_plan4_id```
Grouping the bl base on whether a specific plan is set, (i.e. shapes)
we can skip the ```IS NULL OR``` because we already know which plan
is null and do the hash join directly.
For example, the shapes will be a dictionary with a key of a tuple of booleans
based on whether a plan is set or not and the value is a list of bl_id.
```
{
(True, False, False): [1, 2],
(False, True, True): [3, 4],
(False, False, False): [5],
}
```
we can end up doing something like
```
JOIN budget_line bl
ON bl.id = ANY(ARRAY[3,4])
AND aal.x_plan3_id = bl.x_plan3_id AND aal.x_plan4_id = bl.x_plan4_id
```
which is way more faster.
---
The benchmark is made locally from this client's database which contains
69k aal, 23k bl, 6829 pol and 3 plans for aal and bl.
|Record count |Time before|Time after|
|--------------------------------------------------|-----------------|---------------|
|69k aal, 23k bl, 6829 pol, 3 plans |70.04s |4.6s |
Dalibo:
Before:
Month-over-month grand total by company:
https://explain.dalibo.com/plan/8h3d4e89aaf9f3d4
Overall grand total by company:
https://explain.dalibo.com/plan/445g1f9caf4923e2
Month-over-month grand total by plan:
https://explain.dalibo.com/plan/53a138ca50b2a7c4
Overall grand total by plan:
https://explain.dalibo.com/plan/hdbe169ddc7g5785
After:
Month-over-month grand total by company:
https://explain.dalibo.com/plan/hcc86c801e6872bf
Overall grand total by company:
https://explain.dalibo.com/plan/69b2421a3581f98h
Month-over-month grand total by plan:
https://explain.dalibo.com/plan/a88f398bbbch3148
Overall grand total by plan:
https://explain.dalibo.com/plan/1gg749ae7ab1553c
opw-6345552
Forward-Port-Of: odoo/enterprise#127732
Forward-Port-Of: odoo/enterprise#124161Shopfloor work orders now handle quantity updates consistently with the backend for continuous production, avoiding unintended changes to the quantity being produced. The work order form layout was also reorganized to make continuous production information clearer for users.
Original PR description
In this commit, shopfloor is modified in order to match the behaviour in the backend; On updating WO's quantity, the quantity producing is not updated if its a continuous production. Workorder form fields were also re-ordered as a part of the ongoing continuous production clean. Task: 6346515 Forward-Port-Of: odoo/enterprise#123215
Payroll warning checks are now grouped so the system avoids repeating the same lookup many times. This should make payslip and employee payroll version processing faster when many warnings are active, without changing the warnings users see.
Kitchen staff can now print preparation tickets on demand directly from the kitchen workflow. Tickets can also print automatically when orders reach configured stages, and added barcodes let staff scan tickets to move orders forward faster.
Original PR description
*: pos_restaurant_preparation_display, pos_urban_piper, pos_self_order_preparation_display In this commit: ------------------- - Introduced functionality to print KOTs on demand from the kitchen. - Added support for automatic printing when an order is moved to a configured stage. - Added barcodes to KOTs printed from the kitchen, allowing kitchen staff to scan them and directly move the order to the next stage. task: 6131467 Related PR: https://github.com/odoo/odoo/pull/273944
Sign managers and the person who sent a signature request can now add or change the linked record at any stage. This helps teams correct or complete request details after the request has moved beyond the sent state, while keeping the field read-only for other users.
Original PR description
Before: - The 'Linked To' field on a Signature Request could only be edited while the request was sent state After: - Sign manager and user who sent SR can now set or change the "Linked To" field at any time. - Other users keep seeing the field as read-only. Impact: - Admins and request senders can correct or add the linked record even after the request has moved past the sent state. Taskid: 6321326
This update refreshes the spreadsheet interface to align with the latest underlying spreadsheet library. Users will see more consistent icons, section styling, and drag-and-drop behavior when working with lists, pivots, and filters in spreadsheet side panels.
The Mexico e-invoicing website sale flow was updated to stay aligned with recent community changes. This helps keep online checkout invoicing behavior consistent and reduces the risk of issues for Mexican localization users.
Original PR description
community PR: https://github.com/odoo/odoo/pull/278561
Resolved issues and error corrections
Project Forecast no longer shows the Time Management section in project settings when the Timesheets app is not installed. This prevents users from seeing irrelevant settings and keeps project configuration aligned with installed apps.
Original PR description
**Steps to reproduce:** - Install the project_forecast module. - Go to Projects -> Open the settings of any project (create one if none exist) -> Settings. - You will see the Time Management section. **Issue:** The project_forecast module was forcefully setting the invisible attribute of group_time_managment to 0. This caused the group to remain visible at all times, even when the Timesheets app was not installed. **Fix:** Remove the forced attribute setting from the project_project_view. The visibility is already properly managed by the hr_timesheet module, and project_forecast does not depend on timesheet_grid or hr_timesheet. task-6195716 Forward-Port-Of: odoo/enterprise#128350 Forward-Port-Of: odoo/enterprise#121454
An automated accounting test was adjusted to match a related platform fix in how grouped data handles file-size information. This keeps the test suite aligned with the corrected behavior and helps prevent false failures during future updates.
Original PR description
The fix at https://github.com/odoo/odoo/pull/281911 adds bin_size: tru in the web_read_group. This commit adpats an accounting test as a consequence Forward-Port-Of: odoo/enterprise#128089 Forward-Port-Of: odoo/enterprise#127638
Correcting a paid payslip now creates the related payroll batch under the same company as the payslip, instead of defaulting to the user's currently active company. This prevents payroll correction batches from mixing companies and helps keep multi-company payroll records accurate.
Original PR description
Steps to reproduce: - Have an employee in company B, with a paid payslip - Log in with company A active (company B allowed but not selected) - Open the employee's paid payslip and click "Correct" The refund and correction payslips are computed in company B (their company follows the employee), but the pay run created for them by the wizard has no explicit company and falls back to the active company A. Set the pay run's company from the payslips it contains, and group the payslips by company as well as by structure so that a batch never mixes companies. task-6428755 Forward-Port-Of: odoo/enterprise#126064
International shipments using Sendcloud DPD can now include the required customer tax details in customs information. This prevents validation errors when shipping to customers with VAT numbers and adds safer fallback values for required customs fields.
Original PR description
### This is a revision of #119399 which had to be reverted. Original issue ----- Deliveries cannot be validated using DPD with Sendcloud, users get an error. Steps to reproduce ----- - Set up…
### This is a revision of #119399 which had to be reverted. Original issue ----- Deliveries cannot be validated using DPD with Sendcloud, users get an error. Steps to reproduce ----- - Set up Sendcloud DPD - Create a SO - Interntional customer - Some VAT number - Some product - Add sendcloud delivery - Confirm SO - Validate the linked picking > Error: “The receiver VAT number is missing; please provide it to continue” Issue's cause ----- Tax numbers should be included in the `customs_information` field of the request as per the API https://sendcloud.dev/api/v2/parcels/create-a-parcel-or-parcels#body-one-of-0-parcel-customs-information-tax-numbers For the `vat_label` field, we have to force the language to English in the context because the field is translated by default, but sendcloud only accepts the english names (eg French "TVA" is not accepted, expected value is "VAT"). https://github.com/odoo/odoo/blob/d1d1610332a1596d026fb0a42ec236d1a79c71cc/odoo/addons/base/models/res_country.py#L75 Revert cause ----- The vat_label field is marked for translation (translate=True) https://github.com/odoo/odoo/blob/d1d1610332a1596d026fb0a42ec236d1a79c71cc/odoo/addons/base/models/res_country.py#L75 So if the user has the DB in french for example, we are sending "TVA" instead of "VAT" in the name field. Other issues ----- - We need to provide an actual fallback for `customs_invoice_nr`. As it stands, if we create a new delivery it cannot be validated because Sendcloud doesn't accept for the field to be empty. - Same for `name`, we need to provide an actual fallback. ----- Ticket: opw-6250860 Forward-Port-Of: odoo/enterprise#127425 Forward-Port-Of: odoo/enterprise#124245
The payment registration flow now uses the bank account selected in the payment wizard when no bank account is set on the invoice entry. This prevents the “Pay Now” button from disappearing in valid payment scenarios, making online payment initiation more reliable for users.
Original PR description
Before this commit, it could happens that when we don't put a partner_bank_id on the move it self. The "pay now" button was never displayed. It was because partner_bank_ids was only checking the value from the move and not the wizard. Now if there is no partner_bank_id in the wizard.batches then we look at the value in the wizard and we use it. task-6374002 Forward-Port-Of: odoo/enterprise#123759
Completed point-of-sale kitchen orders are now removed from the customer-facing order status display instead of showing again as "Almost There." This keeps the status screen accurate for customers and staff once an order has finished preparation.
Original PR description
Steps to reproduce ------------------ - Open a PoS session, the Kitchen Display, and the Order Status Display. - Create an order and send it to the Kitchen. - Process the order through all the stages until it reaches the final (completed) stage. Issue ----- - Once the order reaches the completed stage, it reappears in the "Almost There" section of the Order Status Display instead of being removed. Cause ----- - The applied domain fetched all kitchen orders, including completed ones. The display logic only distinguishes whether an order is in the second last preparation stage than show it as "Ready", all other orders are shown as "Almost There". As a result, completed orders fall back into the "Almost There" section.. Fix --- - Updated the domain to fetch only active kitchen orders and exclude completed ones from the Order Status Display. Task: 6394865 Forward-Port-Of: odoo/enterprise#124947
The Point of Sale Urban Piper ticket screen now shows the order information button on mobile as well as desktop. This ensures staff using phones or smaller devices can access the same order details without switching views or devices.
Original PR description
Before this commit: ------------ - The order info button was not visible on the ticket screen in the mobile UI. After this commit: ------------ - Display the order info button in both the mobile and desktop views of the ticket screen. Related: - Community: https://github.com/odoo/odoo/pull/276568 Task-6388045 Forward-Port-Of: odoo/enterprise#127785 Forward-Port-Of: odoo/enterprise#124485
The Timesheet Assistant now gives a clearer suggestion when it detects time spent in the Discuss inbox. Instead of an awkward discussion-related label, users will see “Checking Inbox,” making timesheet suggestions easier to understand and use.
Original PR description
## Previous Behavior When the Timesheet Assistant detected a user spending time in their Discuss inbox, it generated a suggestion labeled "Discussing in/with Inbox". The name of this suggestion was judged to not make much sense. ## New Expected Behavior When the Timesheet Assistant detects a user spending time in their Discuss inbox, it will now generate a suggestion labeled "Checking Inbox" due to a new assistant rule. task-[6420655](https://www.odoo.com/odoo/project/4105/tasks/6420655) Forward-Port-Of: odoo/enterprise#127901 Forward-Port-Of: odoo/enterprise#126328
Live Chat sessions using an AI agent now send the correct agent identifier when starting a chat. This prevents errors that blocked guest users or website visitors from opening the chat window, improving access to automated support.
Original PR description
When configuring an AI agent in Live Chat and opening it as a guest user or from website > chat bubble, user is unable to open the chat window from the chat bubble and get a traceback instead. Currently, In `LivechatChannelRule` the `ai_agent_id` is declared as a relational field , so its value is a model record instead of an id. To fix this pass `ai_agent_id.id` when building the livechat session parameters to avoid serializing the model and causing a circular JSON error. task-6479187
A small typo was corrected in the Belgian payroll meal voucher report logic. This helps keep payroll reporting code clear and reduces the risk of confusion during future maintenance, with no expected change to day-to-day user workflows.
The timesheet menu has been adjusted to display more cleanly on mobile devices. This makes it easier for users to review or enter timesheets from smaller screens without a clunky interface.
Original PR description
In this commit, we improve the display of the timesheet systray in mobile view as it was clunky. task-6332208 Forward-Port-Of: odoo/enterprise#127829 Forward-Port-Of: odoo/enterprise#122491
Updates field service planning so completion actions appear in the right place depending on the view, reducing confusion for users. The onboarding tour now matches the updated scheduling workflow, and signing in from the Gantt popover refreshes the view so users can continue their work smoothly.
Original PR description
## [FIX] planning_field_service: display complete button in popover footer Before this commit, the complete button is displayed in the card even in the gantt popover instead of displaying it in the…
## [FIX] planning_field_service: display complete button in popover footer Before this commit, the complete button is displayed in the card even in the gantt popover instead of displaying it in the footer of the gantt popover. This commit makes sure the complete button in the card is only displayed in the kanban view and that button is displayed in the footer of the gantt view. ## [FIX] planning_field_service: adapt onboarding tour based on recent changes Before this commit, the quick create on resource_ids field in planning.slot has been replaced by a form view inside a modal. The Sign in button in gantt/calendar popover no longer automatically redirects the user to the form view of the intervention and so the user cannot directly complete the shift. This commit adapts the onboarding tour based on the recent changes. It also forces a reload in the gantt view when the user signs in a intervention via the Sign in button in the gantt popover. runbot-error-941063 task-[6353582](https://www.odoo.com/odoo/project/4105/tasks/6353582) Forward-Port-Of: odoo/enterprise#122495
This fixes cases where the Documents app on mobile could show an enabled Info & Tags button while the details panel was hidden or inaccessible. Users should have a more reliable experience when selecting files, switching views, reloading, or returning from previews.
Original PR description
**Steps to reproduce:** - Go to Documents app in mobile - Go to the kanban view - Add some files and select one - Click on `Info & Tags` button in the control panel - Reload the page - Chatter is not…
**Steps to reproduce:** - Go to Documents app in mobile - Go to the kanban view - Add some files and select one - Click on `Info & Tags` button in the control panel - Reload the page - Chatter is not displayed but the button is still enabled - Switching to the list view properly shows it **Issue:** Original fix (see [1]) was not enough for every case. Additional issues: - Chatter hidden on init even when its panel has `visible = true` - State desynchronized with the view when switching menu type (kanban/list) or by previewing a document and coming back - When using the button with an open preview, chatter shows up in the background but is not accessible (and going back discards it) - Removing selection with an open chatter disable the related action **Fix:** - Disable the chatter on mobile init by default to avoid having to manually move it back - Reset chatter on selection removal to avoid getting stuck in the menu - Reset chatter on view switch to avoid being in the wrong state afterwards (and revert the previous css changes) Not a great fix (quite mobile-specific) and there might still be some edge cases. [1] original fix: https://github.com/odoo/enterprise/commit/cf3c2fce8a6b7b2d7547d44a0e4423f887986d52 opw-6061993 Forward-Port-Of: odoo/enterprise#127874 Forward-Port-Of: odoo/enterprise#121521
Fixes an issue where task or ticket timers could stutter, switch between different elapsed times, or show negative values after repeated stop/start and page reload actions. This improves reliability of time tracking for users working with timesheet timers, such as Field Service tasks.
Original PR description
A running task/ticket timer can sometimes stutter, show negative values, and track the wrong elapsed time. ### Steps to reproduce On a record with a timesheet timer (for example, a Field Service…
A running task/ticket timer can sometimes stutter, show negative values, and track the wrong elapsed time. ### Steps to reproduce On a record with a timesheet timer (for example, a Field Service task): 1. Start the timer and let it run for about 15-20 seconds. 2. Stop it and confirm the dialog. 3. Start it again. This creates a new `timer_start`. 4. Reload the page. The timer starts jumping every second between two different values. As it keeps running, it can even show negative values such as `00:00:-57`. If the problem does not appear right away, repeat steps 2-4 a few times. It usually shows up after a few stop/start/reload cycles. ### Cause The timer shown in the button bar is the `timer_start_field` widget. It starts a `setInterval` that updates a shared `TimerReactive` object once per second. While a form is loading, Odoo renders it several times in a row (for example: a first render, another when the chatter is loaded, and another when the record data comes back from the server). Rendering a form builds all of its fields to produce the display, so each of these renders creates its own `timer_start_field`. Odoo keeps and mounts only the render that ends up on screen; the earlier ones are thrown away before being mounted. The interval is started while the field renders, from the record observer set up in `setup`, before the field is mounted. So the fields that are later thrown away also start an interval. Those intervals keep running for the rest of the session. Each one updates the same shared `TimerReactive` object using the `timer_start` it was created with. As long as every instance has the same `timer_start`, they all write the same value and the problem stays hidden. After the timer is stopped and started again, the old instances keep the old `timer_start` while the mounted one uses the new one. Every second they overwrite each other's value, so the timer jumps between two different elapsed times. When the instance with the newer `timer_start` writes right after one with an older start, it tries to show a smaller elapsed time than what is already there, and the subtraction in `TimerReactive` produces a negative number of seconds. ### Fix Move the per-second timer update into a `useEffect`. The effect only runs after the field is mounted, and Owl automatically cleans it up when the field is unmounted or when `timer_start` or `timer_pause` change. This means fields that are destroyed before they are mounted never start an interval, so only the mounted field updates the shared timer. `onRecordChange` no longer starts or stops the interval. It only updates the displayed timer value to match the current record. opw-6209405 Forward-Port-Of: odoo/enterprise#128151 Forward-Port-Of: odoo/enterprise#126242
Code cleanup and technical improvements
This update removes a duplicate internal way of referring to the active point-of-sale order and uses one standard method instead. It should not change cashier workflows, but it makes the POS code easier to maintain across localization, stock, and IoT features.
Original PR description
..., pos_stock, l10n_sa_edi_pos, l10n_at_pos, l10n_mx_edi_pos, pos_iot --- `PosStore.selectedOrder` was just an alias for `getOrder()`. Use `getOrder()` directly everywhere and drop the getter. --- Task: https://www.odoo.com/odoo/project/1737/tasks/6468321
20 changes
Security fixes and vulnerability patches
Interviewers can no longer edit or delete applicants they referred unless they are assigned to interview them. This keeps referral records protected and ensures interviewers only manage candidates within their proper responsibilities.
Original PR description
Steps to reproduce: - Create user A with interviewer role - A is referring a candidate B for a job - Go to referral and click on the number "1" - Group the view by stages - Drag & drop B between stages Current behavior: Interviewer can write/unlink on his referee aplicant Expected behavior: Interviewer has only rights on assigned interviewee applicant task-id: 6452353
Enhancements to existing features
Users can again use folder action menus when working with document views embedded in spreadsheets or knowledge articles. This makes it easier to share and reuse live folder views while keeping shared access tokens protected from unintended exposure.
Original PR description
Also impacted: test_documents_full It is convenient to export a dynamic view of a folder in both spreadsheet and knowledge links settings. * Care is taken to avoid leaking access folders tokens through the search panel/model's state in knowledge. * We also enable sharing folders shared via link through embedded views as it enables benefitting from the power of them vs. adding the link to the folder in the article. * As with other actions initiated on shortcuts, the "real" operation is done on the target. Sharing the target is simpler than patching a folder "child_of" to return the target children (shortcut as documents_unique_folder_id is not supported). Task-5180137
Turkish payroll settings are updated for 2026, including clearer minimum wage naming and new configurable social security contribution values. This helps payroll teams calculate SSI contributions more accurately within the updated minimum and maximum contribution limits.
Original PR description
- Update the Turkish payroll rule parameters for 2026. - Rename the minimum wage parameter to 'Turkiye Minimum Net Wage'. - Add configurable parameters for the SSI minimum contribution base and employee contribution rate. - Update the SSI contribution computation to account for both the minimum and maximum contribution bases. **task-6397284**
The Dutch payroll localization now includes the 2026 resident income tax rate values. This helps payroll calculations stay aligned with upcoming tax rules for employees residing in the Netherlands.
Original PR description
Added 2026 values for the residents' income tax rates rule parameter. task-6462877 Forward-Port-Of: odoo/enterprise#127556
Resolved issues and error corrections
Australian payroll batches can now include employees with and without leave allocations without causing an error. This helps payroll teams process mixed payslip batches reliably, with missing unused leave values treated as zero instead of blocking the batch.
Original PR description
Creating a batch of payslips mixing employees with and without leave allocations raises a KeyError: ``` File "l10n_au_hr_payroll/models/hr_payslip.py", line 869, in _add_unused_leaves_to_payslip…
Creating a batch of payslips mixing employees with and without leave allocations raises a KeyError:
```
File "l10n_au_hr_payroll/models/hr_payslip.py", line 869, in _add_unused_leaves_to_payslip
annual_gross = leaves_totals[payslip.id]['annual'] * daily_wage
~~~~~~~~~~~~~^^^^^^^^^^^^
KeyError: 22
```
Current Issue:
`_l10n_au_get_unused_leave_by_type` only materialises leaves_by_date[payslip.id] inside the allocation loop, so a payslip whose employee has no matching allocation never gets a key. `_l10n_au_get_unused_leave_totals` then rebuilt a plain dict out of those entries and only fell back to a defaultdict when leaves_by_date was completely empty. A mixed batch is not empty, so the plain dict was returned and `_add_unused_leaves_to_payslip` raised on the payslips that were missing from it.
This never showed up in the UI, **where payslips are created one at a time**: a single slip either has an allocation, or produces an empty mapping that hits the fallback.
Approach:
Build the totals on a defaultdict and update it instead of returning a plain dict, so any payslip without allocation resolves to 0 rather than being absent. This also drops the need for the empty special case, and keeps the mapping consistent with the defaultdict returned by `_l10n_au_get_unused_leave_by_type`, which `_l10n_au_get_leaves_for_withhold` indexes the same way.
task-6465229Odoo now correctly updates VoIP call records when a call is answered or rejected in another phone application such as Linphone. This prevents those calls from being incorrectly marked as missed, giving users a more accurate call history when Odoo is open alongside external VoIP tools.
Original PR description
Steps to reproduce: - Have an external VoIP software configured (e.g. Linphone) - Have your Odoo configured and opened too - Call your VoIP number, using your smartphone => Both the softphone and…
Steps to reproduce: - Have an external VoIP software configured (e.g. Linphone) - Have your Odoo configured and opened too - Call your VoIP number, using your smartphone => Both the softphone and Linphone ring - Answer or reject using Linphone => The VoIP call record in Odoo immediately switches from "Trying to call" to "Missed". While there was no guarantee for our VoIP integration to work alongside Linphone in 19.0, we decided this should be an easy safe enough fix. Starting 19.2 (with [1]), the fix will be simplified and hopefully prettier thanks to the ameliorations that were made. After this fix, provided Odoo is open while Linphone is used, the call records will now switch to the right terminated / rejected status, still immediately once Linphone answers / rejects. In future versions and especially 20.0+, the system will be different and will allow way more features like this one to work better (e.g. here the call record only even exists if Odoo is opened while using Linphone and we won't have any information about the call duration). [1]: https://github.com/odoo/enterprise/commit/942f32316ab02d8c739fe7fdd5ec2bdde472a68e task-6449259
Auto planning for monthly schedules now correctly includes the last day of the selected month. This prevents working time from being left unscheduled because of timezone conversion issues, improving reliability for sales planning and resource scheduling.
Original PR description
Steps to reproduce: --------------------------- 1. Install `sale_planning` with demo data. 2. Create a SO with a planning product, set the quantity to 100 hours, and confirm the SO. 3. Click the "To…
Steps to reproduce: --------------------------- 1. Install `sale_planning` with demo data. 2. Create a SO with a planning product, set the quantity to 100 hours, and confirm the SO. 3. Click the "To Plan" button, then click "Auto Plan". 4. Make sure the "Month" filter is selected in the scale options and observe the planned slots. Issue: -------- When auto planning slots for a month, the last day of the month is excluded. For example, slots are scheduled only until July 30th, even though July 31st is a working day. Cause: -------- While preparing the context, `stopDate` is set to July 31st at 00:00. It is then passed to [serializeDateTime()](https://github.com/odoo/odoo/blob/dacaad91bba8f959daf5d89a046c5a1c11e48eec/addons/web/static/src/core/l10n/dates.js#L553-L560), which converts the datetime to UTC. Depending on the user's timezone, this can shift the date to the previous day, causing the last day of the month to be excluded. Solution: ------------ Use `localEndOf()` to set `stopDate` to the local end of the selected range before passing it to `serializeDateTime()`. This ensures the last day of the month is preserved during UTC conversion. **NOTE:** Forward-port the solution from the 18.0 version, which was adapted to the publish shift use case in 18.3 and introduced this issue. Add a HOOT test case to prevent this regression in future versions. References: [18](https://github.com/odoo/enterprise/commit/bc3db24f83473d5646f7c2cfca8ed1c5b064ea2e) and [saas-18.3](https://github.com/odoo/enterprise/commit/c81fba31780869940f726b695ad46a87f69798fb) opw-6391495
Internal transfers between a branch and its parent company can now be reconciled even when the branch transaction was processed with a reconciliation model. This prevents erroneous company mismatch errors and keeps branch-to-company bank matching working as expected.
Original PR description
When reconciling an internal transfer between a branch and its parent company, an User Error is raised if the branch transaction has been processed via reconciliation model. Steps to reproduce: -…
When reconciling an internal transfer between a branch and its parent company, an User Error is raised if the branch transaction has been processed via reconciliation model. Steps to reproduce: - Have a company with branch both selected - On the branch, create a reconciliation model "Internal transfer" that assigns the whole balance to the liquidity transfer account - Have a Bank journal on the company and a Bank journal on the branch - On the branch bank journal, create a -100 transaction 'testb' and reconcile it using the branch internal transfer model - On the company bank journal, creata a 100 transaction, open the reconciliation widget and select the branch transaction to match it Issue: The reconciliation is refused with a company inconsistency error ``` Uh-oh! You’ve got some company inconsistencies here: - “BNK1/2026/00011 test” belongs to company “YourCompany” while “Reconciliation Model” (reconcile_model_id: 'Internal Transfer branch') belongs to another company. To avoid a mess, no company crossover is allowed! ``` However, if user manually assign the transfer account to the branch transaction, the reconciliation proceed as expected Analysis: When reconciling, we build the counterpart journal item by cloning the values of the matched move line, copying also the reconcile model. That field is company dependent and flagged copy=False, so it should not be propagated. opw-6365856
Resetting a bank statement line to draft no longer triggers an error caused by an unexpected system response. This helps accounting users complete transaction corrections without interruption.
Original PR description
Currently, an error occurs when resetting a **bank** statement line to draft. **Steps to Reproduce:** - Install the `Accounting` module. - Go to `Accounting Dashboard` and click the `three-dot` on…
Currently, an error occurs when resetting a **bank** statement line to draft. **Steps to Reproduce:** - Install the `Accounting` module. - Go to `Accounting Dashboard` and click the `three-dot` on bank journal. - Open `Transactions`. - Create a new `statement line`. - Select the `statement line`, click the `gear action`, and click `Reset to Draft`. `AttributeError: 'bool' object has no attribute 'setdefault'` After the [recent commit], when resetting the statement line to draft, the server action run [1] and the linked move is going reset to draft, and the method returns the result [2]. After the mentioned commit, the method returns True [3]. When the result from [4] is passed to clean_action, it raises an error [5]. This commit ensures that it returns None after resetting the statement line linked to the invoice to draft, as it previously returned None and same as like [6]. [recent commit]: https://github.com/odoo/odoo/commit/712718d9df0fd5044ac57fdd9ec58e64bece36c0 [1]- https://github.com/odoo/enterprise/blob/7ca28a1c079d22b60e3756ca9b4f404771f214e8/account_accountant/views/bank_rec_widget_views.xml#L541-L551 [2]- https://github.com/odoo/enterprise/blob/eae51e3ca155ca29e1de54f4bd223e7540b2aa7f/account_accountant/models/account_bank_statement.py#L112-L114 [3]: https://github.com/odoo/odoo/blob/b3444e4bd421a30229b0dc7c8d5eb6c78b8b51ed/addons/account/models/account_move.py#L6236-L6251 [4]: https://github.com/odoo/odoo/blob/a6f99706c6a62fc65666a0ff5e58fa465b41a6fb/addons/web/controllers/action.py#L53-L59 [5]: https://github.com/odoo/odoo/blob/b3444e4bd421a30229b0dc7c8d5eb6c78b8b51ed/addons/web/controllers/utils.py#L24 [6]: https://github.com/odoo/odoo/blob/d7df2e8acff9eb7066993fa6a0b0c6d7c85baabc/addons/account/models/account_payment.py#L1206-L1208 sentry-7354160052
This change adds automated coverage for an Italian tax report carryover scenario, helping ensure VAT amounts are handled correctly across reporting periods. It reduces the risk of regressions in Italian localization reports after related fixes.
Original PR description
Add test for https://github.com/odoo/odoo/pull/279471 opw-6354509
The TikTok sales integration now skips shops that have not completed authorization when syncing orders. This prevents scheduled order imports from failing due to missing authorization details, keeping the sync process stable for connected shops.
Original PR description
Currently, an error occurs when orders are being fetched from shops with pending authorization. Steps to replicate: - Install `sale_tiktok`. - Open Sales > Configuration > Shops (Under the title…
Currently, an error occurs when orders are being fetched from shops with pending authorization.
Steps to replicate:
- Install `sale_tiktok`.
- Open Sales > Configuration > Shops (Under the title tiktok shops).
- Click `Connect New Shop` > Give values for `App key, App secret, Service ID`.
- Click `Connect Shop & Authorize` and then Return back to Odoo.
- Run the Scheduled Action `TikTok Shop: sync orders`.
Error:
```
File '/home/odoo/src/enterprise/saas-19.4/sale_tiktok/utils.py', line 171, in make_tiktok_api_request
if now > shop.access_token_expire_datetime - timedelta(minutes=5):
TypeError: unsupported operand type(s) for -: 'bool' and 'datetime.timedelta'
ValueError: TypeError('unsupported operand type(s) for -: 'bool' and 'datetime.timedelta'') while evaluating
'model._sync_orders()'
```
Cause:
- Since the shop has not yet been authorized with TikTok, the `access_token_expire_datetime` field is not set. This field is only populated after the shop is successfully authorized (see [this]).
- Later, when the `TikTok Shop: sync orders` cron runs, the flow reaches [here], where we checks whether the access token is expired and needs to be refreshed. At this point, `access_token_expire_datetime` is still False because the shop has not been authorized yet.
Solution:
- The orders should only be fetched from those shops that are authorized with TikTok.
- Used the `access_token` field to determine whether a shop is authorized, as it is only populated after the authorization flow is successfully completed.
[this]: https://github.com/odoo/enterprise/blob/593409ca160863d3e985f3cf5e2aadda1d450b41/sale_tiktok/controllers/onboarding.py#L52-L54
[here]: https://github.com/odoo/enterprise/blob/593409ca160863d3e985f3cf5e2aadda1d450b41/sale_tiktok/utils.py#L171
sentry-7631179329Fixed an issue where attendance records at Monday midnight could be missing from the weekly Gantt view when the week starts on Monday. This ensures managers and HR teams see the same attendance information in weekly planning views as they do in list and monthly views.
Original PR description
### Current behavior: With first day of week set to Monday, a Monday attendance that starts and ends at local midnight does not appear in Attendances weekly Gantt. The same record is visible in List…
### Current behavior: With first day of week set to Monday, a Monday attendance that starts and ends at local midnight does not appear in Attendances weekly Gantt. The same record is visible in List and Monthly Gantt. Switching the first day of week to Sunday also shows it in weekly Gantt view. ### Expected behavior: Monday attendances should appear in weekly Gantt view when the week starts on Monday, including 0-duration records at Monday 00:00. ### Steps to reproduce: 1. Set first day of the week to Monday 2. Create an attendance on Monday with check-in and check-out at 00:00 3. Open Attendances > Gantt > Weekly 4. The Monday column is empty while List still shows the record ### Cause of the issue: `AttendanceGanttModel._getDomain` filters with check_out > range start. When the week starts Monday, range start is Monday 00:00, so a record whose check_out equals that bound is excluded. ### Fix: Fix the comparison to be `check_out >= range start` so records ending at week start are still fetched and rendered. opw-6414884
The bank journal screen now hides online synchronization prompts when the selected bank statement source is not set to synchronization only. This prevents users from seeing misleading send-now actions or connection requests after changing how bank statements are handled.
Original PR description
Before this commit, the "send now" button and the connection request were shown as soon as we had an account online account link to the journal. But when changing the bank statement source, the information would still be there. Changing the invisible condition to hide it when the bank statement source is different from only_sync no task id
Barcode deliveries now use the real storage location of a scanned serial-numbered item, even when the workflow does not ask workers to scan a source location. This prevents stock from being deducted from the wrong warehouse location, avoiding inaccurate inventory balances such as stale or negative quantities.
Original PR description
Steps to reproduce --- 1. Enable Storage Locations and Lots/Serial Numbers. 2. Set the delivery operation type's "Source Location" to "Do not scan". 3. Create a serial-tracked product with a serial…
Steps to reproduce --- 1. Enable Storage Locations and Lots/Serial Numbers. 2. Set the delivery operation type's "Source Location" to "Do not scan". 3. Create a serial-tracked product with a serial stored in a sublocation (e.g. WH/Stock/Section 2). 4. Confirm a sale order for it, open the delivery in Barcode, and scan an unreserved serial. Issue --- Scanning the unreserved serial creates a new move line that falls back to _defaultLocation() because the decoded scan carries no source location (the operation type does not require scanning one) and never carries the serial's quant location. https://github.com/odoo/enterprise/blob/f42cfa7265ce32bd95f7f805cf4fb54d8b79cce1/stock_barcode/static/src/models/barcode_model.js#L937-L944 For a delivery, that default resolves to the picking's own source location (the parent WH/Stock), so the line is sourced from the parent instead of the sublocation where the serial physically sits. https://github.com/odoo/enterprise/blob/f42cfa7265ce32bd95f7f805cf4fb54d8b79cce1/stock_barcode/static/src/models/barcode_picking_model.js#L1542-L1544 On validation the unit is deducted from the parent location instead of the sublocation, leaving a stale quant of the serial in the sublocation and a negative quant at the parent. opw-5864414
Fixes a General Ledger issue where opening balances could show the wrong foreign currency amount or a missing currency when companies with different currencies share the same chart of accounts. This prevents misleading balances in multi-company accounting reports.
Original PR description
**Steps to reproduce:** * Install the **Accounting** module. * Create two companies with different currencies (e.g. **USD** and **CAD**) sharing the same **Chart of Accounts**. * Open a shared…
**Steps to reproduce:** * Install the **Accounting** module. * Create two companies with different currencies (e.g. **USD** and **CAD**) sharing the same **Chart of Accounts**. * Open a shared account and: * Add both companies in the **Company** field. * Under the **Mappings** tab, configure a mapping for each company. * In each company, create and post a journal entry on the same shared account (for example, a receivable account) using the company's own currency. * Set the journal entry dates to the **current month**. * Open **Accounting → Reporting → General Ledger**. * Change the reporting period to the **following month** so the posted entries are shown as the **Initial Balance**. * Open the report separately for each company. **Observed behavior:** * From the **CAD company**, the Initial Balance displays **USD 2,000** instead of the expected **USD 1,000**. * From the **USD company**, the **Currency** column on the Initial Balance is **blank**. **Cause:** * The SQL query for the `id_with_accumulated_balance` groupby used `SUM(amount_currency)` and `MIN(currency_id)` to aggregate all pre-period lines into a single Initial Balance row. * In a multi-company shared Chart of Accounts, lines from different companies (each with their own currency) were collapsed into the same group, causing `SUM(amount_currency)` to add amounts across currencies and `MIN(currency_id)` to return an arbitrary currency ID. * Additionally, the Python accumulation loop incorrectly performed **integer addition** on `currency_id` (a foreign key), further corrupting the displayed currency. **Fix:** * Replace `SUM(amount_currency)` and `MIN(currency_id)` with `CASE` expressions `MIN = MAX` is a uniformity check that works for **any number of currencies**: if every row in the group shares the same currency the condition is true and the correct sum is returned; if even one row differs the condition is false and both fields return `NULL`. The original three-column `GROUP BY (id, date, account_id)` is preserved. * The Initial Balance row now correctly shows a **blank** currency column, consistent with the Odoo 18 behavior, instead of an incorrect aggregated foreign currency amount. opw-6375310
The update adjusts automated testing for restaurant appointment flows in Point of Sale so tests continue working after POS data reloads. This keeps production behavior unchanged while improving confidence that the appointment workflow remains reliable.
Original PR description
A recent PR in the community repository introduced a full clear of both `localStorage` and `sessionStorage` when reloading POS data. While this is the intended behavior in production, it breaks the test framework. This commit mocks the `clear` methods directly within the tour steps right before the reload action. This ensures the test survives the page reload and keeps its state, without polluting the core production code with test-specific logic. task-6456447
This fix prevents barcode-related inventory screens from failing when tracked and untracked stock movement lines appear together. It keeps tracking details correctly matched to the right lines and shows a clear message for lines without tracking information instead of causing an error.
Original PR description
Problem: `_compute_electronic_product_code` built `tracking_number_list` by filtering out move lines without a lot_id/lot_name, but kept iterating over the full, unfiltered `move_line_ids`. As soon as a tracked product had an untracked move line mixed in with tracked ones (e.g. a manufacturing byproduct move line with no lot), the two lists fell out of sync: at best tracking numbers got assigned to the wrong move line, at worst `tracking_number_list[i]` went out of range and raised an IndexError. Solution: Exclude untracked move lines from `move_line_ids` before building `tracking_number_list`, so both stay the same length and index- aligned. Untracked lines get their own explicit "no tracking number" error instead of breaking the alignment for the rest. Steps to reproduce: Open runbot V19 -> go to moves history (Inventory) -> add `electronic_product_code` to list view using studio -> remove filter/select all records -> https://anotepad.com/notes/jwxyskc2
This fix prevents an unnecessary warning popup from appearing in self-order kiosk setups when the system checks for an IoT printer. It helps avoid confusing customers or staff with an alert that is not relevant for that ordering flow.
Original PR description
This PR fixes the test where iot request triggers a "failed to contact your iot box on local network popup"
This fix prevents spreadsheet-related data from being converted into plain text when it should keep its structured format. It helps ensure users see and interact with linked records accurately in spreadsheet views and controls.
Original PR description
See community PR task-6307092
Acerta payroll exports for Belgian employees now include weekend days when an eligible leave period overlaps a weekend. This ensures sick leave and similar absences are reported according to Acerta requirements, reducing missing data in payroll submissions.
Original PR description
## Steps to reproduce: - Install l10n_be_hr_payroll_acerta - Create an employee in a belgian company - Create a sick time off for the created employee that overlaps with a weekend - Export acerta report for the employee - Notice the weekend that overlaps with the time off is not present in the report ## Cause: While exporting the report file we only loop over the created work entries' dates and since weekends doesn't have work entries we don't consider them in the report. ## Fix: When generating the line of a leave's start date we check if the leave overlaps with a WE, we fetch the WE's date and we generate a line for each day of the WE. According to Acerta this is the correct behavior for their reports for specific types of leaves. **opw-6313534** Forward-Port-Of: odoo/enterprise#127852 Forward-Port-Of: odoo/enterprise#124500
1 change
Resolved issues and error corrections
WhatsApp messages using templates with many mixed variable types now place each value in the intended placeholder. This prevents customers from receiving messages with swapped or incorrect details when templates contain 10 or more variables.
Original PR description
**Issue**:
Sending a WhatsApp template with 10 or more variables can assign values to the wrong placeholders when the body contains mixed variable types, such as free text, field, or user name variables.
Templates containing only free-text variables are not affected.
**Reason**:
Meta consumes template parameters positionally, but for mixed variable types, Odoo built the parameter list using the template variable recordset order.
That order can differ from the numeric placeholder order, notably placing {{10}}, {{11}}, {{12}}... before {{1}}
when sending the message, as the payload parameters are not ordered by their numeric placeholder index.
**Fix:**
Sort body variables by their numeric placeholder index before preparing the Meta payload.
Task-6401501
Forward-Port-Of: odoo/enterprise#125671