Wednesday, July 29, 2026
12 changes · saas-19.1
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#123387