Daily updates from Odoo
Wednesday, July 29, 2026
122 changes
19 changes
Enhancements to existing features
Users reconciling a bank transaction with a different partner will now be notified and can choose whether to move the bank account to the selected partner. This helps keep partner bank details accurate and reduces manual cleanup after reconciliation.
Original PR description
Add a new notification in the bank reco widget when a user do a reconciliation with a partner different from the one on the st_line. The idea is to let the user chose if he wants to move the bank account from the st_line partner to the move he tries to reconcile. task-6303397 Forward-Port-Of: odoo/enterprise#125702 Forward-Port-Of: odoo/enterprise#120900
Australian payroll now applies a superannuation contribution cap in the relevant salary rule. This helps businesses stay aligned with ATO requirements for maximum superannuation contributions, including the updated guarantee calculation from 1 July 2026.
Original PR description
Added superannuation limit to the QE rule. Task-6221399 Forward-Port-Of: odoo/enterprise#119792
The ActivityWatch suggestions panel now shows total tracked time by project and a grand total for all suggestions. This gives users a clearer view of their logged hours before turning suggestions into timesheets.
Original PR description
This commit introduces new time tracking metrics to the ActivityWatch suggestions panel to improve user visibility into their tracked hours. **Enhancements:** - Added the total duration per project in the By Project grouped view. - Added a grand total footer for all suggestions at the bottom of the list. task-6088877 Forward-Port-Of: odoo/enterprise#125087 Forward-Port-Of: odoo/enterprise#114772
Bank statement reconciliation has been optimized to avoid timeouts in large multi-company databases. This should make scheduled reconciliation jobs run much faster and more reliably, reducing delays in accounting operations.
Original PR description
The standard cron process reconciles batches of 100 bank statement lines at a time. In multi-company environments with large historical datasets, this query frequently timed out, exceeding the…
The standard cron process reconciles batches of 100 bank statement lines at a time. In multi-company environments with large historical datasets, this query frequently timed out, exceeding the 15-minute execution limit. Even when throttled to a batch size of 10 records, the query required approximately 18.8 seconds to execute. The performance degradation was driven by the following factors: 1. Suboptimal Lateral Filtering: The statement line batch array filter was placed inside the `LATERAL` block's `WHERE` clause. This prevented the query planner from optimizing the drive path effectively across iterations. 2. Inefficient Join Sequence and Filtering: The original join order scanned `account_move_line` before resolving the company hierarchy constraint. As a result, millions of rows across all companies were retrieved from the index, forcing repeated primary key lookups on `res_company` before ultimately discarding over 99.9% of the records via the late `parent_path` hierarchy filter. This commit addresses these issues by: 1. Moving the `st_line.id` filtering constraint out of the lateral subquery and into the outer main query block to guide the execution path properly. 2. Reordering the inner `LATERAL` subquery to resolve the company hierarchy (`res_company`) prior to joining `account_move_line`. This constraints the scan boundaries early in the pipeline. Performance Benchmarks (10 record batch): - Before Execution Time: ~18,818 ms - Before Shared Hit Blocks: 8,535,272 - After Execution Time: ~130 ms - After Shared Hit Blocks: 67,204 ms Before Plan: https://explain.dalibo.com/plan/h4739gh3519eaa43 After Plan: https://explain.dalibo.com/plan/5ebc5g8deff272gc Forward-Port-Of: odoo/enterprise#125686 Forward-Port-Of: odoo/enterprise#125250
Resolved issues and error corrections
Incoming Chilean electronic document emails that are missing a recipient tax ID no longer stop the mailbox processing job. The system now handles the missing value gracefully, so one malformed customer claim will not repeatedly block other incoming mail from being processed.
Original PR description
### Problem `Mail: Fetchmail Service` cron aborts with `AttributeError: 'NoneType' object has no attribute 'upper'` in `l10n_cl_edi/models/fetchmail_server.py::_process_incoming_customer_claim` when…
### Problem
`Mail: Fetchmail Service` cron aborts with `AttributeError: 'NoneType' object has no attribute 'upper'` in `l10n_cl_edi/models/fetchmail_server.py::_process_incoming_customer_claim` when an incoming customer claim DTE has no `<RUTRecep>`:
```python
dte.findtext('.//ns0:RUTRecep', ...).upper() or
dte.findtext('.//ns0:RutReceptor', ...).upper()
```
`findtext()` returns `None` when the tag is missing, so `.upper()` blows up before the `or` fallback can run. Once the cron hits such a message it re-crashes on every subsequent run and blocks the whole mailbox until the offending mail is deleted.
### Fix
Guard each `findtext(...)` with `or ''` so the `or` chain actually falls through. Empty `partner_vat` is already handled by the existing "Partner … has not been found" branch a few lines below.
### Traceback (Odoo 19)
```
File "/mnt/extra-addons/enterprise/l10n_cl_edi/models/fetchmail_server.py", line 285, in _process_incoming_customer_claim
dte.findtext('.//ns0:RUTRecep', namespaces=XML_NAMESPACES).upper() or
AttributeError: 'NoneType' object has no attribute 'upper'
```
### Ticket
No ticket open for this but opw-5257481 is related.
Forward-Port-Of: odoo/enterprise#123387The Malaysia Statement of Account report now calculates total and overdue amounts using the selected statement date. This ensures the PDF totals match the balances shown in the report, improving accuracy for past-date customer account statements.
Original PR description
## Current behavior: In Malaysia's Statement of Account, the total and total overdue amounts dont consider the selected Statement Date, and will calculate all the balances up until today in the…
## Current behavior: In Malaysia's Statement of Account, the total and total overdue amounts dont consider the selected Statement Date, and will calculate all the balances up until today in the generated PDF report ## Expected behavior: The total and total overdue amounts should only sum the balances included in the report up until the selected Statement Date ## Steps to reproduce: 1. Install l10n_my_reports module, switch to Malaysian company 2. Go inside Invoicing > Report > Aged receivable 3. Select a specific date in the past 4. Observe that the total amounts dont match with the balance column, and wont change regardless of the date selected ## Cause of the issue: The template used o.total_overdue which ignores the report domain and statement date ## Fix: Accumulate overdue_total in the template loop with the same domain and date_to cutoff as the balance lines, so it always matches the displayed Balance lines for the selected Statement Date opw-6332970 Forward-Port-Of: odoo/enterprise#125410 Forward-Port-Of: odoo/enterprise#123694
Mexican payroll now calculates expected work hours even when a draft payslip has not yet been created. This helps off-cycle payroll runs start with consistent attendance and work-hour values, reducing manual corrections and payroll delays.
Original PR description
Previously, the `_preprocess_work_hours_data` method would abort early if no payslip was found for the given period. This prevented the correct generation or evaluation of expected Mexican work hours…
Previously, the `_preprocess_work_hours_data` method would abort early if no payslip was found for the given period. This prevented the correct generation or evaluation of expected Mexican work hours in contexts where a draft payslip does not yet exist (i.e., when an off-cycle payslip is initially generated for a given time period). To resolve this, the strict dependency on the payslip record has been removed. It now falls back to the contract version's base data when a payslip is absent: - The calendar defaults to the contract version's `resource_calendar_id`. - The duration is assumed to be standard (`is_wrong_duration = False`). - The Mexican schedule table (`l10n_mx_schedule_table`) is fetched globally from the environment (`hr.rule.parameter`) rather than relying on the payslip-specific helper method. This ensures expected work hours and attendance fields are calculated consistently across all payroll workflows, regardless of whether the payslip has been instantiated. opw-6351402 Forward-Port-Of: odoo/enterprise#125219
Accrual report totals now show the correct amounts when users group records by dates such as order date. This prevents misleading zero totals in purchase and sales accounting reports, helping teams review bills and revenue accruals accurately.
Original PR description
Currently, when users group accrual reports by dates, aggregated computed fields are not calculated correctly. As a result, values such as the total amount may be displayed as zero even when the…
Currently, when users group accrual reports by dates, aggregated computed fields are not calculated correctly. As a result, values such as the total amount may be displayed as zero even when the grouped records contain non-zero amounts.
## Steps to produce:
- Install Purchase and Accounting
- Create a product `Energy drink Sample` with cost 10$
- Create and Confirm a PO with the energy drink sample with vendor as `Administrator`
- Set `Received` as 1.
- Go to Accounting > Review > Bill to Receive
- Search Filter> Remove vendor grouping > Use Custom Group `Order Date`
- Expand the group
## Observed Behavior
Although the grouped lines contain an amount of 10 dollars, the aggregated total displayed in the sum remains zero.
The total should reflect the combined value of the grouped lines.
## Root Cause:
This issue occurs because opening the view triggers `_read_group_for_accrual`, which overrides the `_read_group` method on purchase order lines. The purpose of this override is to support grouping on computed fields that are not stored in the database such as `amount_to_invoice_at_date (Amount)`, `qty_received_at_date (Received)`.
When `_read_group_for_accrual` is executed, it delegates grouping for non-computed fields to the parent `_read_group` implementation, as shown at [1].
The parent method returns results in `res` similar to:
```
[(datetime.datetime(2026, 6, 1, 0, 0), 1.0, 10.0, 1)]
```
During iteration over res at [2], the code uses `group[0]` as the grouping key. In this example, group[0] is `datetime.datetime(2026, 6, 1, 0, 0)`, which represents the granularity date
(e.g., the first day of the month when grouping by month).
However, `records_by_group` is keyed by the actual purchase order line order dates rather than the granularity dates returned by `_read_group`. For example:
```
{datetime.datetime(2026, 6, 12, 0, 0): purchase.order.line(1,)}
```
As a result, the lookup performed using the granularity date (`datetime.datetime(2026, 6, 1, 0, 0))` does not find a matching entry in `records_by_group`. Consequently, records falls back to an empty `purchase.order.line()` recordset.
Later, at [3], the aggregation logic computes totals using this empty recordset. Since the required fields are evaluated on an empty set of records, the aggregated values are computed as zero.
This ultimately causes the method to return a total value of zero
[1]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L9-L24
[2]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L40-L42
[3]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L44-L47
## Solution:
The issue can be resolved by grouping records using the same date granularity specified in the groupby, rather than using the field values.
By aligning the grouping logic with the granularity returned by `_read_group` (for example, grouping by the first day of the month when using monthly grouping ), the keys in `records_by_group` match the values returned in `res`. As a result, the corresponding records are correctly retrieved during aggregation.
This ensures that the aggregation is performed on the appropriate purchase order lines instead of an empty recordset, allowing the computed totals to be calculated correctly.
[opw-6261225](https://www.odoo.com/odoo/project/49/tasks/6261225)
Forward-Port-Of: odoo/enterprise#120369Users without Planning access can now add products from the catalog on relevant sales quotations without hitting an access error. This prevents blocked sales workflows when quotations are linked to field service or planning records the user cannot directly access.
Original PR description
A user without Project rights cannot add a product from the catalog on a sale order Steps to reproduce: 1. Install industry_fsm_sale module 2. Go to Settings > Users & Companies > Users and open user Marc Demo 3. Set Field Service and Project rights to No 4. Log in as Marc Demo 5. Go to Sales and open any quotation 6. Click on Catalog in the order lines 7. An access error is raised Issue: industry_fsm_sale overrides `action_add_from_catalog` and tries to read sale.order.tasks_ids but users can't always access this field as it requires Project rights Solution: Check that the user has Project rights before trying to read tasks_ids opw-6315647 Forward-Port-Of: odoo/enterprise#125526 Forward-Port-Of: odoo/enterprise#123228
Swiss payroll contract templates now show the same relevant wage fields as the employee contract view. When a template is loaded for an employee, wage type and wage-related values such as hourly wage are correctly carried over, reducing manual re-entry and payroll setup errors.
Original PR description
## Issue When creating a Contract Template for a Swiss company, the template does not match the version shown in the Employee's view. Also, some fields are not correctly applied when loading a…
## Issue
When creating a Contract Template for a Swiss company, the template does not match the version shown in the Employee's view. Also, some fields are not correctly applied when loading a contract template on an employee (e.g. `hourly_wage`, `wage`, ...).
## Steps to reproduce
1. Install *Switzerland - Swissdec Certified ELM 5.0 - Payroll* (`l10n_ch_hr_payroll`)
2. (Create and) Use a Swiss company
3. In Employees > Configuration > Contract Templates, create a Contract Template
- Wage Type: Hourly Wage
- Hourly Wage: Any value > 0
- **(Notice how the aforementionned fields are missing from the template)**
4. In Employees > Employees, create an Employee
5. On the new employee's view, on the Payroll tab, click "Load Template"
and load the template created in step 3
6. **The data from the template is not applied to the employee's contract**
## Cause
The fields loaded from a contract template are listed in the `whitelist` variable of the `hr.version.wizard`:
https://github.com/odoo/odoo/blob/5c3deb11627f4d6762c4994207bd582afb96f064/addons/hr/wizard/hr_contract_template_wizard.py#L15-L30
Multiple fields were missing from the whitelist (e.g. `hourly_wage`, `l10n_ch_has_{hourly|monthly|lesson}`, ...). These fields would not be loaded from the template when applying a template on an employee.
**This commit replicates the employee's version view on the contract template and adds the related fields to the whitelist for them to be correctly applied when loading a contract template.**
opw-5966664
opw-6128467
Forward-Port-Of: odoo/enterprise#125435
Forward-Port-Of: odoo/enterprise#110683Credit notes for Guatemalan invoices now reference the original invoice's actual issue date instead of a technical certification timestamp. This helps prevent rejected electronic documents by matching SAT validation requirements.
Original PR description
### Issue before this commit: When reverting a Guatemalan invoice (creating a credit note), the XML node FechaEmisionDocumentoOrigen incorrectly reports the EDI document's technical certification…
### Issue before this commit: When reverting a Guatemalan invoice (creating a credit note), the XML node FechaEmisionDocumentoOrigen incorrectly reports the EDI document's technical certification date instead of the original invoice's emission date. This causes the SAT to reject the document. ### Steps to reproduce the issue: 1. Download Accounting and l10n_gt 2. Revert an invoice (credit note) inserting a different date than the one of the invoice 3. See that FechaEmisionDocumentoOrigen report the date of the credit note instead of the one of the invoice ### Cause of the issue: The _l10n_gt_edi_add_reference_values method extracted the date from original_document.datetime (the technical timestamp of when the XML was generated) rather than using the actual accounting date of the original invoice. ### Reason to introduce the fix: SAT validation rules strictly require the reference date to match the exact commercial emission date of the original invoice. Fetching invoice_date directly ensures compliance, avoids timezone conversion errors, and prevents the XML from being rejected. Source: https://www.lawinsider.com/es/contracts/dJXl4Vo79L2 <img width="730" height="205" alt="2026-07-17_10-19" src="https://github.com/user-attachments/assets/802e7bb3-fcf9-48db-b86f-227b494001b6" /> opw-6394409 Forward-Port-Of: odoo/enterprise#125788 Forward-Port-Of: odoo/enterprise#124794
Installing POS no longer resets customized payroll account settings on Swiss salary rules. This protects customer configuration from being overwritten when related accounting features are installed.
Original PR description
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code…
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code 1000) - Install 'POS' - Check the accounts you configured on the salary rules >The accounts are reset to their default values ### Cause of Issue: POS depends on the module `stock_account`. When `stock_account` is installed, the `_configure_journals` method in its `__init__.py` creates a new `account.journal` for inventory valuation. To apply default values to this journal, the method retrieves data from the chart template. https://github.com/odoo/odoo/blob/7d16ef88784e18e885b97dbded3231775f1349d2/addons/stock_account/__init__.py#L41 The `hr_payroll_account` module overrides `_post_load_data` and unconditionally calls `_load_payroll_accounts(template_code, company)`. https://github.com/odoo/enterprise/blob/fcf06997a0eb999af86e4b6917311a86f04a390e/hr_payroll_account/models/account_chart_template.py#L16-L18 This triggers the reinstallation of the default payroll accounts, which overwrites and discards any manual configuration changes the user has made to their salary rules. ### Fix: Ensure that default payroll accounts are only reset during a genuine chart of accounts loading process, and not during localized post-load operations triggered by other modules. opw-6251112 Forward-Port-Of: odoo/enterprise#121687
Opening the Scrap option from a new manufacturing operation in the Barcode app no longer triggers an error when no location record is available. This prevents interruptions for warehouse and manufacturing users, including a related consignment scanning scenario.
Original PR description
Versions -------- - 18.0+ Steps ----- 1. Open Barcode app; 2. click Operations; 3. click MANUFACTURING 4. click New; 5. click cogwheel on top right; 6. click Scrap. Issue ----- Traceback: > Error: Record stock.location with id=undefined doesn't exist in the cache Cause ----- When setting up the default context for the scrap menu, it it assumes `this.record` is not empty. Solution -------- Make `cache.getRecord` not raise an error when a location isn't found. Use optional chaining for other parts of the context that rely on a `record` being present. Also fixes a related issue introduced by 4b457fe, where the same traceback would be thrown on opening a new MO and scanning a product whilst consignment is enabled. opw-6397774 Forward-Port-Of: odoo/enterprise#125571 Forward-Port-Of: odoo/enterprise#124818
The manufacturing planning screen now correctly uses a product's Bill of Materials batch size even when the BOM was not manually selected while adding the product. This prevents under-planning production quantities and helps ensure forecasts generate the right replenishment amounts.
Original PR description
In MPS, if a product’s Bill of Materials (BOM) is not specified at the time of addition, the system will not correctly account for batch size. Steps to reproduce: ------------------- * Create a…
In MPS, if a product’s Bill of Materials (BOM) is not specified at the time of addition, the system will not correctly account for batch size. Steps to reproduce: ------------------- * Create a product with a bom that has a batch size of 2 * Open MPS * Add the product - without specifying the bom - Route Manufacture * Add 1 in the Forcast Demand -> the batch size from the bom it's not taken into account. Observation: ------------- When updating mps, it will call get_production_schedule_view_state: https://github.com/odoo/enterprise/blob/7092dd2cc3578c3f22d1fbc82d958d57e2f93553/mrp_mps/models/mrp_mps.py#L424 this function when calculating the quantity to resplenish will call _get_resplenish_qty: https://github.com/odoo/enterprise/blob/7092dd2cc3578c3f22d1fbc82d958d57e2f93553/mrp_mps/models/mrp_mps.py#L534 to know the quantity to resplenish it will need the batch size, in mps they will only consider the batch size from the bom registered: https://github.com/odoo/enterprise/blob/7092dd2cc3578c3f22d1fbc82d958d57e2f93553/mrp_mps/models/mrp_mps.py#L863-L865 Since there is no default value for bom_id, If there is no bom selected, there is no batch size. opw-6259956 Forward-Port-Of: odoo/enterprise#124922 Forward-Port-Of: odoo/enterprise#119560
The Belgian Partner VAT Listing now always covers the official calendar year, from January 1 to December 31. This prevents incorrect reporting periods for companies whose fiscal year does not match the calendar year, improving compliance accuracy.
Original PR description
The Belgian Partner VAT Listing must always report on the civil calendar year (01/01/N to 12/31/N). Previously, the report was relying on the company's fiscal year configuration, which caused incorrect reporting periods for companies with non-calendar fiscal years. This commit overrides `_custom_options_initializer` to strictly enforce a civil year date range based on the selected year, entirely ignoring custom fiscal year boundaries. Task-6086513 Forward-Port-Of: odoo/enterprise#125752 Forward-Port-Of: odoo/enterprise#114337
Fixes an issue where changing a work order's planned end time in the Gantt view could crash when dependent work orders were present. The scheduling flow now keeps the information it needs to update related work orders reliably, helping manufacturing planners adjust timelines without interruption.
Original PR description
Resizing a work order from the right edge in the Gantt view updated only the stop date (`date_finished`). Since the work order Gantt view defines dependencies, this triggered dependency propagation…
Resizing a work order from the right edge in the Gantt view updated only the stop date (`date_finished`). Since the work order Gantt view defines dependencies, this triggered dependency propagation through `web_gantt_reschedule`. During that flow, `_web_gantt_move_candidates` needed both the old start and stop dates to update dependent work orders, but the old-value snapshot only contained fields present in `vals`. As a result, `date_start` was missing and the resize crashed with a `KeyError: 'date_start'`. Handle the missing old start date in the generic Gantt dependency propagation flow. When only the stop date is changed, the old start date is added to the old-value snapshot without adding it to the actual write values. This keeps the right-edge resize payload unchanged while giving dependency propagation the values it needs. Steps to reproduce: 1. Create a manufacturing order with work orders. 2. Plan the manufacturing order. 3. Open the Work Order Gantt view. 4. Resize a work order from the right edge to change its planned end date. Before this commit: Right-edge resizing a work order with dependencies crashed during dependency propagation with `KeyError: 'date_start'`. After this commit: Right-edge resizing keeps the changed `date_finished`, dependency propagation has access to the old `date_start`, and dependent work orders are rescheduled without crashing. task-6345347
This fix ensures the US payroll localization is installed through the standard automatic installation process instead of a later setup hook. This makes payroll module detection more reliable during database creation and upgrades, reducing the risk of modules appearing temporarily uninstalled.
Original PR description
In a [previous PR], a test was introduced to reject any `l10n_xx_hr_*` module that has a `countries` key in its manifest and depends on a module that also has that country key. This test was too…
In a [previous PR], a test was introduced to reject any `l10n_xx_hr_*` module that has a `countries` key in its manifest and depends on a module that also has that country key. This test was too broad and rejected some valid cases: 1. A non-auto-install module can have a country key defined to add flags in the apps kanban view. ([src]) 2. A module that has a country-specific regular dependency, but not as an auto-install condition. The second case is illustrated by [l10n_us_hr_payroll], which should auto-install when `hr_payroll` is installed and a US company exists. With the old test, achieving this required adding `l10n_us` to its auto-install dependencies. But, since `l10n_us` is not auto-installable, `l10n_us_hr_payroll` would not be installed if you create a DB with a US company and only install `hr_payroll`. In practice, that module was still being installed via a [post-init hook] in `hr_payroll`. This hook was installing all `l10n_XX_hr_payroll` modules for each country where a company is located, which is the behavior of the `countries` parameter in the manifest. This caused issues during upgrades as this runs late in the process: after the auto-discovery phase. Modules installed by this hook would be considered as `uninstalled` until `hr_payroll` is loaded. This commit narrows the check to only fail if: - A module has a `country` key in its manifest, and - It has a country-specific module in its **auto-install** dependencies. Moreover, it modifies `l10n_us_hr_payroll` to correctly rely on the auto_install mechanism instead of the post-init hook. [previous PR]: https://github.com/odoo/enterprise/pull/101843 [src]: https://github.com/odoo/odoo/blob/6df9f92a537aa4bb4ee5dc946fe31c4e56e6dfea/odoo/addons/base/models/ir_module.py#L271-L273 [l10n_us_hr_payroll]: https://github.com/odoo/enterprise/blob/24a33ffb769557be498d61328522bb77f68d3a5a/l10n_us_hr_payroll/__manifest__.py [post-init hook]: https://github.com/odoo/enterprise/blob/85185595cfd1ee5310ceb9dc80c0589accad2f19/hr_payroll/__init__.py#L21 Forward-Port-Of: odoo/enterprise#125853 Forward-Port-Of: odoo/enterprise#120279
The barcode app now correctly blocks scanning products that were not reserved when extra products are not allowed, even after users leave and reopen a transfer. It also restores the ability to add products on immediate delivery transfers where that action is expected, reducing confusion and preventing incorrect stock entries.
Original PR description
This [PR] made sure it was not possible to scan unreserved products when `allow_extra_product` was disabled, even when exiting and re-entering a transfer. It worked under the assumption that an immediate transfer always stays in draft, which is wrong for deliveries. The 2nd commit of this PR partly address this issue by allowing the user to add multiple products with the "Add Product" button when the transfer is immediate. While working on this issue, we encountered a bug in the scanning prevention that should have been caught by a tour but was not. This is fixed in the 1st commit. More details in the commit messages. [PR]: https://github.com/odoo/enterprise/pull/123793 Forward-Port-Of: odoo/enterprise#125313
This fix prevents Mexican electronic document status checks from repeatedly triggering on the same records. It prioritizes older customer invoices and limits vendor bill checks, reducing unnecessary background processing and helping scheduled checks run more reliably.
Original PR description
While trying to fix the SAT cron, we did not think it through it could cause infinite cron triggering. Indeed, the write_date is updated on every record that is handled. Fix is manyfold: - Avoid…
While trying to fix the SAT cron, we did not think it through it could cause infinite cron triggering. Indeed, the write_date is updated on every record that is handled. Fix is manyfold: - Avoid re-processing what we already check within the last 4 hours/12 hours depending on the type of the document. - The domain takes the *static* create date instead of the write_date to make sure we don't endless re-process the same record and that the window of 7/60 days applies. - Limit the Vendor Bill to be checked only during 7 days after their creation. - Use the create_date in the order of the search to ensure we process older records first, before their time-window closes. - Process the Vendor Bills last, this ensure Customer Invoices will be processed in priority in case we are not able to process everything within the last 4/12 hours. This is still imperfect and a little fragile, we will find a better solution in master, most likely by adding a dedicated field to keep track of the last SAT check. See https://github.com/odoo/enterprise/pull/123213 See https://github.com/odoo/enterprise/pull/103272 task-none Forward-Port-Of: odoo/enterprise#125598 Forward-Port-Of: odoo/enterprise#125317
22 changes
Enhancements to existing features
Australian payroll now applies a superannuation contribution cap in the relevant quarterly earnings rule. This helps businesses stay aligned with ATO requirements for maximum superannuation contributions, including updated super guarantee calculations from 1 July 2026.
Original PR description
Added superannuation limit to the QE rule. Task-6221399 Forward-Port-Of: odoo/enterprise#119792
Bank reconciliation now alerts users when they match a bank statement line to a different partner than the one currently linked to that bank account. This helps users decide whether to move the bank account to the reconciled partner, reducing partner-account mismatches in accounting records.
Original PR description
Add a new notification in the bank reco widget when a user do a reconciliation with a partner different from the one on the st_line. The idea is to let the user chose if he wants to move the bank account from the st_line partner to the move he tries to reconcile. task-6303397 Forward-Port-Of: odoo/enterprise#125702 Forward-Port-Of: odoo/enterprise#120900
Belgian payroll now uses the bicycle reimbursement rates applicable from October 1, 2026. The allowance increases to €0.32 per kilometer and the daily tax-exempt cap rises to €12.80, helping payroll calculations stay aligned with Belgian regulations.
Original PR description
This PR updates the Belgian bicycle reimbursement rates to reflect the amounts applicable from October 1, 2026. ### Changes - Increase the bicycle reimbursement rate from the previous amount to €0.32/km. - Increase the maximum daily tax-exempt reimbursement to €12.80/day. These values are aligned with the latest Belgian regulations and are required for payroll calculations from October 1, 2026. Task-6385742 Forward-Port-Of: odoo/enterprise#124481
Automatic bank reconciliation has been optimized for companies with large accounting histories, reducing a query that could time out to a much faster execution. This helps scheduled reconciliation jobs complete reliably in multi-company databases and reduces delays in accounting operations.
Original PR description
The standard cron process reconciles batches of 100 bank statement lines at a time. In multi-company environments with large historical datasets, this query frequently timed out, exceeding the…
The standard cron process reconciles batches of 100 bank statement lines at a time. In multi-company environments with large historical datasets, this query frequently timed out, exceeding the 15-minute execution limit. Even when throttled to a batch size of 10 records, the query required approximately 18.8 seconds to execute. The performance degradation was driven by the following factors: 1. Suboptimal Lateral Filtering: The statement line batch array filter was placed inside the `LATERAL` block's `WHERE` clause. This prevented the query planner from optimizing the drive path effectively across iterations. 2. Inefficient Join Sequence and Filtering: The original join order scanned `account_move_line` before resolving the company hierarchy constraint. As a result, millions of rows across all companies were retrieved from the index, forcing repeated primary key lookups on `res_company` before ultimately discarding over 99.9% of the records via the late `parent_path` hierarchy filter. This commit addresses these issues by: 1. Moving the `st_line.id` filtering constraint out of the lateral subquery and into the outer main query block to guide the execution path properly. 2. Reordering the inner `LATERAL` subquery to resolve the company hierarchy (`res_company`) prior to joining `account_move_line`. This constraints the scan boundaries early in the pipeline. Performance Benchmarks (10 record batch): - Before Execution Time: ~18,818 ms - Before Shared Hit Blocks: 8,535,272 - After Execution Time: ~130 ms - After Shared Hit Blocks: 67,204 ms Before Plan: https://explain.dalibo.com/plan/h4739gh3519eaa43 After Plan: https://explain.dalibo.com/plan/5ebc5g8deff272gc Forward-Port-Of: odoo/enterprise#125686 Forward-Port-Of: odoo/enterprise#125250
Odoo now limits Chilean company activity selections to the maximum supported by the official electronic document format. This helps prevent electronic invoices and related documents from being rejected because too many activities were selected.
Original PR description
The Chilean XML schema supports maximum of 4 activities (l10n_cl_company_activity_ids), but we allow to add more than that. If this happens, it causes rejections since electronic documents are being sent with more than 4 options selected, and returning rejection errors. Adding constraint to limit l10n_cl_company_activity_ids task-id: 6329320 Forward-Port-Of: odoo/enterprise#123856
Timesheet Assistant suggestions can now be managed with keyboard shortcuts, including moving between rows, selecting items, and selecting ranges. This improves accessibility and helps users review and process timesheet suggestions more efficiently, while avoiding keyboard shortcut interference outside the suggestions list.
Original PR description
Implement full keyboard controls for managing timesheet suggestions to improve accessibility and user efficiency. This adds support for the following interactions: - ArrowUp / ArrowDown to navigate focus through rows - Space to select/deselect the focused item (and set the selection anchor) - Shift + Arrows to select continuous ranges of suggestions task: 6267620 Forward-Port-Of: odoo/enterprise#120437
Resolved issues and error corrections
Malaysia Statement of Account PDFs now calculate total and overdue amounts using the selected statement date. This ensures the totals match the displayed balance lines, improving accuracy for past-date reporting and customer account reviews.
Original PR description
## Current behavior: In Malaysia's Statement of Account, the total and total overdue amounts dont consider the selected Statement Date, and will calculate all the balances up until today in the…
## Current behavior: In Malaysia's Statement of Account, the total and total overdue amounts dont consider the selected Statement Date, and will calculate all the balances up until today in the generated PDF report ## Expected behavior: The total and total overdue amounts should only sum the balances included in the report up until the selected Statement Date ## Steps to reproduce: 1. Install l10n_my_reports module, switch to Malaysian company 2. Go inside Invoicing > Report > Aged receivable 3. Select a specific date in the past 4. Observe that the total amounts dont match with the balance column, and wont change regardless of the date selected ## Cause of the issue: The template used o.total_overdue which ignores the report domain and statement date ## Fix: Accumulate overdue_total in the template loop with the same domain and date_to cutoff as the balance lines, so it always matches the displayed Balance lines for the selected Statement Date opw-6332970 Forward-Port-Of: odoo/enterprise#125410 Forward-Port-Of: odoo/enterprise#123694
Accrual report totals now display correctly when users group purchase and sales accounting records by date. This prevents misleading zero totals in grouped views, helping finance teams review bills to receive and related accrual amounts accurately.
Original PR description
Currently, when users group accrual reports by dates, aggregated computed fields are not calculated correctly. As a result, values such as the total amount may be displayed as zero even when the…
Currently, when users group accrual reports by dates, aggregated computed fields are not calculated correctly. As a result, values such as the total amount may be displayed as zero even when the grouped records contain non-zero amounts.
## Steps to produce:
- Install Purchase and Accounting
- Create a product `Energy drink Sample` with cost 10$
- Create and Confirm a PO with the energy drink sample with vendor as `Administrator`
- Set `Received` as 1.
- Go to Accounting > Review > Bill to Receive
- Search Filter> Remove vendor grouping > Use Custom Group `Order Date`
- Expand the group
## Observed Behavior
Although the grouped lines contain an amount of 10 dollars, the aggregated total displayed in the sum remains zero.
The total should reflect the combined value of the grouped lines.
## Root Cause:
This issue occurs because opening the view triggers `_read_group_for_accrual`, which overrides the `_read_group` method on purchase order lines. The purpose of this override is to support grouping on computed fields that are not stored in the database such as `amount_to_invoice_at_date (Amount)`, `qty_received_at_date (Received)`.
When `_read_group_for_accrual` is executed, it delegates grouping for non-computed fields to the parent `_read_group` implementation, as shown at [1].
The parent method returns results in `res` similar to:
```
[(datetime.datetime(2026, 6, 1, 0, 0), 1.0, 10.0, 1)]
```
During iteration over res at [2], the code uses `group[0]` as the grouping key. In this example, group[0] is `datetime.datetime(2026, 6, 1, 0, 0)`, which represents the granularity date
(e.g., the first day of the month when grouping by month).
However, `records_by_group` is keyed by the actual purchase order line order dates rather than the granularity dates returned by `_read_group`. For example:
```
{datetime.datetime(2026, 6, 12, 0, 0): purchase.order.line(1,)}
```
As a result, the lookup performed using the granularity date (`datetime.datetime(2026, 6, 1, 0, 0))` does not find a matching entry in `records_by_group`. Consequently, records falls back to an empty `purchase.order.line()` recordset.
Later, at [3], the aggregation logic computes totals using this empty recordset. Since the required fields are evaluated on an empty set of records, the aggregated values are computed as zero.
This ultimately causes the method to return a total value of zero
[1]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L9-L24
[2]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L40-L42
[3]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L44-L47
## Solution:
The issue can be resolved by grouping records using the same date granularity specified in the groupby, rather than using the field values.
By aligning the grouping logic with the granularity returned by `_read_group` (for example, grouping by the first day of the month when using monthly grouping ), the keys in `records_by_group` match the values returned in `res`. As a result, the corresponding records are correctly retrieved during aggregation.
This ensures that the aggregation is performed on the appropriate purchase order lines instead of an empty recordset, allowing the computed totals to be calculated correctly.
[opw-6261225](https://www.odoo.com/odoo/project/49/tasks/6261225)
Forward-Port-Of: odoo/enterprise#120369Fixes an issue where the Planning Gantt view could crash when users grouped shifts by role and a role had no assigned resource or working schedule. This keeps planning reports usable for edge cases involving unstaffed or flexible roles.
Original PR description
Currently, an error occurs when grouping planning slots by role. **Steps to Reproduce:** - Install the `Planning` module. - Go to `Planning` > `Configuration` > `Roles`. - Create a `role` without…
Currently, an error occurs when grouping planning slots by role.
**Steps to Reproduce:**
- Install the `Planning` module.
- Go to `Planning` > `Configuration` > `Roles`.
- Create a `role` without assigning any resource to it (or assign a resource without a working time).
- Go to `Planning`, create a new `planning slot`, assign the `role` created above, set the start date to `20/07/2026 12:00 PM` and the end date to `21/07/2026 2:00 AM`.
- Switch to the `Gantt view` of the `planning slots`.
- Group by `Role` and set the custom date range to `07/19/2026 -> 07/20/2026`, then click `Apply`.
`KeyError: 1`
The error occurs when the user groups the planning slots by role in the Gantt view. During
the computation of the Gantt progress bar, if the existing slot's role has no resource, or has
a flexible resource without a calendar, the regular resources become empty [1]. Then it
attempts to compute the valid work intervals for these empty resources [2], resulting in an
empty calendar work interval dictionary ({}) [3]. Later, when computing the duration over the
period with the valid range slots, it tries to access the resource directly from the empty
calendar_intervals dictionary [4], which raises the error.
This commit ensures that, when no work intervals are available for a resource, an empty
work interval is used instead.
[1]- https://github.com/odoo/enterprise/blob/3871b75fb74a7b35bfea9610ca08b853eb723320/planning/models/planning_slot.py#L307
[2]: https://github.com/odoo/odoo/blob/5663509fe5caa1191fafcea3b3879dcef9ceca8f/addons/resource/models/resource_resource.py#L220
[3]: https://github.com/odoo/enterprise/blob/3871b75fb74a7b35bfea9610ca08b853eb723320/planning/models/planning_slot.py#L3415-L3417
[4]- https://github.com/odoo/enterprise/blob/3871b75fb74a7b35bfea9610ca08b853eb723320/planning/models/planning_slot.py#L3300
sentry-7620222164
Forward-Port-Of: odoo/enterprise#124991Odoo now imports Lazada and Shopee orders with discounts, vouchers, coins, and shipping fees in a way that matches the marketplace totals. This reduces reconciliation differences caused by rounding and makes order amounts clearer for sales and accounting teams.
Original PR description
Marketplace orders with discount lines could not match the platform total: Odoo accumulated a small rounding residue vs Shopee's total_amount / Lazada's order price. sale_shopee ----------- Changes:…
Marketplace orders with discount lines could not match the platform total: Odoo accumulated a small rounding residue vs Shopee's total_amount / Lazada's order price. sale_shopee ----------- Changes: - Fetch buyer-side escrow amounts via `_fetch_order_income` and pass them through `self.env.context` (`order_income`). - Build item lines from the buyer-paid item price with `discount=0` and a recomputed tax-exclusive `price_unit`. - Distribute order-level discounts (seller/platform vouchers and coins) as dedicated negative lines per product tax group via `_prepare_discount_lines_values`. - Append a shipping line from `buyer_paid_shipping_fee` with fiscal-position mapped taxes. - Reconcile any leftover residue with `_adjust_order_total` using a single tax-free amount-adjustment line. - Register `default_discount_product` and configure it on upgrade (v1.1). sale_lazada ----------- - Port the same reconciliation model as shopee: reconciled line specs, discount=0 with discounted unit from paid_price, shipping line from shipping_fee, order-level "Discount line" distributed at order-level. task-6112062 Forward-Port-Of: odoo/enterprise#125068 Forward-Port-Of: odoo/enterprise#117561
Users without Planning or Project permissions can now open the product catalog from relevant sales quotations without seeing an access error. The fix ensures the system checks permissions before reading restricted planning or field service information, reducing interruptions in the sales workflow.
Original PR description
A user without Project rights cannot add a product from the catalog on a sale order Steps to reproduce: 1. Install industry_fsm_sale module 2. Go to Settings > Users & Companies > Users and open user Marc Demo 3. Set Field Service and Project rights to No 4. Log in as Marc Demo 5. Go to Sales and open any quotation 6. Click on Catalog in the order lines 7. An access error is raised Issue: industry_fsm_sale overrides `action_add_from_catalog` and tries to read sale.order.tasks_ids but users can't always access this field as it requires Project rights Solution: Check that the user has Project rights before trying to read tasks_ids opw-6315647 Forward-Port-Of: odoo/enterprise#125526 Forward-Port-Of: odoo/enterprise#123228
Mexican payroll now calculates expected work hours even when a payslip has not yet been created. This helps off-cycle payroll runs start with accurate work hour and attendance values, reducing manual corrections and workflow interruptions.
Original PR description
Previously, the `_preprocess_work_hours_data` method would abort early if no payslip was found for the given period. This prevented the correct generation or evaluation of expected Mexican work hours…
Previously, the `_preprocess_work_hours_data` method would abort early if no payslip was found for the given period. This prevented the correct generation or evaluation of expected Mexican work hours in contexts where a draft payslip does not yet exist (i.e., when an off-cycle payslip is initially generated for a given time period). To resolve this, the strict dependency on the payslip record has been removed. It now falls back to the contract version's base data when a payslip is absent: - The calendar defaults to the contract version's `resource_calendar_id`. - The duration is assumed to be standard (`is_wrong_duration = False`). - The Mexican schedule table (`l10n_mx_schedule_table`) is fetched globally from the environment (`hr.rule.parameter`) rather than relying on the payslip-specific helper method. This ensures expected work hours and attendance fields are calculated consistently across all payroll workflows, regardless of whether the payslip has been instantiated. opw-6351402 Forward-Port-Of: odoo/enterprise#125219
The Belgian Partner VAT Listing now always uses the required calendar year period, from January 1 to December 31. This prevents incorrect VAT listing periods for companies whose fiscal year does not match the calendar year, improving compliance and reporting accuracy.
Original PR description
The Belgian Partner VAT Listing must always report on the civil calendar year (01/01/N to 12/31/N). Previously, the report was relying on the company's fiscal year configuration, which caused incorrect reporting periods for companies with non-calendar fiscal years. This commit overrides `_custom_options_initializer` to strictly enforce a civil year date range based on the selected year, entirely ignoring custom fiscal year boundaries. Task-6086513 Forward-Port-Of: odoo/enterprise#125752 Forward-Port-Of: odoo/enterprise#114337
This fix prevents payroll salary rule account settings from being reset when other apps, such as Point of Sale, are installed. Businesses that customize Swiss payroll accounting can now keep their manual debit and credit account choices intact.
Original PR description
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code…
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code 1000) - Install 'POS' - Check the accounts you configured on the salary rules >The accounts are reset to their default values ### Cause of Issue: POS depends on the module `stock_account`. When `stock_account` is installed, the `_configure_journals` method in its `__init__.py` creates a new `account.journal` for inventory valuation. To apply default values to this journal, the method retrieves data from the chart template. https://github.com/odoo/odoo/blob/7d16ef88784e18e885b97dbded3231775f1349d2/addons/stock_account/__init__.py#L41 The `hr_payroll_account` module overrides `_post_load_data` and unconditionally calls `_load_payroll_accounts(template_code, company)`. https://github.com/odoo/enterprise/blob/fcf06997a0eb999af86e4b6917311a86f04a390e/hr_payroll_account/models/account_chart_template.py#L16-L18 This triggers the reinstallation of the default payroll accounts, which overwrites and discards any manual configuration changes the user has made to their salary rules. ### Fix: Ensure that default payroll accounts are only reset during a genuine chart of accounts loading process, and not during localized post-load operations triggered by other modules. opw-6251112 Forward-Port-Of: odoo/enterprise#121687
Manufacturing planning now uses the product's Bill of Materials batch size even when no specific BOM is selected while adding the product to the Master Production Schedule. This helps planners get correct replenishment quantities and avoid under-planning production batches.
Original PR description
In MPS, if a product’s Bill of Materials (BOM) is not specified at the time of addition, the system will not correctly account for batch size. Steps to reproduce: ------------------- * Create a…
In MPS, if a product’s Bill of Materials (BOM) is not specified at the time of addition, the system will not correctly account for batch size. Steps to reproduce: ------------------- * Create a product with a bom that has a batch size of 2 * Open MPS * Add the product - without specifying the bom - Route Manufacture * Add 1 in the Forcast Demand -> the batch size from the bom it's not taken into account. Observation: ------------- When updating mps, it will call get_production_schedule_view_state: https://github.com/odoo/enterprise/blob/7092dd2cc3578c3f22d1fbc82d958d57e2f93553/mrp_mps/models/mrp_mps.py#L424 this function when calculating the quantity to resplenish will call _get_resplenish_qty: https://github.com/odoo/enterprise/blob/7092dd2cc3578c3f22d1fbc82d958d57e2f93553/mrp_mps/models/mrp_mps.py#L534 to know the quantity to resplenish it will need the batch size, in mps they will only consider the batch size from the bom registered: https://github.com/odoo/enterprise/blob/7092dd2cc3578c3f22d1fbc82d958d57e2f93553/mrp_mps/models/mrp_mps.py#L863-L865 Since there is no default value for bom_id, If there is no bom selected, there is no batch size. opw-6259956 Forward-Port-Of: odoo/enterprise#124922 Forward-Port-Of: odoo/enterprise#119560
Belgian payroll calculations now handle payslip corrections that are still in progress, not only those already finalized. This helps prevent incorrect historical payroll totals when correcting employee payslips, reducing the risk of payroll errors.
Original PR description
Currently when computing all_previous_monthly_payslips, if a corrected payslip has a correction in the done state, it will work correctly, but when we are currently correcting, since we are not in the done state yet, the negative payslip is not taken into account. Instead of assuming the correction payslip will be in a done state, we will now filter out all corrected and negative correction payslips task-6397633
This fixes Belgian payroll calculations by updating the 3000 deduction rules for the second and third quarters of 2026. It helps ensure payroll declarations and related accounting tests stay aligned with the expected legal values.
Original PR description
Forward-Port-Of: odoo/enterprise#124348 Forward-Port-Of: odoo/enterprise#124034
This fixes barcode transfer behavior when extra products are not allowed. Users are now prevented from scanning unreserved products after reopening a transfer, while immediate delivery transfers can still use the Add Product button when appropriate.
Original PR description
This [PR] made sure it was not possible to scan unreserved products when `allow_extra_product` was disabled, even when exiting and re-entering a transfer. It worked under the assumption that an immediate transfer always stays in draft, which is wrong for deliveries. The 2nd commit of this PR partly address this issue by allowing the user to add multiple products with the "Add Product" button when the transfer is immediate. While working on this issue, we encountered a bug in the scanning prevention that should have been caught by a tour but was not. This is fixed in the 1st commit. More details in the commit messages. [PR]: https://github.com/odoo/enterprise/pull/123793 Forward-Port-Of: odoo/enterprise#125313
Opening the Scrap option from a new manufacturing barcode operation no longer triggers an error. This improves reliability for warehouse and manufacturing users, including scenarios where consignment products are scanned before the manufacturing order is fully saved.
Original PR description
Versions -------- - 18.0+ Steps ----- 1. Open Barcode app; 2. click Operations; 3. click MANUFACTURING 4. click New; 5. click cogwheel on top right; 6. click Scrap. Issue ----- Traceback: > Error: Record stock.location with id=undefined doesn't exist in the cache Cause ----- When setting up the default context for the scrap menu, it it assumes `this.record` is not empty. Solution -------- Make `cache.getRecord` not raise an error when a location isn't found. Use optional chaining for other parts of the context that rely on a `record` being present. Also fixes a related issue introduced by 4b457fe, where the same traceback would be thrown on opening a new MO and scanning a product whilst consignment is enabled. opw-6397774 Forward-Port-Of: odoo/enterprise#125571 Forward-Port-Of: odoo/enterprise#124818
Swedish SIE4 general ledger exports now use the actual fiscal year dates selected by the company, including non-standard fiscal years. This prevents mismatches between the dates declared in the export and the accounting data included, improving reliability for reporting and compliance.
Original PR description
## Issue: Exporting the general ledger as SIE4 set the duration of fiscal years to one year from the starting date of the fiscal year. ## Steps to reproduce: - Create a fiscal year A of one month for…
## Issue: Exporting the general ledger as SIE4 set the duration of fiscal years to one year from the starting date of the fiscal year. ## Steps to reproduce: - Create a fiscal year A of one month for year X-1 (December 1st to December31th year X-1) - Create a fiscal year B of 1 year and 1 month (January 1st Year X to January 31th year X+1) - Create Invoices in November year X-1, December year X-1, year X and in January year X+1 and confirm them - Go to General Leder - Set date to the fiscal year B - Export as SIE4 ### Current behavior: - **for previous fiscal year** - declared fiscal year (#RAR field) goes : - from fiscal year X date_from -1 year - to fiscal year X date_to -1 year - However data are computed: - from fiscal year X date_from -1 year - to fiscal year X date_from -1 day - **for current fiscal year** - declared fiscal year (#RAR field) goes : - from fiscal year X date_from - to fiscal year X date_to - However data are computed: - from fiscal year X date_from - to fiscal year X date_from +1 year ### Expected behavior: Declared fiscal year match the one that is use for computation. - for previous fiscal year - from fiscal year X-1 date_from - to fiscal year X date_from -1 day - for current year - from fiscal year X date_from - to fiscal year X date_to Cause: Current year length was wrong because it [relied on](https://github.com/odoo/enterprise/blob/22b4100006ec24bf6e4b64042cbfdba360fcf470/l10n_se_sie4_export/models/account_general_ledger.py#L139-L140) the next_date_from which was wrong. opw-6264766 Forward-Port-Of: odoo/enterprise#125354
This fix prevents Mexican electronic invoice status checks from repeatedly triggering on the same documents. It reduces unnecessary background processing and prioritizes customer invoices so important checks are handled within the required time windows.
Original PR description
While trying to fix the SAT cron, we did not think it through it could cause infinite cron triggering. Indeed, the write_date is updated on every record that is handled. Fix is manyfold: - Avoid…
While trying to fix the SAT cron, we did not think it through it could cause infinite cron triggering. Indeed, the write_date is updated on every record that is handled. Fix is manyfold: - Avoid re-processing what we already check within the last 4 hours/12 hours depending on the type of the document. - The domain takes the *static* create date instead of the write_date to make sure we don't endless re-process the same record and that the window of 7/60 days applies. - Limit the Vendor Bill to be checked only during 7 days after their creation. - Use the create_date in the order of the search to ensure we process older records first, before their time-window closes. - Process the Vendor Bills last, this ensure Customer Invoices will be processed in priority in case we are not able to process everything within the last 4/12 hours. This is still imperfect and a little fragile, we will find a better solution in master, most likely by adding a dedicated field to keep track of the last SAT check. See https://github.com/odoo/enterprise/pull/123213 See https://github.com/odoo/enterprise/pull/103272 task-none Forward-Port-Of: odoo/enterprise#125598 Forward-Port-Of: odoo/enterprise#125317
This fix changes US payroll localization so it is installed through the standard automatic installation process instead of a later setup hook. This helps avoid upgrade issues where payroll localization modules could be temporarily treated as not installed, improving reliability for US companies using payroll.
Original PR description
In a [previous PR], a test was introduced to reject any `l10n_xx_hr_*` module that has a `countries` key in its manifest and depends on a module that also has that country key. This test was too…
In a [previous PR], a test was introduced to reject any `l10n_xx_hr_*` module that has a `countries` key in its manifest and depends on a module that also has that country key. This test was too broad and rejected some valid cases: 1. A non-auto-install module can have a country key defined to add flags in the apps kanban view. ([src]) 2. A module that has a country-specific regular dependency, but not as an auto-install condition. The second case is illustrated by [l10n_us_hr_payroll], which should auto-install when `hr_payroll` is installed and a US company exists. With the old test, achieving this required adding `l10n_us` to its auto-install dependencies. But, since `l10n_us` is not auto-installable, `l10n_us_hr_payroll` would not be installed if you create a DB with a US company and only install `hr_payroll`. In practice, that module was still being installed via a [post-init hook] in `hr_payroll`. This hook was installing all `l10n_XX_hr_payroll` modules for each country where a company is located, which is the behavior of the `countries` parameter in the manifest. This caused issues during upgrades as this runs late in the process: after the auto-discovery phase. Modules installed by this hook would be considered as `uninstalled` until `hr_payroll` is loaded. This commit narrows the check to only fail if: - A module has a `country` key in its manifest, and - It has a country-specific module in its **auto-install** dependencies. Moreover, it modifies `l10n_us_hr_payroll` to correctly rely on the auto_install mechanism instead of the post-init hook. [previous PR]: https://github.com/odoo/enterprise/pull/101843 [src]: https://github.com/odoo/odoo/blob/6df9f92a537aa4bb4ee5dc946fe31c4e56e6dfea/odoo/addons/base/models/ir_module.py#L271-L273 [l10n_us_hr_payroll]: https://github.com/odoo/enterprise/blob/24a33ffb769557be498d61328522bb77f68d3a5a/l10n_us_hr_payroll/__manifest__.py [post-init hook]: https://github.com/odoo/enterprise/blob/85185595cfd1ee5310ceb9dc80c0589accad2f19/hr_payroll/__init__.py#L21 Forward-Port-Of: odoo/enterprise#125853 Forward-Port-Of: odoo/enterprise#120279
14 changes
Enhancements to existing features
Australian payroll now applies the required superannuation contribution cap in payslip calculations. This helps businesses stay compliant with ATO rules, including updated super guarantee calculation requirements from 1 July 2026.
Original PR description
Added superannuation limit to the QE rule. Task-6221399 Forward-Port-Of: odoo/enterprise#119792
Pakistan payroll rules have been updated to use the latest 2026 income tax brackets. The separate extra tax surcharge mechanism has been removed, helping payroll calculations stay aligned with current local tax requirements.
Original PR description
[IMP] l10n_pk_hr_payroll: update 2026 tax brackets . tax brackets are updated . extra tax surcharge mechanism is deleted task-6401729 Forward-Port-Of: odoo/enterprise#124988
Bank statement reconciliation has been optimized to avoid timeouts in large multi-company environments. This helps scheduled accounting processes complete much faster and more reliably, reducing delays in financial operations.
Original PR description
The standard cron process reconciles batches of 100 bank statement lines at a time. In multi-company environments with large historical datasets, this query frequently timed out, exceeding the…
The standard cron process reconciles batches of 100 bank statement lines at a time. In multi-company environments with large historical datasets, this query frequently timed out, exceeding the 15-minute execution limit. Even when throttled to a batch size of 10 records, the query required approximately 18.8 seconds to execute. The performance degradation was driven by the following factors: 1. Suboptimal Lateral Filtering: The statement line batch array filter was placed inside the `LATERAL` block's `WHERE` clause. This prevented the query planner from optimizing the drive path effectively across iterations. 2. Inefficient Join Sequence and Filtering: The original join order scanned `account_move_line` before resolving the company hierarchy constraint. As a result, millions of rows across all companies were retrieved from the index, forcing repeated primary key lookups on `res_company` before ultimately discarding over 99.9% of the records via the late `parent_path` hierarchy filter. This commit addresses these issues by: 1. Moving the `st_line.id` filtering constraint out of the lateral subquery and into the outer main query block to guide the execution path properly. 2. Reordering the inner `LATERAL` subquery to resolve the company hierarchy (`res_company`) prior to joining `account_move_line`. This constraints the scan boundaries early in the pipeline. Performance Benchmarks (10 record batch): - Before Execution Time: ~18,818 ms - Before Shared Hit Blocks: 8,535,272 - After Execution Time: ~130 ms - After Shared Hit Blocks: 67,204 ms Before Plan: https://explain.dalibo.com/plan/h4739gh3519eaa43 After Plan: https://explain.dalibo.com/plan/5ebc5g8deff272gc Forward-Port-Of: odoo/enterprise#125686 Forward-Port-Of: odoo/enterprise#125250
The Chilean electronic invoicing flow now limits companies to the maximum four business activities accepted by the official XML schema. This helps prevent electronic documents from being rejected because too many activities were selected.
Original PR description
The Chilean XML schema supports maximum of 4 activities (l10n_cl_company_activity_ids), but we allow to add more than that. If this happens, it causes rejections since electronic documents are being sent with more than 4 options selected, and returning rejection errors. Adding constraint to limit l10n_cl_company_activity_ids task-id: 6329320 Forward-Port-Of: odoo/enterprise#123856
Resolved issues and error corrections
The Malaysia Statement of Account report now calculates total and overdue amounts using the selected statement date. This ensures the PDF totals match the displayed balance lines, improving accuracy for past-date customer account reporting.
Original PR description
## Current behavior: In Malaysia's Statement of Account, the total and total overdue amounts dont consider the selected Statement Date, and will calculate all the balances up until today in the…
## Current behavior: In Malaysia's Statement of Account, the total and total overdue amounts dont consider the selected Statement Date, and will calculate all the balances up until today in the generated PDF report ## Expected behavior: The total and total overdue amounts should only sum the balances included in the report up until the selected Statement Date ## Steps to reproduce: 1. Install l10n_my_reports module, switch to Malaysian company 2. Go inside Invoicing > Report > Aged receivable 3. Select a specific date in the past 4. Observe that the total amounts dont match with the balance column, and wont change regardless of the date selected ## Cause of the issue: The template used o.total_overdue which ignores the report domain and statement date ## Fix: Accumulate overdue_total in the template loop with the same domain and date_to cutoff as the balance lines, so it always matches the displayed Balance lines for the selected Statement Date opw-6332970 Forward-Port-Of: odoo/enterprise#125410 Forward-Port-Of: odoo/enterprise#123694
Accrual report totals now show the correct combined values when users group purchase or sales-related accounting entries by dates. This prevents misleading zero totals in grouped views, helping finance teams review bills to receive and related reports accurately.
Original PR description
Currently, when users group accrual reports by dates, aggregated computed fields are not calculated correctly. As a result, values such as the total amount may be displayed as zero even when the…
Currently, when users group accrual reports by dates, aggregated computed fields are not calculated correctly. As a result, values such as the total amount may be displayed as zero even when the grouped records contain non-zero amounts.
## Steps to produce:
- Install Purchase and Accounting
- Create a product `Energy drink Sample` with cost 10$
- Create and Confirm a PO with the energy drink sample with vendor as `Administrator`
- Set `Received` as 1.
- Go to Accounting > Review > Bill to Receive
- Search Filter> Remove vendor grouping > Use Custom Group `Order Date`
- Expand the group
## Observed Behavior
Although the grouped lines contain an amount of 10 dollars, the aggregated total displayed in the sum remains zero.
The total should reflect the combined value of the grouped lines.
## Root Cause:
This issue occurs because opening the view triggers `_read_group_for_accrual`, which overrides the `_read_group` method on purchase order lines. The purpose of this override is to support grouping on computed fields that are not stored in the database such as `amount_to_invoice_at_date (Amount)`, `qty_received_at_date (Received)`.
When `_read_group_for_accrual` is executed, it delegates grouping for non-computed fields to the parent `_read_group` implementation, as shown at [1].
The parent method returns results in `res` similar to:
```
[(datetime.datetime(2026, 6, 1, 0, 0), 1.0, 10.0, 1)]
```
During iteration over res at [2], the code uses `group[0]` as the grouping key. In this example, group[0] is `datetime.datetime(2026, 6, 1, 0, 0)`, which represents the granularity date
(e.g., the first day of the month when grouping by month).
However, `records_by_group` is keyed by the actual purchase order line order dates rather than the granularity dates returned by `_read_group`. For example:
```
{datetime.datetime(2026, 6, 12, 0, 0): purchase.order.line(1,)}
```
As a result, the lookup performed using the granularity date (`datetime.datetime(2026, 6, 1, 0, 0))` does not find a matching entry in `records_by_group`. Consequently, records falls back to an empty `purchase.order.line()` recordset.
Later, at [3], the aggregation logic computes totals using this empty recordset. Since the required fields are evaluated on an empty set of records, the aggregated values are computed as zero.
This ultimately causes the method to return a total value of zero
[1]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L9-L24
[2]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L40-L42
[3]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L44-L47
## Solution:
The issue can be resolved by grouping records using the same date granularity specified in the groupby, rather than using the field values.
By aligning the grouping logic with the granularity returned by `_read_group` (for example, grouping by the first day of the month when using monthly grouping ), the keys in `records_by_group` match the values returned in `res`. As a result, the corresponding records are correctly retrieved during aggregation.
This ensures that the aggregation is performed on the appropriate purchase order lines instead of an empty recordset, allowing the computed totals to be calculated correctly.
[opw-6261225](https://www.odoo.com/odoo/project/49/tasks/6261225)
Forward-Port-Of: odoo/enterprise#120369This change ensures US payroll localization installs at the right point when payroll is enabled for a US company. It removes reliance on a late setup step that could confuse upgrade processing and makes the module discovery rules more accurate.
Original PR description
In a [previous PR], a test was introduced to reject any `l10n_xx_hr_*` module that has a `countries` key in its manifest and depends on a module that also has that country key. This test was too…
In a [previous PR], a test was introduced to reject any `l10n_xx_hr_*` module that has a `countries` key in its manifest and depends on a module that also has that country key. This test was too broad and rejected some valid cases: 1. A non-auto-install module can have a country key defined to add flags in the apps kanban view. ([src]) 2. A module that has a country-specific regular dependency, but not as an auto-install condition. The second case is illustrated by [l10n_us_hr_payroll], which should auto-install when `hr_payroll` is installed and a US company exists. With the old test, achieving this required adding `l10n_us` to its auto-install dependencies. But, since `l10n_us` is not auto-installable, `l10n_us_hr_payroll` would not be installed if you create a DB with a US company and only install `hr_payroll`. In practice, that module was still being installed via a [post-init hook] in `hr_payroll`. This hook was installing all `l10n_XX_hr_payroll` modules for each country where a company is located, which is the behavior of the `countries` parameter in the manifest. This caused issues during upgrades as this runs late in the process: after the auto-discovery phase. Modules installed by this hook would be considered as `uninstalled` until `hr_payroll` is loaded. This commit narrows the check to only fail if: - A module has a `country` key in its manifest, and - It has a country-specific module in its **auto-install** dependencies. Moreover, it modifies `l10n_us_hr_payroll` to correctly rely on the auto_install mechanism instead of the post-init hook. [previous PR]: https://github.com/odoo/enterprise/pull/101843 [src]: https://github.com/odoo/odoo/blob/6df9f92a537aa4bb4ee5dc946fe31c4e56e6dfea/odoo/addons/base/models/ir_module.py#L271-L273 [l10n_us_hr_payroll]: https://github.com/odoo/enterprise/blob/24a33ffb769557be498d61328522bb77f68d3a5a/l10n_us_hr_payroll/__manifest__.py [post-init hook]: https://github.com/odoo/enterprise/blob/85185595cfd1ee5310ceb9dc80c0589accad2f19/hr_payroll/__init__.py#L21 Forward-Port-Of: odoo/enterprise#120279
Fixes an issue where deleting a quality check in the middle of a manufacturing work order could cause following checks to disappear from the shop floor view. The remaining checks now stay properly connected, helping operators continue quality control without missing required steps.
Original PR description
Steps to reproduce the bug: - Create a BOM for product P1 with one work order WO1 - Create 3 quality points linked to WO1 via the `operation_id` field - Confirm a manufacturing order for P1: - 3…
Steps to reproduce the bug:
- Create a BOM for product P1 with one work order WO1
- Create 3 quality points linked to WO1 via the `operation_id` field
- Confirm a manufacturing order for P1:
- 3 quality checks A → B → C are generated
- Open the shop floor for the work order:
- Observe that all 3 quality checks are displayed
- Delete quality check B (the middle one)
- come back to the shop floor for the work order:
- Observe that quality check C is no longer displayed in the shop floor
Problem:
After deleting check B, check C disappeared from the shop floor. Quality checks are stored as a doubly-linked list via the `next_check_id` and `previous_check_id` fields on `quality.check`. The shop floor JS (`mrp_display_record.js`) traverses this list starting from the check with no `previous_check_id`, then follows `next_check_id` until the chain ends. When check B was deleted, it nullified the FK references pointing to it, leaving check A with `next_check_id = False` and check C with `previous_check_id = False`. The traversal from A therefore stopped immediately, and C was never reached.
No `unlink` override existed on `quality.check` to repair the chain before deletion.
Solution:
Added an `unlink` override that, before deleting each check, reconnects its predecessor and successor: if the deleted check has both a previous and a next, `prev.next_check_id` is set to `next` and `next.previous_check_id` is set to `prev`, preserving a valid chain for the remaining checks.
opw-6369298
Forward-Port-Of: odoo/enterprise#124118Manual payroll accounting choices are no longer overwritten when installing POS or related inventory accounting features. This protects companies using Swiss payroll from losing customized salary rule account settings after adding new apps.
Original PR description
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code…
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code 1000) - Install 'POS' - Check the accounts you configured on the salary rules >The accounts are reset to their default values ### Cause of Issue: POS depends on the module `stock_account`. When `stock_account` is installed, the `_configure_journals` method in its `__init__.py` creates a new `account.journal` for inventory valuation. To apply default values to this journal, the method retrieves data from the chart template. https://github.com/odoo/odoo/blob/7d16ef88784e18e885b97dbded3231775f1349d2/addons/stock_account/__init__.py#L41 The `hr_payroll_account` module overrides `_post_load_data` and unconditionally calls `_load_payroll_accounts(template_code, company)`. https://github.com/odoo/enterprise/blob/fcf06997a0eb999af86e4b6917311a86f04a390e/hr_payroll_account/models/account_chart_template.py#L16-L18 This triggers the reinstallation of the default payroll accounts, which overwrites and discards any manual configuration changes the user has made to their salary rules. ### Fix: Ensure that default payroll accounts are only reset during a genuine chart of accounts loading process, and not during localized post-load operations triggered by other modules. opw-6251112 Forward-Port-Of: odoo/enterprise#121687
This fix prevents users from scanning products that were not reserved when extra products are not allowed, even after leaving and reopening a transfer. It also keeps the Add Product option available for immediate delivery transfers, avoiding blocked warehouse workflows.
Original PR description
This [PR] made sure it was not possible to scan unreserved products when `allow_extra_product` was disabled, even when exiting and re-entering a transfer. It worked under the assumption that an immediate transfer always stays in draft, which is wrong for deliveries. The 2nd commit of this PR partly address this issue by allowing the user to add multiple products with the "Add Product" button when the transfer is immediate. While working on this issue, we encountered a bug in the scanning prevention that should have been caught by a tour but was not. This is fixed in the 1st commit. More details in the commit messages. [PR]: https://github.com/odoo/enterprise/pull/123793 Forward-Port-Of: odoo/enterprise#125313
The Swedish SIE4 general ledger export now uses the actual configured fiscal year dates instead of assuming every fiscal year is exactly one year long. This prevents mismatches in exported period declarations and accounting data for companies with shortened or extended fiscal years.
Original PR description
## Issue: Exporting the general ledger as SIE4 set the duration of fiscal years to one year from the starting date of the fiscal year. ## Steps to reproduce: - Create a fiscal year A of one month for…
## Issue: Exporting the general ledger as SIE4 set the duration of fiscal years to one year from the starting date of the fiscal year. ## Steps to reproduce: - Create a fiscal year A of one month for year X-1 (December 1st to December31th year X-1) - Create a fiscal year B of 1 year and 1 month (January 1st Year X to January 31th year X+1) - Create Invoices in November year X-1, December year X-1, year X and in January year X+1 and confirm them - Go to General Leder - Set date to the fiscal year B - Export as SIE4 ### Current behavior: - **for previous fiscal year** - declared fiscal year (#RAR field) goes : - from fiscal year X date_from -1 year - to fiscal year X date_to -1 year - However data are computed: - from fiscal year X date_from -1 year - to fiscal year X date_from -1 day - **for current fiscal year** - declared fiscal year (#RAR field) goes : - from fiscal year X date_from - to fiscal year X date_to - However data are computed: - from fiscal year X date_from - to fiscal year X date_from +1 year ### Expected behavior: Declared fiscal year match the one that is use for computation. - for previous fiscal year - from fiscal year X-1 date_from - to fiscal year X date_from -1 day - for current year - from fiscal year X date_from - to fiscal year X date_to Cause: Current year length was wrong because it [relied on](https://github.com/odoo/enterprise/blob/22b4100006ec24bf6e4b64042cbfdba360fcf470/l10n_se_sie4_export/models/account_general_ledger.py#L139-L140) the next_date_from which was wrong. opw-6264766 Forward-Port-Of: odoo/enterprise#125354
Sendcloud shipments can now handle products shipped in fractional quantities, such as 0.5 kg of an item sold by kilogram. This prevents rejected international shipments by sending item descriptions and weights that match the actual delivered quantity.
Original PR description
Issue ----- International deliveries for fractions of items (eg 500g of a product sold by kg) are rejected by Sendcloud. Steps to reproduce ----- - Setup Sendcloud - Fedex international - Create a…
Issue ----- International deliveries for fractions of items (eg 500g of a product sold by kg) are rejected by Sendcloud. Steps to reproduce ----- - Setup Sendcloud - Fedex international - Create a product - weight: 1kg - valid hs code - Create a contact (outside EU if the company is in EU) - Deliver 0.5 of the product to the contact > Error "... parcel not returned from Sendcloud" Cause ----- Sendcloud returns he folloing error: > "The total weight for declared items exceeds the total weight set for the shipment." This is because the `weight` set on the shipment https://github.com/odoo/enterprise/blob/99a13453cac8a7a4677cef3df8896329c90c9e99/delivery_sendcloud/models/sendcloud_service.py#L449-L464 corresponds to the weight of the package, whereas the weight set on the description of the product in `parcel_items` corresponds to the weight of one "full" unit of the product. https://github.com/odoo/enterprise/blob/99a13453cac8a7a4677cef3df8896329c90c9e99/delivery_sendcloud/models/sendcloud_service.py#L327-L335 We cannot change the quantity in `parcel_items` to match the actual delivered one because the field should be an integer. https://sendcloud.dev/api/v2/parcels/create-a-parcel-or-parcels#body-one-of-0-parcel-parcel-items-items-quantity The price is also off, because it gets taken from the `move_line`, so it reflects the price of the actual quantity and not a "full" item. https://github.com/odoo/odoo/blob/208a8a6a5adb8ec1f2710453c2ee3b54c55a9f1e/addons/stock_delivery/models/delivery_carrier.py#L235 Note that this issue is common to **all UoM types**. Solution ----- Since we cannot change the quantity, we can instead adapt the description and weight sent in `parcel_items`. For example, sending 300g of sugar, we would send - description: "Sugar (0.3 kg)" - weight: "0.300" ----- Ticket: opw-6346330 Forward-Port-Of: odoo/enterprise#124686
The Chilean electronic invoicing email process now handles incoming customer claim documents that are missing recipient tax details without crashing. This prevents one malformed message from repeatedly blocking mailbox processing for the company.
Original PR description
### Problem `Mail: Fetchmail Service` cron aborts with `AttributeError: 'NoneType' object has no attribute 'upper'` in `l10n_cl_edi/models/fetchmail_server.py::_process_incoming_customer_claim` when…
### Problem
`Mail: Fetchmail Service` cron aborts with `AttributeError: 'NoneType' object has no attribute 'upper'` in `l10n_cl_edi/models/fetchmail_server.py::_process_incoming_customer_claim` when an incoming customer claim DTE has no `<RUTRecep>`:
```python
dte.findtext('.//ns0:RUTRecep', ...).upper() or
dte.findtext('.//ns0:RutReceptor', ...).upper()
```
`findtext()` returns `None` when the tag is missing, so `.upper()` blows up before the `or` fallback can run. Once the cron hits such a message it re-crashes on every subsequent run and blocks the whole mailbox until the offending mail is deleted.
### Fix
Guard each `findtext(...)` with `or ''` so the `or` chain actually falls through. Empty `partner_vat` is already handled by the existing "Partner … has not been found" branch a few lines below.
### Traceback (Odoo 19)
```
File "/mnt/extra-addons/enterprise/l10n_cl_edi/models/fetchmail_server.py", line 285, in _process_incoming_customer_claim
dte.findtext('.//ns0:RUTRecep', namespaces=XML_NAMESPACES).upper() or
AttributeError: 'NoneType' object has no attribute 'upper'
```
### Ticket
No ticket open for this but opw-5257481 is related.
Forward-Port-Of: odoo/enterprise#123387This fix prevents the automated Mexican electronic invoicing status check from repeatedly reprocessing the same documents. It helps reduce unnecessary system load and prioritizes customer invoices while keeping vendor bill checks within the required time window.
Original PR description
While trying to fix the SAT cron, we did not think it through it could cause infinite cron triggering. Indeed, the write_date is updated on every record that is handled. Fix is manyfold: - Avoid…
While trying to fix the SAT cron, we did not think it through it could cause infinite cron triggering. Indeed, the write_date is updated on every record that is handled. Fix is manyfold: - Avoid re-processing what we already check within the last 4 hours/12 hours depending on the type of the document. - The domain takes the *static* create date instead of the write_date to make sure we don't endless re-process the same record and that the window of 7/60 days applies. - Limit the Vendor Bill to be checked only during 7 days after their creation. - Use the create_date in the order of the search to ensure we process older records first, before their time-window closes. - Process the Vendor Bills last, this ensure Customer Invoices will be processed in priority in case we are not able to process everything within the last 4/12 hours. This is still imperfect and a little fragile, we will find a better solution in master, most likely by adding a dedicated field to keep track of the last SAT check. See https://github.com/odoo/enterprise/pull/123213 See https://github.com/odoo/enterprise/pull/103272 task-none Forward-Port-Of: odoo/enterprise#125598 Forward-Port-Of: odoo/enterprise#125317
12 changes
Enhancements to existing features
Bank statement reconciliation has been optimized to avoid timeouts in multi-company setups with large transaction histories. This should make automated reconciliation jobs run much faster and more reliably, reducing delays in accounting operations.
Original PR description
The standard cron process reconciles batches of 100 bank statement lines at a time. In multi-company environments with large historical datasets, this query frequently timed out, exceeding the…
The standard cron process reconciles batches of 100 bank statement lines at a time. In multi-company environments with large historical datasets, this query frequently timed out, exceeding the 15-minute execution limit. Even when throttled to a batch size of 10 records, the query required approximately 18.8 seconds to execute. The performance degradation was driven by the following factors: 1. Suboptimal Lateral Filtering: The statement line batch array filter was placed inside the `LATERAL` block's `WHERE` clause. This prevented the query planner from optimizing the drive path effectively across iterations. 2. Inefficient Join Sequence and Filtering: The original join order scanned `account_move_line` before resolving the company hierarchy constraint. As a result, millions of rows across all companies were retrieved from the index, forcing repeated primary key lookups on `res_company` before ultimately discarding over 99.9% of the records via the late `parent_path` hierarchy filter. This commit addresses these issues by: 1. Moving the `st_line.id` filtering constraint out of the lateral subquery and into the outer main query block to guide the execution path properly. 2. Reordering the inner `LATERAL` subquery to resolve the company hierarchy (`res_company`) prior to joining `account_move_line`. This constraints the scan boundaries early in the pipeline. Performance Benchmarks (10 record batch): - Before Execution Time: ~18,818 ms - Before Shared Hit Blocks: 8,535,272 - After Execution Time: ~130 ms - After Shared Hit Blocks: 67,204 ms Before Plan: https://explain.dalibo.com/plan/h4739gh3519eaa43 After Plan: https://explain.dalibo.com/plan/5ebc5g8deff272gc Forward-Port-Of: odoo/enterprise#125250
Australian payroll now applies a superannuation contribution cap in the relevant salary rule. This helps businesses stay aligned with Australian Taxation Office requirements for maximum superannuation contributions from July 2026.
Original PR description
Added superannuation limit to the QE rule. Task-6221399 Forward-Port-Of: odoo/enterprise#119792
Limits Chilean company activity selections to the maximum allowed by the official electronic document format. This helps prevent rejected electronic documents caused by too many activities being included.
Original PR description
The Chilean XML schema supports maximum of 4 activities (l10n_cl_company_activity_ids), but we allow to add more than that. If this happens, it causes rejections since electronic documents are being sent with more than 4 options selected, and returning rejection errors. Adding constraint to limit l10n_cl_company_activity_ids task-id: 6329320 Forward-Port-Of: odoo/enterprise#123856
Resolved issues and error corrections
Fixed an issue where accrual reports could show zero totals when users grouped purchase or sales accounting lines by date. The reports now calculate grouped amounts using the correct date period, so totals match the underlying records and give finance teams reliable figures.
Original PR description
Currently, when users group accrual reports by dates, aggregated computed fields are not calculated correctly. As a result, values such as the total amount may be displayed as zero even when the…
Currently, when users group accrual reports by dates, aggregated computed fields are not calculated correctly. As a result, values such as the total amount may be displayed as zero even when the grouped records contain non-zero amounts.
## Steps to produce:
- Install Purchase and Accounting
- Create a product `Energy drink Sample` with cost 10$
- Create and Confirm a PO with the energy drink sample with vendor as `Administrator`
- Set `Received` as 1.
- Go to Accounting > Review > Bill to Receive
- Search Filter> Remove vendor grouping > Use Custom Group `Order Date`
- Expand the group
## Observed Behavior
Although the grouped lines contain an amount of 10 dollars, the aggregated total displayed in the sum remains zero.
The total should reflect the combined value of the grouped lines.
## Root Cause:
This issue occurs because opening the view triggers `_read_group_for_accrual`, which overrides the `_read_group` method on purchase order lines. The purpose of this override is to support grouping on computed fields that are not stored in the database such as `amount_to_invoice_at_date (Amount)`, `qty_received_at_date (Received)`.
When `_read_group_for_accrual` is executed, it delegates grouping for non-computed fields to the parent `_read_group` implementation, as shown at [1].
The parent method returns results in `res` similar to:
```
[(datetime.datetime(2026, 6, 1, 0, 0), 1.0, 10.0, 1)]
```
During iteration over res at [2], the code uses `group[0]` as the grouping key. In this example, group[0] is `datetime.datetime(2026, 6, 1, 0, 0)`, which represents the granularity date
(e.g., the first day of the month when grouping by month).
However, `records_by_group` is keyed by the actual purchase order line order dates rather than the granularity dates returned by `_read_group`. For example:
```
{datetime.datetime(2026, 6, 12, 0, 0): purchase.order.line(1,)}
```
As a result, the lookup performed using the granularity date (`datetime.datetime(2026, 6, 1, 0, 0))` does not find a matching entry in `records_by_group`. Consequently, records falls back to an empty `purchase.order.line()` recordset.
Later, at [3], the aggregation logic computes totals using this empty recordset. Since the required fields are evaluated on an empty set of records, the aggregated values are computed as zero.
This ultimately causes the method to return a total value of zero
[1]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L9-L24
[2]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L40-L42
[3]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L44-L47
## Solution:
The issue can be resolved by grouping records using the same date granularity specified in the groupby, rather than using the field values.
By aligning the grouping logic with the granularity returned by `_read_group` (for example, grouping by the first day of the month when using monthly grouping ), the keys in `records_by_group` match the values returned in `res`. As a result, the corresponding records are correctly retrieved during aggregation.
This ensures that the aggregation is performed on the appropriate purchase order lines instead of an empty recordset, allowing the computed totals to be calculated correctly.
[opw-6261225](https://www.odoo.com/odoo/project/49/tasks/6261225)
Forward-Port-Of: odoo/enterprise#120369International Sendcloud shipments containing less than one unit of a product, such as 0.5 kg, are now described with the correct fractional weight and value. This prevents valid deliveries from being rejected because declared item weights appear higher than the parcel weight.
Original PR description
Issue ----- International deliveries for fractions of items (eg 500g of a product sold by kg) are rejected by Sendcloud. Steps to reproduce ----- - Setup Sendcloud - Fedex international - Create a…
Issue ----- International deliveries for fractions of items (eg 500g of a product sold by kg) are rejected by Sendcloud. Steps to reproduce ----- - Setup Sendcloud - Fedex international - Create a product - weight: 1kg - valid hs code - Create a contact (outside EU if the company is in EU) - Deliver 0.5 of the product to the contact > Error "... parcel not returned from Sendcloud" Cause ----- Sendcloud returns he folloing error: > "The total weight for declared items exceeds the total weight set for the shipment." This is because the `weight` set on the shipment https://github.com/odoo/enterprise/blob/99a13453cac8a7a4677cef3df8896329c90c9e99/delivery_sendcloud/models/sendcloud_service.py#L449-L464 corresponds to the weight of the package, whereas the weight set on the description of the product in `parcel_items` corresponds to the weight of one "full" unit of the product. https://github.com/odoo/enterprise/blob/99a13453cac8a7a4677cef3df8896329c90c9e99/delivery_sendcloud/models/sendcloud_service.py#L327-L335 We cannot change the quantity in `parcel_items` to match the actual delivered one because the field should be an integer. https://sendcloud.dev/api/v2/parcels/create-a-parcel-or-parcels#body-one-of-0-parcel-parcel-items-items-quantity The price is also off, because it gets taken from the `move_line`, so it reflects the price of the actual quantity and not a "full" item. https://github.com/odoo/odoo/blob/208a8a6a5adb8ec1f2710453c2ee3b54c55a9f1e/addons/stock_delivery/models/delivery_carrier.py#L235 Note that this issue is common to **all UoM types**. Solution ----- Since we cannot change the quantity, we can instead adapt the description and weight sent in `parcel_items`. For example, sending 300g of sugar, we would send - description: "Sugar (0.3 kg)" - weight: "0.300" ----- Ticket: opw-6346330 Forward-Port-Of: odoo/enterprise#124686
This fixes how Mexican electronic payment documents calculate invoice balances when foreign-currency payments create exchange-rate adjustments. Businesses will see correct paid and remaining amounts on payment CFDI documents, especially when credit notes and exchange differences are involved.
Original PR description
### Issue: Attributes of DoctoRelacionado node in the XML display an incorrect value because the exchange rate difference entry distorts the calculation, even though the invoice was fully settled.…
### Issue: Attributes of DoctoRelacionado node in the XML display an incorrect value because the exchange rate difference entry distorts the calculation, even though the invoice was fully settled. https://drive.google.com/file/d/1ntQny0o8bkkfYtY5ZNBK0Yq7Rq0I-yfz/view ### Fix: Sorting partials by "not exchange_move_id" first broke the chronological order whenever the invoice/payment partial itself carried an exchange difference (e.g. a foreign currency payment settled at another rate). This made the residual-chain algorithm consume the credit note's "other_residual" on the wrong payment, so ImpSaldoAnt/ImpPagado/ ImpSaldoInsoluto in the payment CFDI's DoctoRelacionado stayed wrong even though the invoice was fully paid. Populate the exchange move mapping in a separate first pass and sort the partials purely by date/id, so credit notes are always deducted from the correct payment. task-id:[6363092](https://www.odoo.com/odoo/project/49/tasks/6363092) Forward-Port-Of: odoo/enterprise#124882
Manual payroll account settings are no longer overwritten when installing related apps such as Point of Sale. This protects customer payroll configuration and avoids unexpected rework after adding new modules.
Original PR description
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code…
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code 1000) - Install 'POS' - Check the accounts you configured on the salary rules >The accounts are reset to their default values ### Cause of Issue: POS depends on the module `stock_account`. When `stock_account` is installed, the `_configure_journals` method in its `__init__.py` creates a new `account.journal` for inventory valuation. To apply default values to this journal, the method retrieves data from the chart template. https://github.com/odoo/odoo/blob/7d16ef88784e18e885b97dbded3231775f1349d2/addons/stock_account/__init__.py#L41 The `hr_payroll_account` module overrides `_post_load_data` and unconditionally calls `_load_payroll_accounts(template_code, company)`. https://github.com/odoo/enterprise/blob/fcf06997a0eb999af86e4b6917311a86f04a390e/hr_payroll_account/models/account_chart_template.py#L16-L18 This triggers the reinstallation of the default payroll accounts, which overwrites and discards any manual configuration changes the user has made to their salary rules. ### Fix: Ensure that default payroll accounts are only reset during a genuine chart of accounts loading process, and not during localized post-load operations triggered by other modules. opw-6251112 Forward-Port-Of: odoo/enterprise#121687
Customers can no longer increase rental product quantities in the cart beyond what is actually available for the selected rental dates. This prevents overselling planned rental services and keeps website orders aligned with scheduling capacity.
Original PR description
From the cart, it is possible to increase the amount ordered of a rental product that synchronizes shifts depending on a specific service Steps to reproduce: 1. Install website_sale_renting_planning…
From the cart, it is possible to increase the amount ordered of a rental product that synchronizes shifts depending on a specific service Steps to reproduce: 1. Install website_sale_renting_planning module 2. Go to Rental > Products and create a new product "test" with Sales enabled, Product Type "Service", Plan Services enabled as "Developer", in the Sales tab, enable Is Published and in the Rental prices tab, create a pricing for Daily period 3. In the General Information tab, click on the internal link to "Developer" 4. Enable Sync Shifts and Rental Orders 5. Go to the eCommerce website and search for product "test" 6. Add as much product "test" to the cart as possible (the quantity is limited) 7. Open the cart 8. You can increase the amount of the product regardless of its availability Issue: We don't check the renting availabilities to limit the maximum quantity of the product Solution: Check that the new quantity of the product is available in `_verify_updated_quantity` for the specified dates. We also need to check the availability of the product when we modify the rental dates opw-6274035 Forward-Port-Of: odoo/enterprise#123056
Barcode transfers now correctly block scanning products that were not reserved when extra products are not allowed, even after leaving and reopening the transfer. The fix also restores the ability to add products to immediate delivery transfers where that workflow is valid, reducing errors and interruptions for warehouse users.
Original PR description
This [PR] made sure it was not possible to scan unreserved products when `allow_extra_product` was disabled, even when exiting and re-entering a transfer. It worked under the assumption that an immediate transfer always stays in draft, which is wrong for deliveries. The 2nd commit of this PR partly address this issue by allowing the user to add multiple products with the "Add Product" button when the transfer is immediate. While working on this issue, we encountered a bug in the scanning prevention that should have been caught by a tour but was not. This is fixed in the 1st commit. More details in the commit messages. [PR]: https://github.com/odoo/enterprise/pull/123793 Forward-Port-Of: odoo/enterprise#125313
Corrects a small payroll validation error that could prevent the Mexican payroll accounting EDI module from installing successfully. This helps companies using Mexican payroll avoid setup failures related to employee and company tax data checks.
Original PR description
[`_l10n_mx_is_curp_needed`] was called with self instead of slip. However, the method expects a payslip record from its caller, [`_compute_issues`]. This typo was introduced in:…
[`_l10n_mx_is_curp_needed`] was called with self instead of slip. However, the method expects a payslip record from its caller, [`_compute_issues`].
This typo was introduced in:
odoo/enterprise@07201466e54f28c6d295d63b908e9a65e39f4862
```py
/home/odoo/src/enterprise/saas-19.3/hr_payroll/models/hr_payslip.py(1913)_compute_issues()
-> issues = generate_issue(slip, context)
/home/odoo/src/enterprise/saas-19.3/l10n_mx_hr_payroll_account_edi/models/hr_payslip.py(235)_issue_mx_warnings()
-> if not slip.company_id.l10n_mx_curp and self._l10n_mx_is_curp_needed():
/home/odoo/src/enterprise/saas-19.3/l10n_mx_hr_payroll_account_edi/models/hr_payslip.py(322)_l10n_mx_is_curp_needed()
-> not self.company_id.partner_id.is_company
/home/odoo/src/odoo/saas-19.3/odoo/orm/fields.py(1726)__get__()
-> record.ensure_one()
> /home/odoo/src/odoo/saas-19.3/odoo/orm/models.py(5344)ensure_one()
-> raise ValueError("Expected singleton: %s" % self)
```
This causes module installation to fail with:
```py
File "/home/odoo/src/odoo/saas-19.3/odoo/tools/convert.py", line 779, in convert_csv_import
raise Exception(env._(
Exception: Module loading l10n_mx_hr_payroll_account_edi failed: file l10n_mx_hr_payroll_account_edi/data/hr.employee.type.csv could not be processed:
Ocurrió un error desconocido durante la importación: <class 'ValueError'>: Expected singleton: res.partner(7, 9)
```
upg-4468049
[`_l10n_mx_is_curp_needed`]: https://github.com/odoo/enterprise/blob/saas-19.3/l10n_mx_hr_payroll_account_edi/models/hr_payslip.py#L235
[`_compute_issues`]: https://github.com/odoo/enterprise/blob/saas-19.3/hr_payroll/models/hr_payslip.py#L1904-L1913The Swedish SIE4 general ledger export now uses the actual configured fiscal year dates instead of assuming every fiscal year lasts exactly one year. This prevents mismatches between declared reporting periods and exported accounting data, especially for shortened or extended fiscal years.
Original PR description
## Issue: Exporting the general ledger as SIE4 set the duration of fiscal years to one year from the starting date of the fiscal year. ## Steps to reproduce: - Create a fiscal year A of one month for…
## Issue: Exporting the general ledger as SIE4 set the duration of fiscal years to one year from the starting date of the fiscal year. ## Steps to reproduce: - Create a fiscal year A of one month for year X-1 (December 1st to December31th year X-1) - Create a fiscal year B of 1 year and 1 month (January 1st Year X to January 31th year X+1) - Create Invoices in November year X-1, December year X-1, year X and in January year X+1 and confirm them - Go to General Leder - Set date to the fiscal year B - Export as SIE4 ### Current behavior: - **for previous fiscal year** - declared fiscal year (#RAR field) goes : - from fiscal year X date_from -1 year - to fiscal year X date_to -1 year - However data are computed: - from fiscal year X date_from -1 year - to fiscal year X date_from -1 day - **for current fiscal year** - declared fiscal year (#RAR field) goes : - from fiscal year X date_from - to fiscal year X date_to - However data are computed: - from fiscal year X date_from - to fiscal year X date_from +1 year ### Expected behavior: Declared fiscal year match the one that is use for computation. - for previous fiscal year - from fiscal year X-1 date_from - to fiscal year X date_from -1 day - for current year - from fiscal year X date_from - to fiscal year X date_to Cause: Current year length was wrong because it [relied on](https://github.com/odoo/enterprise/blob/22b4100006ec24bf6e4b64042cbfdba360fcf470/l10n_se_sie4_export/models/account_general_ledger.py#L139-L140) the next_date_from which was wrong. opw-6264766 Forward-Port-Of: odoo/enterprise#125354
This fix prevents the scheduled email import process from crashing when a Chilean electronic tax document is missing a recipient tax ID. Instead of repeatedly blocking the mailbox, the system now handles the incomplete document through the existing error flow so other incoming messages can continue to be processed.
Original PR description
### Problem `Mail: Fetchmail Service` cron aborts with `AttributeError: 'NoneType' object has no attribute 'upper'` in `l10n_cl_edi/models/fetchmail_server.py::_process_incoming_customer_claim` when…
### Problem
`Mail: Fetchmail Service` cron aborts with `AttributeError: 'NoneType' object has no attribute 'upper'` in `l10n_cl_edi/models/fetchmail_server.py::_process_incoming_customer_claim` when an incoming customer claim DTE has no `<RUTRecep>`:
```python
dte.findtext('.//ns0:RUTRecep', ...).upper() or
dte.findtext('.//ns0:RutReceptor', ...).upper()
```
`findtext()` returns `None` when the tag is missing, so `.upper()` blows up before the `or` fallback can run. Once the cron hits such a message it re-crashes on every subsequent run and blocks the whole mailbox until the offending mail is deleted.
### Fix
Guard each `findtext(...)` with `or ''` so the `or` chain actually falls through. Empty `partner_vat` is already handled by the existing "Partner … has not been found" branch a few lines below.
### Traceback (Odoo 19)
```
File "/mnt/extra-addons/enterprise/l10n_cl_edi/models/fetchmail_server.py", line 285, in _process_incoming_customer_claim
dte.findtext('.//ns0:RUTRecep', namespaces=XML_NAMESPACES).upper() or
AttributeError: 'NoneType' object has no attribute 'upper'
```
### Ticket
No ticket open for this but opw-5257481 is related.
Forward-Port-Of: odoo/enterprise#1233876 changes
Enhancements to existing features
This update limits Chilean company activity selections to the maximum supported by the official electronic document format. It helps prevent electronic invoices and related documents from being rejected because too many business activities were included.
Original PR description
The Chilean XML schema supports maximum of 4 activities (l10n_cl_company_activity_ids), but we allow to add more than that. If this happens, it causes rejections since electronic documents are being sent with more than 4 options selected, and returning rejection errors. Adding constraint to limit l10n_cl_company_activity_ids task-id: 6329320 Forward-Port-Of: odoo/enterprise#123856
Resolved issues and error corrections
Installing POS no longer resets customized payroll account settings for Swiss salary rules. This protects user-entered payroll accounting configurations from being overwritten during unrelated module setup.
Original PR description
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code…
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code 1000) - Install 'POS' - Check the accounts you configured on the salary rules >The accounts are reset to their default values ### Cause of Issue: POS depends on the module `stock_account`. When `stock_account` is installed, the `_configure_journals` method in its `__init__.py` creates a new `account.journal` for inventory valuation. To apply default values to this journal, the method retrieves data from the chart template. https://github.com/odoo/odoo/blob/7d16ef88784e18e885b97dbded3231775f1349d2/addons/stock_account/__init__.py#L41 The `hr_payroll_account` module overrides `_post_load_data` and unconditionally calls `_load_payroll_accounts(template_code, company)`. https://github.com/odoo/enterprise/blob/fcf06997a0eb999af86e4b6917311a86f04a390e/hr_payroll_account/models/account_chart_template.py#L16-L18 This triggers the reinstallation of the default payroll accounts, which overwrites and discards any manual configuration changes the user has made to their salary rules. ### Fix: Ensure that default payroll accounts are only reset during a genuine chart of accounts loading process, and not during localized post-load operations triggered by other modules. opw-6251112 Forward-Port-Of: odoo/enterprise#121687
This fixes incorrect balance amounts shown in Mexican electronic payment documents when foreign currency exchange differences were involved. Payments and credit notes are now applied in the right order, helping ensure compliant and accurate CFDI reporting after invoices are fully settled.
Original PR description
### Issue: Attributes of DoctoRelacionado node in the XML display an incorrect value because the exchange rate difference entry distorts the calculation, even though the invoice was fully settled.…
### Issue: Attributes of DoctoRelacionado node in the XML display an incorrect value because the exchange rate difference entry distorts the calculation, even though the invoice was fully settled. https://drive.google.com/file/d/1ntQny0o8bkkfYtY5ZNBK0Yq7Rq0I-yfz/view ### Fix: Sorting partials by "not exchange_move_id" first broke the chronological order whenever the invoice/payment partial itself carried an exchange difference (e.g. a foreign currency payment settled at another rate). This made the residual-chain algorithm consume the credit note's "other_residual" on the wrong payment, so ImpSaldoAnt/ImpPagado/ ImpSaldoInsoluto in the payment CFDI's DoctoRelacionado stayed wrong even though the invoice was fully paid. Populate the exchange move mapping in a separate first pass and sort the partials purely by date/id, so credit notes are always deducted from the correct payment. task-id:[6363092](https://www.odoo.com/odoo/project/49/tasks/6363092) Forward-Port-Of: odoo/enterprise#124882
Rental orders using a custom make-to-order or buy route now correctly generate the return transfer as well as the delivery and purchase. This prevents missing return operations, helping teams track rented products back into stock reliably.
Original PR description
### Steps to reproduce: - In the settings enable: Multi-Steps Routes and rental transfers - Unarchive the MTO route - Create a rental product P with a buy route and a set vendor - Create a rental…
### Steps to reproduce: - In the settings enable: Multi-Steps Routes and rental transfers - Unarchive the MTO route - Create a rental product P with a buy route and a set vendor - Create a rental order for 1 x P and set the the MTO route on the sol - Confirm the order #### > The delivery as well as the purchase for 1 unit of P was generated but the return was not. ### Cause of the issue: The procurement generated to handle both the delivery and the return rental picking are handled by the `_create_procurements`: https://github.com/odoo/enterprise/blob/b0e48baaf99bdc4faefd2ffdd3bd5637fb548593/sale_stock_renting/models/sale_order_line.py#L353-L374 The `route_ids` set and used is the `mto_route` set on the sol: https://github.com/odoo/odoo/blob/0f061503e26ac8c441d62d91947419119e48c47a/addons/sale_stock/models/sale_order_line.py#L415-L422 https://github.com/odoo/odoo/blob/0f061503e26ac8c441d62d91947419119e48c47a/addons/sale_stock/models/sale_order_line.py#L282-L297 However, in the present case, the mto route does not contain any rule with a relevant `location_src_id` in the rental location so that the return will not be generated. opw-6361322 Forward-Port-Of: odoo/enterprise#124862 Forward-Port-Of: odoo/enterprise#124097
The Swedish SIE4 general ledger export now uses the actual configured fiscal year dates instead of assuming every fiscal year lasts exactly one year. This keeps exported reporting periods aligned with the accounting data, especially for shortened or extended fiscal years.
Original PR description
## Issue: Exporting the general ledger as SIE4 set the duration of fiscal years to one year from the starting date of the fiscal year. ## Steps to reproduce: - Create a fiscal year A of one month for…
## Issue: Exporting the general ledger as SIE4 set the duration of fiscal years to one year from the starting date of the fiscal year. ## Steps to reproduce: - Create a fiscal year A of one month for year X-1 (December 1st to December31th year X-1) - Create a fiscal year B of 1 year and 1 month (January 1st Year X to January 31th year X+1) - Create Invoices in November year X-1, December year X-1, year X and in January year X+1 and confirm them - Go to General Leder - Set date to the fiscal year B - Export as SIE4 ### Current behavior: - **for previous fiscal year** - declared fiscal year (#RAR field) goes : - from fiscal year X date_from -1 year - to fiscal year X date_to -1 year - However data are computed: - from fiscal year X date_from -1 year - to fiscal year X date_from -1 day - **for current fiscal year** - declared fiscal year (#RAR field) goes : - from fiscal year X date_from - to fiscal year X date_to - However data are computed: - from fiscal year X date_from - to fiscal year X date_from +1 year ### Expected behavior: Declared fiscal year match the one that is use for computation. - for previous fiscal year - from fiscal year X-1 date_from - to fiscal year X date_from -1 day - for current year - from fiscal year X date_from - to fiscal year X date_to Cause: Current year length was wrong because it [relied on](https://github.com/odoo/enterprise/blob/22b4100006ec24bf6e4b64042cbfdba360fcf470/l10n_se_sie4_export/models/account_general_ledger.py#L139-L140) the next_date_from which was wrong. opw-6264766 Forward-Port-Of: odoo/enterprise#125354
This fix prevents the Employees list from crashing when HR document counts are shown across multiple companies. It ensures document totals are calculated separately per employee/company setup, so multi-company users can view the full employee list reliably.
Original PR description
Steps to reproduce: ------------------- 1. Install `documents_hr` and `web_studio` with demo data. 2. Add `document_count` to the Employees list view via Studio. 3. Create a second company with an…
Steps to reproduce:
-------------------
1. Install `documents_hr` and `web_studio` with demo data.
2. Add `document_count` to the Employees list view via Studio.
3. Create a second company with an employee, enable multi-company.
4. Open Employees list, click **All** in the search panel.
Issues:
------
Issue 1:
```python
File "/home/odoo/odoo/enterprise/documents_hr/models/hr_employee.py", line 25, in _compute_document_count
if not self.company_id.documents_hr_settings:
File "/home/odoo/odoo/community/odoo/orm/fields.py", line 1429, in __get__
record.ensure_one()
File "/home/odoo/odoo/community/odoo/orm/models.py", line 5640, in ensure_one
raise ValueError("Expected singleton: %s" % self)
ValueError: Expected singleton: res.company(1, 2)
```
Issue 2:
```python
File "/home/odoo/odoo/enterprise/documents_hr/models/hr_employee.py", line 31, in _compute_document_count
('partner_id', '=', self.work_contact_id.id)
File "/home/odoo/odoo/community/odoo/orm/fields_misc.py", line 117, in __get__
raise ValueError("Expected singleton: %s" % record)
ValueError: Expected singleton: res.partner(9, 8, 7)
```
Cause:
---------
https://github.com/odoo/enterprise/blob/38674448e387159d28f98c1678856fcaf5f7f52e/documents_hr/models/hr_employee.py#L23-L48
1. The document count computation assumes all employees belong to the same company by directly accessing `self.company_id.documents_hr_settings`. In a multi-company environment, `self` may contain employees from different companies, making self.company_id a multi-recordset and triggering a singleton error.
2. Similarly, when `documents_hr_settings` is disabled, the fallback computation accesses `self.work_contact_id` on a multi-recordset, causing another singleton error.
Solution:
-----------
Split the employees based on whether `documents_hr_settings` is enabled and compute each group separately.
Additionally, use the current employee's `work_contact_id` in the fallback computation to avoid singleton error.
**NOTE:**
This has been resolved from saas-19.4 onward with this improvement [commit](https://github.com/odoo/enterprise/commit/d018d8205300b434728129e199ae048cefbaa296).
opw-6351141
Forward-Port-Of: odoo/enterprise#1248265 changes
Enhancements to existing features
This update prevents Chilean companies from selecting more than four business activities for electronic documents, matching the official Chilean XML requirements. It helps avoid document rejection errors caused by sending too many activity options.
Original PR description
The Chilean XML schema supports maximum of 4 activities (l10n_cl_company_activity_ids), but we allow to add more than that. If this happens, it causes rejections since electronic documents are being sent with more than 4 options selected, and returning rejection errors. Adding constraint to limit l10n_cl_company_activity_ids task-id: 6329320 Forward-Port-Of: odoo/enterprise#123856
Resolved issues and error corrections
Swedish SIE4 general ledger exports now use the company’s actual fiscal year dates instead of assuming every fiscal year lasts exactly one year. This prevents exported reporting periods from being misstated when fiscal years are shorter or longer than usual, improving accounting accuracy and compliance.
Original PR description
## Issue: Exporting the general ledger as SIE4 set the duration of fiscal years to one year from the starting date of the fiscal year. ## Steps to reproduce: - Create a fiscal year A of one month for…
## Issue: Exporting the general ledger as SIE4 set the duration of fiscal years to one year from the starting date of the fiscal year. ## Steps to reproduce: - Create a fiscal year A of one month for year X-1 (December 1st to December31th year X-1) - Create a fiscal year B of 1 year and 1 month (January 1st Year X to January 31th year X+1) - Create Invoices in November year X-1, December year X-1, year X and in January year X+1 and confirm them - Go to General Leder - Set date to the fiscal year B - Export as SIE4 ### Current behavior: - **for previous fiscal year** - declared fiscal year (#RAR field) goes : - from fiscal year X date_from -1 year - to fiscal year X date_to -1 year - However data are computed: - from fiscal year X date_from -1 year - to fiscal year X date_from -1 day - **for current fiscal year** - declared fiscal year (#RAR field) goes : - from fiscal year X date_from - to fiscal year X date_to - However data are computed: - from fiscal year X date_from - to fiscal year X date_from +1 year ### Expected behavior: Declared fiscal year match the one that is use for computation. - for previous fiscal year - from fiscal year X-1 date_from - to fiscal year X date_from -1 day - for current year - from fiscal year X date_from - to fiscal year X date_to Cause: Current year length was wrong because it [relied on](https://github.com/odoo/enterprise/blob/22b4100006ec24bf6e4b64042cbfdba360fcf470/l10n_se_sie4_export/models/account_general_ledger.py#L139-L140) the next_date_from which was wrong. opw-6264766 Forward-Port-Of: odoo/enterprise#125354
Custom debit and credit accounts on Swiss payroll salary rules are no longer reset when installing related apps such as Point of Sale. This protects user configuration and prevents unexpected payroll accounting changes after module installations.
Original PR description
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code…
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code 1000) - Install 'POS' - Check the accounts you configured on the salary rules >The accounts are reset to their default values ### Cause of Issue: POS depends on the module `stock_account`. When `stock_account` is installed, the `_configure_journals` method in its `__init__.py` creates a new `account.journal` for inventory valuation. To apply default values to this journal, the method retrieves data from the chart template. https://github.com/odoo/odoo/blob/7d16ef88784e18e885b97dbded3231775f1349d2/addons/stock_account/__init__.py#L41 The `hr_payroll_account` module overrides `_post_load_data` and unconditionally calls `_load_payroll_accounts(template_code, company)`. https://github.com/odoo/enterprise/blob/fcf06997a0eb999af86e4b6917311a86f04a390e/hr_payroll_account/models/account_chart_template.py#L16-L18 This triggers the reinstallation of the default payroll accounts, which overwrites and discards any manual configuration changes the user has made to their salary rules. ### Fix: Ensure that default payroll accounts are only reset during a genuine chart of accounts loading process, and not during localized post-load operations triggered by other modules. opw-6251112 Forward-Port-Of: odoo/enterprise#121687
This fixes a payroll issue where employees could not defer a new time off request if an earlier deferred absence already affected the same payroll period. It ensures closed payroll periods can still handle later time off corrections without blocking HR payroll processing.
Original PR description
# How to reproduce For an employee with full attendances for april and may: - Create payslip for the month of April, Compute Sheet & Confirm - Create a Time off request for that employee ffrom the…
# How to reproduce For an employee with full attendances for april and may: - Create payslip for the month of April, Compute Sheet & Confirm - Create a Time off request for that employee ffrom the 1st of April to the 10th of April, Approve & Validate > Since the April payroll is closed, you need to defer the Time Off - Report to Next Month - Create payslip for the month of May > The deffered time off should be there - Compute Sheet & Confirm - Create a Time off request for that employee for the 3rd of May, Approve & Validate > Again, the May payroll is closed, so you need to defer the Time Off # The issue You cannot defer the time off because "There is no work entries linked to this time off to report" # The cause When deferring a time off, we call `action_report_to_next_month` that will look for work entries generated during the leave period to defer : https://github.com/odoo/enterprise/blob/f93882555864a1f0a2a3e3863780096c78923bfa/hr_payroll_holidays/models/hr_leave.py#L94-L101 The issue is that, since [this commit], we search for work entries that are not leaves and the first deferring we did transformed the work entries at the start of may into leaves. [this commit]: https://github.com/odoo/enterprise/commit/13ce65b8ca61f9a825f2876e2727cddfae83f894 opw-6318809 Forward-Port-Of: odoo/enterprise#123263
This fixes an issue where Mexican electronic payment documents could show incorrect paid or remaining balances when foreign currency exchange-rate differences were involved. Payments and credit notes are now applied in the proper order, helping ensure compliant and accurate CFDI reporting.
Original PR description
### Issue: Attributes of DoctoRelacionado node in the XML display an incorrect value because the exchange rate difference entry distorts the calculation, even though the invoice was fully settled.…
### Issue: Attributes of DoctoRelacionado node in the XML display an incorrect value because the exchange rate difference entry distorts the calculation, even though the invoice was fully settled. https://drive.google.com/file/d/1ntQny0o8bkkfYtY5ZNBK0Yq7Rq0I-yfz/view ### Fix: Sorting partials by "not exchange_move_id" first broke the chronological order whenever the invoice/payment partial itself carried an exchange difference (e.g. a foreign currency payment settled at another rate). This made the residual-chain algorithm consume the credit note's "other_residual" on the wrong payment, so ImpSaldoAnt/ImpPagado/ ImpSaldoInsoluto in the payment CFDI's DoctoRelacionado stayed wrong even though the invoice was fully paid. Populate the exchange move mapping in a separate first pass and sort the partials purely by date/id, so credit notes are always deducted from the correct payment. task-id:[6363092](https://www.odoo.com/odoo/project/49/tasks/6363092) Forward-Port-Of: odoo/enterprise#124882
7 changes
Enhancements to existing features
ZKTeco attendance punches are now processed one transaction at a time, so an issue with one punch no longer stops the whole batch. The update also adds scheduled processing and clearer status information, helping teams spot and resolve attendance import issues more easily.
Resolved issues and error corrections
Field service shifts now keep equipment that users manually remove from the shift list, even after changing shift times or signing in. This prevents deleted serial-numbered equipment from reappearing while still refreshing equipment correctly when the customer changes.
Original PR description
Issue: When a user manually removes a specific equipment (SN) from a shift's list, editing the shift times or signing in reloads the entire list, bringing the deleted equipment back. Solution: Skip reassignment if the currently selected lots (`._origin`) are a valid subset of the customer's total equipment. This preserves manual deletions while still correctly updating the list if the customer is completely changed. Task: 6346404 Forward-Port-Of: odoo/enterprise#125040
This fix prevents incoming Chilean electronic document emails from repeatedly blocking mailbox processing when a recipient tax ID is missing. Instead of crashing, the system now handles the missing value through the existing validation flow, keeping email imports running reliably.
Original PR description
### Problem `Mail: Fetchmail Service` cron aborts with `AttributeError: 'NoneType' object has no attribute 'upper'` in `l10n_cl_edi/models/fetchmail_server.py::_process_incoming_customer_claim` when…
### Problem
`Mail: Fetchmail Service` cron aborts with `AttributeError: 'NoneType' object has no attribute 'upper'` in `l10n_cl_edi/models/fetchmail_server.py::_process_incoming_customer_claim` when an incoming customer claim DTE has no `<RUTRecep>`:
```python
dte.findtext('.//ns0:RUTRecep', ...).upper() or
dte.findtext('.//ns0:RutReceptor', ...).upper()
```
`findtext()` returns `None` when the tag is missing, so `.upper()` blows up before the `or` fallback can run. Once the cron hits such a message it re-crashes on every subsequent run and blocks the whole mailbox until the offending mail is deleted.
### Fix
Guard each `findtext(...)` with `or ''` so the `or` chain actually falls through. Empty `partner_vat` is already handled by the existing "Partner … has not been found" branch a few lines below.
### Traceback (Odoo 19)
```
File "/mnt/extra-addons/enterprise/l10n_cl_edi/models/fetchmail_server.py", line 285, in _process_incoming_customer_claim
dte.findtext('.//ns0:RUTRecep', namespaces=XML_NAMESPACES).upper() or
AttributeError: 'NoneType' object has no attribute 'upper'
```
### Ticket
No ticket open for this but opw-5257481 is related.
Forward-Port-Of: odoo/enterprise#123387Accrual report totals now display the correct combined amounts when users group purchase or sales accounting lines by dates. This prevents misleading zero totals in grouped views, helping accounting teams review bills to receive and related reports accurately.
Original PR description
Currently, when users group accrual reports by dates, aggregated computed fields are not calculated correctly. As a result, values such as the total amount may be displayed as zero even when the…
Currently, when users group accrual reports by dates, aggregated computed fields are not calculated correctly. As a result, values such as the total amount may be displayed as zero even when the grouped records contain non-zero amounts.
## Steps to produce:
- Install Purchase and Accounting
- Create a product `Energy drink Sample` with cost 10$
- Create and Confirm a PO with the energy drink sample with vendor as `Administrator`
- Set `Received` as 1.
- Go to Accounting > Review > Bill to Receive
- Search Filter> Remove vendor grouping > Use Custom Group `Order Date`
- Expand the group
## Observed Behavior
Although the grouped lines contain an amount of 10 dollars, the aggregated total displayed in the sum remains zero.
The total should reflect the combined value of the grouped lines.
## Root Cause:
This issue occurs because opening the view triggers `_read_group_for_accrual`, which overrides the `_read_group` method on purchase order lines. The purpose of this override is to support grouping on computed fields that are not stored in the database such as `amount_to_invoice_at_date (Amount)`, `qty_received_at_date (Received)`.
When `_read_group_for_accrual` is executed, it delegates grouping for non-computed fields to the parent `_read_group` implementation, as shown at [1].
The parent method returns results in `res` similar to:
```
[(datetime.datetime(2026, 6, 1, 0, 0), 1.0, 10.0, 1)]
```
During iteration over res at [2], the code uses `group[0]` as the grouping key. In this example, group[0] is `datetime.datetime(2026, 6, 1, 0, 0)`, which represents the granularity date
(e.g., the first day of the month when grouping by month).
However, `records_by_group` is keyed by the actual purchase order line order dates rather than the granularity dates returned by `_read_group`. For example:
```
{datetime.datetime(2026, 6, 12, 0, 0): purchase.order.line(1,)}
```
As a result, the lookup performed using the granularity date (`datetime.datetime(2026, 6, 1, 0, 0))` does not find a matching entry in `records_by_group`. Consequently, records falls back to an empty `purchase.order.line()` recordset.
Later, at [3], the aggregation logic computes totals using this empty recordset. Since the required fields are evaluated on an empty set of records, the aggregated values are computed as zero.
This ultimately causes the method to return a total value of zero
[1]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L9-L24
[2]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L40-L42
[3]-
https://github.com/odoo/enterprise/blob/eeea1377a7d36d11e140c44dee30a45228e7b4a6/account_accountant/models/analytic_mixin.py#L44-L47
## Solution:
The issue can be resolved by grouping records using the same date granularity specified in the groupby, rather than using the field values.
By aligning the grouping logic with the granularity returned by `_read_group` (for example, grouping by the first day of the month when using monthly grouping ), the keys in `records_by_group` match the values returned in `res`. As a result, the corresponding records are correctly retrieved during aggregation.
This ensures that the aggregation is performed on the appropriate purchase order lines instead of an empty recordset, allowing the computed totals to be calculated correctly.
[opw-6261225](https://www.odoo.com/odoo/project/49/tasks/6261225)
Forward-Port-Of: odoo/enterprise#120369Uruguayan export e-invoices that are fully offset by discounts now generate the required discount details correctly. This helps exporters issue valid zero-total documents for customs or incoterm scenarios and avoid rejection by Uruware validation.
Original PR description
## Problem When generating an export CFE (e-Factura Exportación) in the Uruguayan EDI module, invoices that include a discount line equal to the subtotal — resulting in a **total of 0.00** — were not…
## Problem When generating an export CFE (e-Factura Exportación) in the Uruguayan EDI module, invoices that include a discount line equal to the subtotal — resulting in a **total of 0.00** — were not handled correctly by the XML/CFE generation logic. This use case is valid and required by exporters who need to reflect the declared value of goods/services while invoicing at zero (e.g. to comply with customs or incoterm requirements such as FCA). In Uruware's validation portal, the "Descuentos y Recargos" (discounts & surcharges) section of the subtotal block must be correctly populated for the CFE to be accepted. **Example:** An invoice with a line of 648.00 UYU and a global discount of −648.00 UYU → Total: 0.00. The export value is still declared, taxes are zero, but the CFE must reflect the discount amount explicitly. <img width="592" height="679" alt="example_expo_invoice_discount" src="https://github.com/user-attachments/assets/aa83c158-e342-4da5-a251-fc209bbed5c4" /> ## Root Cause The CFE template (`cfe_template.xml`) and the move computation logic (`account_move.py`) did not account for the case where export invoices carry line-level or global discounts that zero out the total. The discount amount was either omitted from the XML nodes or computed incorrectly, causing Uruware validation to fail or the discount block to not render. ## Fix - **`l10n_uy_edi/models/account_move.py`** — Updated the export invoice computation to correctly include discount amounts in the CFE data dict, ensuring the `ValorDR` is filled with the value of the discount per line. - **`l10n_uy_edi/views/cfe_template.xml`** — Adjusted the template condition so `MntExpoyAsim` node accepts 0 as value. ## Steps to Reproduce (before fix) 1. Create an export invoice (e-Factura Exportación) for a foreign partner. 2. Add a product line with a unit price, e.g. 216.00 × 3 = 648.00 UYU. 3. Add a global discount of 648.00 (same amount) so the total is 0.00. 4. Confirm and send to Uruware — the CFE is rejected / discount block is missing. ## Verification After the fix, the same invoice generates a valid CFE accepted by Uruware with the discount correctly reflected in the `DscRcgGlobal` node and the discount line visible on the printed document. Forward-Port-Of: odoo/enterprise#124910 Forward-Port-Of: odoo/enterprise#120130
Odoo Social Marketing now disables the reply option for Twitter posts when Twitter does not permit a reply, such as posts that do not mention the company account or quote one of its tweets. This helps prevent accidental or automated replies that would fail or risk unwanted outreach.
Original PR description
Purpose ======= To prevent LLM from spamming Twitter users, Twitter does not allow to reply to a tweet if we are not mentioned in it, or if the tweet does not quote one of our tweet. For that reason, we disable the reply button when needed. Task-5964524 Forward-Port-Of: odoo/enterprise#125739 Forward-Port-Of: odoo/enterprise#112161
Mexican payroll can now calculate expected work hours even when a payslip has not yet been created. This helps off-cycle payroll runs and related checks produce consistent attendance and work-hour results earlier in the process.
Original PR description
Previously, the `_preprocess_work_hours_data` method would abort early if no payslip was found for the given period. This prevented the correct generation or evaluation of expected Mexican work hours…
Previously, the `_preprocess_work_hours_data` method would abort early if no payslip was found for the given period. This prevented the correct generation or evaluation of expected Mexican work hours in contexts where a draft payslip does not yet exist (i.e., when an off-cycle payslip is initially generated for a given time period). To resolve this, the strict dependency on the payslip record has been removed. It now falls back to the contract version's base data when a payslip is absent: - The calendar defaults to the contract version's `resource_calendar_id`. - The duration is assumed to be standard (`is_wrong_duration = False`). - The Mexican schedule table (`l10n_mx_schedule_table`) is fetched globally from the environment (`hr.rule.parameter`) rather than relying on the payslip-specific helper method. This ensures expected work hours and attendance fields are calculated consistently across all payroll workflows, regardless of whether the payslip has been instantiated. opw-6351402 Forward-Port-Of: odoo/enterprise#125219
10 changes
Enhancements to existing features
Companies using Chilean electronic invoicing can now select only up to four business activities, matching the official XML requirements. This helps prevent electronic documents from being rejected because too many activities were included.
Original PR description
The Chilean XML schema supports maximum of 4 activities (l10n_cl_company_activity_ids), but we allow to add more than that. If this happens, it causes rejections since electronic documents are being sent with more than 4 options selected, and returning rejection errors. Adding constraint to limit l10n_cl_company_activity_ids task-id: 6329320 Forward-Port-Of: odoo/enterprise#123856
Resolved issues and error corrections
Resource-based appointments can now use the configured staff member as the event organizer instead of always using the appointment type creator. This prevents the wrong person from being shown as the notification sender or receiving booking updates, and lets businesses adjust the organizer later.
Original PR description
**Steps to reproduce:** - Install Appointment app - Create an appointment_type based on resources - Book this appointment to create the calendar event - The creator of the appointment type is used as the notification sender (organizer) - He also gets notified on each further booking - It can't be changed afterwards **Issue:** `calendar.event` has its `user_id` set using `self.create_uid.id` for event-based appointments when handling the form. Then this `user_id` is used to send the notification mail using the related template. **Fix:** We allow `appointment_type.staff_user_ids` to be used when based on resources, and we try to use its first record as the default organizer instead (so that it can be updated later on). opw-6042615
Accrual list reports now remember the selected "As of" date when users open a record and return via breadcrumbs. This prevents the report from unexpectedly resetting to today's date and helps accounting teams keep reviewing the same period without reapplying filters.
Original PR description
Issue: In accrual list reports (Billed Not Received / Invoiced Not Delivered), selecting an "As of" date, opening a line, and returning with the breadcrumb resets the date filter to the default value…
Issue: In accrual list reports (Billed Not Received / Invoiced Not Delivered), selecting an "As of" date, opening a line, and returning with the breadcrumb resets the date filter to the default value (today's date) Steps to reproduce: 1) Open an accrual list report ( Accounting > Audit > Purchases > Bill to receive / Billed Not Received OR Invoices to be issues / invoiced Not delivered) 2) Pick any "As of" date 3) Open any row 4) Click breadcrumb to return to the accrual list 5) Observe the "As of" date has been reset to today's date To generate some data you could: create a PO, then upload the bill, validate the receipt, then you'll find it in bills received Cause: `AccrualListController.setup()` always initialized state.date with a fresh default date and did not re-put the previously saved `accrual_entry_date` from restored context https://github.com/odoo/enterprise/blob/899f0d45b2ae1dc4e5e06a2e0acb3d006d12d563/account_reports/static/src/views/accrual_list_controller.js#L10-L16 Although `setDate()` stored the selected date in context, `setup()` overwrote the UI state on controller recreation https://github.com/odoo/enterprise/blob/899f0d45b2ae1dc4e5e06a2e0acb3d006d12d563/account_reports/static/src/views/accrual_list_controller.js#L61-L65 Solution: - Persist `accrual_entry_date` in `AccrualListSearchModel` via `exportState()` / `_importState()`, so the date is restored in search context before the list model loads on breadcrumb navigation. - Initialize the date picker through `setDate()` in `onWillStart()` instead of hardcoding `DateTime.now()` in `setup()`, so restoration and user changes share the same code path. - In `setDate()`, reset grouped list caches (`currentGroups` and `groups`) before `root.load()`, because those caches are not keyed on `accrual_entry_date` and would otherwise show stale vendor groups after a date change or breadcrumb restore. opw-6232263 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fix ensures that Tyro payment surcharge fees are added to the point-of-sale order before the order is finalized. It helps prevent checkout discrepancies where surcharge amounts could be missed due to timing issues.
Original PR description
Currently when completing a Tyro payment with a surcharge fee in some cases there is a race condition preventing the surcharge line to be added to the pos order before its validation This PR fixes that issue opw-6402191
Uninstalling the AI Documents module now properly removes leftover auto-sorting rules. This prevents errors when users later upload files to folders that previously used AI auto-sort, keeping document workflows running smoothly.
Original PR description
Currently, an error occurs when a user uploads a document. **Steps to Reproduce:** - Install the `ai_documents` module. - Go to `Documents` and create a `folder`, or use an `existing one`. - Open the…
Currently, an error occurs when a user uploads a document.
**Steps to Reproduce:**
- Install the `ai_documents` module.
- Go to `Documents` and create a `folder`, or use an `existing one`.
- Open the `folder` > click `Actions` > `Auto-sort`, and `save`.
- Uninstall the `ai_documents` module.
- Go back to `Documents`, open the `same folder`, and `upload any document`.
- Error is logged in the `terminal`.
`ValueError: Invalid field documents.document.ai_sortable in condition ('ai_sortable', '=', True)`
When the ai_documents module is installed and the user enables Auto-sort for a folder [1], an
automation rule and its linked server action are created [2] (if they do not already exist).
Whenever a document is uploaded to that folder, the automation rule triggers the server action,
which runs the AI prompt to classify and sort the document.
However, when the ai_documents module is uninstalled, the related automation rule and server
action are not removed. As a result, uploading a document to the same folder still triggers the
automation rule. While evaluating its domain, it attempts to access the ai_sortable field, which
no longer exists because it is defined by the ai_documents module, raise the error [3].
This commit ensures that uninstalling the ai_documents module removes the related automation
rules. The linked server actions are then deleted automatically through the field's ondelete='cascade' [4].
[1]: https://github.com/odoo/enterprise/blob/c6d3efb164a23d555e30d0f0f7de3de2ef1d08d3/ai_documents/wizard/ai_documents_sort.py#L100
[2]- https://github.com/odoo/enterprise/blob/c6d3efb164a23d555e30d0f0f7de3de2ef1d08d3/ai_documents/models/documents_document.py#L313-L330
[3]- https://github.com/odoo/enterprise/blob/c6d3efb164a23d555e30d0f0f7de3de2ef1d08d3/ai_documents/models/documents_document.py#L321
[4]: https://github.com/odoo/odoo/blob/2cb2f33c871bf83c74098ada568e167aad24f2a5/addons/base_automation/models/ir_actions_server.py#L17
sentry-7607903354Installing related apps such as Point of Sale no longer resets customized payroll account settings. This preserves manual salary rule account configurations and prevents unexpected accounting changes after adding modules.
Original PR description
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code…
### Steps to reproduce: - In a database with Switzerland localization, install 'Accounting' and 'Payroll' - Manually change the debit and credit accounts on the Swiss ELM salary rules (rule code 1000) - Install 'POS' - Check the accounts you configured on the salary rules >The accounts are reset to their default values ### Cause of Issue: POS depends on the module `stock_account`. When `stock_account` is installed, the `_configure_journals` method in its `__init__.py` creates a new `account.journal` for inventory valuation. To apply default values to this journal, the method retrieves data from the chart template. https://github.com/odoo/odoo/blob/7d16ef88784e18e885b97dbded3231775f1349d2/addons/stock_account/__init__.py#L41 The `hr_payroll_account` module overrides `_post_load_data` and unconditionally calls `_load_payroll_accounts(template_code, company)`. https://github.com/odoo/enterprise/blob/fcf06997a0eb999af86e4b6917311a86f04a390e/hr_payroll_account/models/account_chart_template.py#L16-L18 This triggers the reinstallation of the default payroll accounts, which overwrites and discards any manual configuration changes the user has made to their salary rules. ### Fix: Ensure that default payroll accounts are only reset during a genuine chart of accounts loading process, and not during localized post-load operations triggered by other modules. opw-6251112 Forward-Port-Of: odoo/enterprise#121687
Changing a payslip to a payroll structure that does not use worked day lines now properly removes old worked day entries. Belgian payroll reporting is also adjusted so off-cycle payslips without worked days still include the correct remuneration amounts.
Original PR description
hr_payroll: Previously, changing to a structure with `use_worked_day_lines = False` (e.g., 13th month) caused `valid_slips` to be empty and return early, leaving stale worked day lines on the payslip. This commit resets the worked_days_lines before filtering for valid payslips. l10n_be_hr_payroll: After fixing the payroll bug and clearing worked_days_lines correctly, the DMFA report fails to correctly consider remunerations since the off-cycle payslips do not have worked_days_lines anymore. This commit backports a fix from odoo/enterprise#106689 to not skip remunerations for payslips with no worked days lines. task-6401942 Forward-Port-Of: odoo/enterprise#124986
Mexican payroll CFDI payslips will now have their SAT validation status refreshed correctly in Odoo. This prevents valid payslip tax documents from incorrectly appearing as unknown, improving payroll compliance visibility for users.
Original PR description
l10n_mx_hr_payroll_account_edi introduces new l10n_mx_edi.document states (payslip_sent, payslip_sent_failed, payslip_cancel, payslip_cancel_failed) but never extends the two hooks the base l10n_mx_edi module relies on to keep sat_state in sync: - _get_update_sat_status_domains(), which builds the domain used by the SAT-status cron (and manual refresh) to pick documents to poll. Payslip states were missing from it, so their SAT status was never fetched at all. - _update_document_sat_state(), which routes a fetched SAT status to a per-source-document handler. It has no branch for the payslip states, so even a manual poll would silently do nothing. As a result, payslip CFDIs validated in the SAT always appeared as "not_defined" in Odoo. opw-6192651
This fixes an issue where Mexican electronic payment XML could show incorrect related-document balances when exchange rate differences were involved. Payments, credit notes, and invoices are now processed in the proper order so fully settled invoices are reported accurately.
Original PR description
### Issue: Attributes of DoctoRelacionado node in the XML display an incorrect value because the exchange rate difference entry distorts the calculation, even though the invoice was fully settled.…
### Issue: Attributes of DoctoRelacionado node in the XML display an incorrect value because the exchange rate difference entry distorts the calculation, even though the invoice was fully settled. https://drive.google.com/file/d/1ntQny0o8bkkfYtY5ZNBK0Yq7Rq0I-yfz/view ### Fix: Sorting partials by "not exchange_move_id" first broke the chronological order whenever the invoice/payment partial itself carried an exchange difference (e.g. a foreign currency payment settled at another rate). This made the residual-chain algorithm consume the credit note's "other_residual" on the wrong payment, so ImpSaldoAnt/ImpPagado/ ImpSaldoInsoluto in the payment CFDI's DoctoRelacionado stayed wrong even though the invoice was fully paid. Populate the exchange move mapping in a separate first pass and sort the partials purely by date/id, so credit notes are always deducted from the correct payment. task-id:[6363092](https://www.odoo.com/odoo/project/49/tasks/6363092) Forward-Port-Of: odoo/enterprise#124882
Automatic bank reconciliation rules now use clearer matching logic and consider transaction amounts, helping avoid incorrect or duplicate rules. Users will also see only reconciliation rules relevant to the selected journal, making bank reconciliation more accurate and easier to manage.
Original PR description
Reconcile models automatically created now use contains instead of match regex and take the amount into consideration when creating the rule as well as checking for existing rules, it's checked whether all of the lines are positive or negative. Added an extra filter on the reconcile models so that it only shows rules that would be applied on the journal, and did some optimizations in the substring matching. task-6140372
5 changes
Resolved issues and error corrections
This fixes a problem where cancelling some active German POS certification transactions could fail because required receipt details were missing. The system now supplies a minimal cancellation receipt when needed, helping prevent failed cancellations while keeping existing receipt data unchanged.
Original PR description
When cancelling active transactions, the schema was forwarded as-is from the listed transaction. ACTIVE transactions can have an empty schema, and Fiskaly rejects the cancellation PUT with:
{
"code": "E_TX_NO_TYPE_DEFINED",
"message": "`schema.raw.process_type` must be defined for
updating or finishing a transaction",
"status_code": 409,
"error": "Conflict"
}
Fall back to a minimal CANCELLATION receipt schema when the transaction has no schema, while preserving any schema that is already present.
opw-6345005Changing the delivery address on an outgoing rental transfer no longer incorrectly changes the destination from the rental location to the general customer location. This helps rental deliveries keep the correct stock flow and avoids manual corrections after address updates.
Original PR description
**Issue** Changing the `partner_id` of an outgoing rental transfer could reset its destination location to the partner customer location instead of the rental location. **Steps to reproduce** -…
**Issue** Changing the `partner_id` of an outgoing rental transfer could reset its destination location to the partner customer location instead of the rental location. **Steps to reproduce** - Enable Rental Transfers from Rental Configuration. - Create a Sales Order for a rental product. - Open the related delivery transfer. - Using Studio, make the Destination Location field visible. -> Current destination location is: Partner/Customer/Rental - Change the delivery address -> The destination location become: Partner/Customer **Cause** Changing the delivery address (i.e: the partner_id) triggers `_compute_location_id`: https://github.com/odoo/odoo/blob/85372b625a80ab50fe2d8a6bce49a890b2bf1665/addons/stock/models/stock_picking.py#L949-L950 Since commit https://github.com/odoo/odoo/commit/8c90fc1fd336ee872fd67d8d72473e4f6c55b2e0, not only draft picking are recomputed. As a result, `location_dest_id` is set as `picking.partner_id.property_stock_customer` https://github.com/odoo/odoo/blob/85372b625a80ab50fe2d8a6bce49a890b2bf1665/addons/stock/models/stock_picking.py#L959-L961 https://github.com/odoo/odoo/blob/85372b625a80ab50fe2d8a6bce49a890b2bf1665/addons/stock/models/stock_picking.py#L963 Regardless whether we are in rental setup opw-6237495
The POS preparation display now counts orders in the badge the same way the preparation screen shows them. This prevents overnight open orders from disappearing from the badge and stops reset orders from being counted after they are removed from the screen.
Original PR description
Steps to reproduce: - Configure a preparation display on a POS config with a product category - Place an order and leave it in a non-final stage - Keep the session open past midnight Issue: The…
Steps to reproduce: - Configure a preparation display on a POS config with a product category - Place an order and leave it in a non-final stage - Keep the session open past midnight Issue: The kanban order-count badge drops the order once its create_date falls behind "today", while the preparation screen still lists it. The same divergence makes the badge keep counting an order that a "Reset" already removed from the screen. _compute_order_count() scoped its search on pos_config_id and create_date >= today, whereas the screen is built by get_preparation_display_order() from _get_open_orders_in_display() and _get_stageless_orders_in_display(), which have no date filter and instead bound the set by the order stage `done` flag and the session state. An order open across midnight is therefore in the screen set but not in the badge set. Conversely reset() marks the current stage done, which drops the order from the screen set, but the badge only skipped orders whose latest stage is the final stage, so an order reset while still in the first stage stayed counted. opw-6414302
The Point of Sale customer balance now shows the correct amount when the company currency differs from the PoS currency. This prevents Pay Later orders from being converted twice, avoiding understated customer dues and payment follow-up errors.
Original PR description
Steps to reproduce: - set the company currency to XCG - set the PoS sales journal currency and the PoS pricelist currency to USD - configure a XCG <-> USD rate - create a customer without any…
Steps to reproduce: - set the company currency to XCG - set the PoS sales journal currency and the PoS pricelist currency to USD - configure a XCG <-> USD rate - create a customer without any outstanding balance - open the PoS, create an order of USD 100 and validate it with the Customer Account (Pay Later) payment method - open the Customers screen and look at the Total Due of that customer Issue: The Total Due shows about USD 55.56, i.e. the amount converted once too many, instead of the expected USD 100. Cause: get_total_due() sums two amounts that are not expressed in the same currency before converting them. partner.total_due comes from the accounting entries, it is the sum of account.move.line.amount_residual and is therefore in company currency, while total_settled is the sum of pos.payment.amount of the still open sessions, which is in the currency of the order, so the PoS one. The addition is done first and the result is then converted from the company currency to the PoS one, so the pay later payments end up converted a second time. opw-6403320
Swedish SIE4 general ledger exports now use the actual configured fiscal year dates instead of assuming every fiscal year lasts exactly one year. This prevents exported reporting periods from being misstated when a company has shortened or extended fiscal years, improving accuracy for accounting and compliance.
Original PR description
## Issue: Exporting the general ledger as SIE4 set the duration of fiscal years to one year from the starting date of the fiscal year. ## Steps to reproduce: - Create a fiscal year A of one month for…
## Issue: Exporting the general ledger as SIE4 set the duration of fiscal years to one year from the starting date of the fiscal year. ## Steps to reproduce: - Create a fiscal year A of one month for year X-1 (December 1st to December31th year X-1) - Create a fiscal year B of 1 year and 1 month (January 1st Year X to January 31th year X+1) - Create Invoices in November year X-1, December year X-1, year X and in January year X+1 and confirm them - Go to General Leder - Set date to the fiscal year B - Export as SIE4 ### Current behavior: - **for previous fiscal year** - declared fiscal year (#RAR field) goes : - from fiscal year X date_from -1 year - to fiscal year X date_to -1 year - However data are computed: - from fiscal year X date_from -1 year - to fiscal year X date_from -1 day - **for current fiscal year** - declared fiscal year (#RAR field) goes : - from fiscal year X date_from - to fiscal year X date_to - However data are computed: - from fiscal year X date_from - to fiscal year X date_from +1 year ### Expected behavior: Declared fiscal year match the one that is use for computation. - for previous fiscal year - from fiscal year X-1 date_from - to fiscal year X date_from -1 day - for current year - from fiscal year X date_from - to fiscal year X date_to Cause: Current year length was wrong because it [relied on](https://github.com/odoo/enterprise/blob/22b4100006ec24bf6e4b64042cbfdba360fcf470/l10n_se_sie4_export/models/account_general_ledger.py#L139-L140) the next_date_from which was wrong. opw-6264766 Forward-Port-Of: odoo/enterprise#125354