Daily updates from Odoo
Friday, June 12, 2026
15 changes · 19.0
New functionality added to Odoo
This update introduces a new rule for calculating superannuation guarantee payments in Australia, aligning with Australian Taxation Office (ATO) requirements. Specifically, it incorporates 'Qualifying Earnings' (QE) as the primary source for superannuation contributions, replacing previous calculations from Ordinary Time Earnings (OTE) starting July 1st, 2026. This ensures accurate and compliant superannuation reporting.
Original PR description
Added new salary rule for Qualifying earnings. Super Streams now per payrun. task-6012509
Enhancements to existing features
This pull request introduces support for classifying and tracking prophylactic leave (maternity protection and health risk) within the payroll system for Belgian companies. It creates two new unpaid leave categories, aligning with local regulations and providing accurate payroll reporting. This improves compliance and reporting accuracy.
Original PR description
Divided existing prophylactic leave into two categories: maternity protection -> LEAVE14849 / dmfa_code: 51 health risk -> LEAVE225 (existing) / dmfa_code: 53 Added both as unpaid in the payroll structure PR community: task-6285834
This update enhances the clarity of bank statements when transactions are split into multiple lines. By adding transaction category data, Odoo now provides more descriptive labels for each line, closely matching the details from CodaBox. This makes it easier for users to understand and track their financial activity.
Original PR description
Currently, when global transaction is split into multiple lines, Odoo assigns the exact same communication text to every single split line. This makes it difficult for users to identify what each specific charge is for. To fix this, this commit introduces the transaction category data. Using this data to append specific transaction details to the end of the communication label. As a result, each split line now has a clear, descriptive label that closely matches the detailed breakdown provided by CodaBox. task-6059709
This update enhances the tracking of Lazada orders by automatically logging the reasons for skipped synchronization. Previously, users had to manually investigate order details to determine the cause. Now, clear logging provides better visibility and simplifies troubleshooting for synchronization issues.
Original PR description
Before this commit, the only way to know why an order was not synchronized was to inspect the order details and infer the reason from the code. This commit now logs those reasons.
Resolved issues and error corrections
This update resolves a requirement from Luxembourg auditors regarding the classification of partners in our SAFT reports. Specifically, it ensures that less than 30% of transactions with payable or receivable accounts have missing supplier or customer IDs, aligning with Luxembourg's FAIA reporting standards. The changes update XML reports to accurately reflect partner classifications.
Original PR description
This PR is one of many triggered by responses from Luxembourg auditors. See PR #113316 for a full list of these PRs. As described in PR #117799, the \CustomerID and \SupplierID elements on…
This PR is one of many triggered by responses from Luxembourg auditors. See PR #113316 for a full list of these PRs. As described in PR #117799, the \CustomerID and \SupplierID elements on \Transaction\Line elements is determined by a partner's `customer_rank` and `supplier_rank`. This is a binary designation, one or the other. The Luxembourg FAIA report requires that less than 30% of \Transaction\Line elements with payable accounts (class 6) can not have \SupplierID. The same applies for \Transaction\Line elements with receivable accounts (class 7) and the \CustomerID element. TSB clarified that any partner on an receivable or payable line should be added to the Customer list or Supplier list respectively https://github.com/odoo/enterprise/pull/100749#issuecomment-3655127511. In addition, I verified that Luxembourg's analysis of four separate FAIA files (from ticket 5427296) aligns with this expectation. <img width="1322" height="690" alt="image" src="https://github.com/user-attachments/assets/1a82f99e-5b32-4dbb-96e1-1b25bab2629b" /> This commit adds partners to the \Supplier and \Customer lists if they have any payable or receivable lines, respectively. It also picks between the \CustomerID and \SupplierID based on a line's `account_type`. This logic is applied to `account_saft` and updates the other, country-specific SAFT reports where appropriate. It also retains the previous `customer_rank` and `supplier_rank` logic as a fallback for older XML reports and for accounts other than `asset_receivable` or `liability_payable`. opw-6118024 Forward-Port-Of: odoo/enterprise#119098 Forward-Port-Of: odoo/enterprise#118714
This update resolves a bug that caused the Asset Depreciation Schedule report to crash when dealing with a large number of assets grouped by account. By adding a safeguard to handle missing data, the report now functions correctly even with significant asset volumes, ensuring accurate reporting for our customers.
Original PR description
#### Description of the issue/feature this PR addresses: Opening the Asset Depreciation Schedule report with a period comparison enabled crashes with KeyError: 'no_format' when prefix grouping is…
#### Description of the issue/feature this PR addresses:
Opening the Asset Depreciation Schedule report with a period comparison enabled crashes with KeyError: 'no_format' when prefix grouping is active (large number of assets in one account group). The report becomes unusable for affected customers.
#### Current behavior before PR:
_regroup_lines_by_name_prefix sums each subline column by indexing prefix_subline['columns'][i]['no_format'] directly. Empty columns are built as {} by _build_column_dict (both col_value and col_data are None), so they have no 'no_format' key. With a comparison period enabled, an asset that has no value in the comparison period produces an empty column for that period; once prefix grouping fires (len(lines) >= prefix_groups_threshold, default 4000), the direct lookup hits that empty dict and raises KeyError: 'no_format'.
#### Desired behavior after PR is merged:
The prefix group total treats a missing 'no_format' as 0, matching the sibling caller in account_asset/models/account_assets_report.py that already guards with .get('no_format', 0). The report builds without crashing and the empty comparison column contributes 0 to the prefix group total.
opw-6225639
Forward-Port-Of: odoo/enterprise#119775
Forward-Port-Of: odoo/enterprise#119088This update resolves an issue preventing valid vendor bills from being created in the GT accounting system. The system incorrectly restricted document types based on company affiliation. This change now allows all legally valid document types for purchase bills, ensuring accurate record-keeping and compliance.
Original PR description
Currently, the system restricts fiscal document types for both sales and purchases based on the company’s VAT affiliation, which prevents valid vendor bills from being recorded. **Steps to…
Currently, the system restricts fiscal document types for both sales and purchases based on the company’s VAT affiliation, which prevents valid vendor bills from being recorded. **Steps to reproduce:** - Install the `l10n_gt_edi` module and switch to a `GT company`. - Navigate to Accounting > Vendors > Bills. - Create a vendor bill. - Try to select a document type such as `FPEQ` or `FCAP`. **Observation:** The system hides valid vendor document types (e.g., `FPEQ`, `FCAP`) if they do not match the company’s VAT affiliation. **Root Cause:** At [1], the method `_compute_l10n_gt_edi_available_doc_types` filters document types using the company’s VAT affiliation (`l10n_gt_edi_vat_affiliation`) for all move types. This logic is correct for sales (where the company is the issuer), but incorrect for purchases (where the vendor determines the document type). As a result, valid purchase document types are wrongly excluded. **Fix:** This commit updates the computation logic to: - Apply affiliation-based filtering only for sales (`out_*`). - Bypass the restriction for purchases (`in_*`), allowing all valid document types. This ensures that vendor bills can include any legally valid document type regardless of the company’s affiliation, while preserving the existing restrictions for sales workflows. [1]: https://github.com/odoo/enterprise/blob/7792926504a823590fbbe574a96994002a92fc17/l10n_gt_edi/models/account_move.py#L162-L166 opw-6099863 Forward-Port-Of: odoo/enterprise#113133
The EDI download button was failing to provide the correct XML files when Carvajal encountered an error. This update adds a necessary callback to generate the XML content, ensuring users can now successfully download the required EDI documents for Carvajal integration. This resolves a critical issue preventing proper data transmission.
Original PR description
When Carvajal returns an error, the EDI document is created with the XML attachment correctly stored in attachment_id. However, the Download button in the EDI Documents tab uses the computed field edi_content, which internally looks for an 'edi_content' callback in _get_move_applicability(). Since l10n_co_edi never provided this key, the computed field always returned empty bytes, resulting in an empty file download. Add the edi_content callback pointing to _l10n_co_edi_generate_xml so the Download button serves the actual generated XML. The "Download" button returns an empty file instead of the generated XML sent to Carvajal. <img width="1376" height="765" alt="Captura de pantalla 2026-06-11 a la(s) 12 21 55 p m" src="https://github.com/user-attachments/assets/ac1b6e85-e67d-4021-8d9c-07a03a390245" />
This update resolves an issue where bank statement CSV imports were incorrectly multiplying amounts by 100. This was caused by a double-parsing of debit and credit fields when both the 'extract' and 'import' modules are installed. The fix ensures that imported amounts are parsed correctly, regardless of which modules are used.
Original PR description
Steps to reproduce --- 1. With Accounting installed, import a bank statement CSV that has separate Debit and Credit columns using number separators (e.g. a line with "1.234,56"). 2. Map the columns…
Steps to reproduce --- 1. With Accounting installed, import a bank statement CSV that has separate Debit and Credit columns using number separators (e.g. a line with "1.234,56"). 2. Map the columns to Debit and Credit and import. The imported amounts are multiplied by 100: "1.234,56" is imported as 123,456.00. Issue --- This only happens when both `account_bank_statement_import_csv` and `account_bank_statement_extract` are installed, which is the default in any Accounting database since both modules are auto-installed. `account_bank_statement_extract` turns debit and credit into real Monetary fields on `account.bank.statement.line`: https://github.com/odoo/enterprise/blob/af863c5a53d0ab50fe67cb9ea910391d4a1979dd/account_bank_statement_extract/models/account_bank_statement_line.py#L7-L8 Because they are now real fields, the generic importer already converts those columns to floats: https://github.com/odoo/odoo/blob/bfa39854e56da4bf23295d62f63d66973ad0d78e/addons/base_import/models/base_import.py#L1281-L1285 The CSV statement wizard then parses the same columns a second time: https://github.com/odoo/enterprise/blob/d7ab7ee1287342638006e290ede20b955aae8370/account_bank_statement_import_csv/wizard/account_bank_statement_import_csv.py#L92-L93 The first pass correctly reads "1.234,56" as "1234.56", but the second pass sees a lone dot, mistakes it for the thousands separator, strips it, and produces 123456. The wizard now parses debit and credit only when they are virtual fields, so when they are real fields the values parsed by the generic importer are reused instead of being parsed twice. Without `account_bank_statement_extract`, debit and credit exist only as virtual import fields, so the generic importer skips them and the wizard parses them once. That is why the regression stays hidden until the extract module is present. opw-6227083 ---
This update resolves an issue where attempting to create a new Global Invoice after canceling a refund related to a Mexican POS order would fail. The fix ensures that the refund's CFDI document is correctly updated, allowing for the creation of a new invoice. This prevents a common error and improves the functionality of the Mexican CFDI reporting process.
Original PR description
Steps to reproduce 1. With a Mexican POS configured, create a POS order and sign a Global Invoice for it. 2. Open a new session and partially return the order; close the session. 3. On the original…
Steps to reproduce 1. With a Mexican POS configured, create a POS order and sign a Global Invoice for it. 2. Open a new session and partially return the order; close the session. 3. On the original order, cancel the Global Invoice through the CFDI page. 4. Try to create a new Global Invoice for the original order. Issue The wizard raises "Orders <REFUND-NAME> are already sent or not eligible for CFDI." Validating the refund auto-signs an `invoice_sent` CFDI on the refund pos.order because its parent is `global_sent`, see `_l10n_mx_edi_check_autogenerate_cfdi_refund` at https://github.com/odoo/enterprise/blob/5af8048f0b0956a024d7eaeb10600eec74bdf3ee/l10n_mx_edi_pos/models/pos_order.py#L98. Cancelling the GI only flips its own document to `ginvoice_cancel`; the refund's `invoice_sent` doc stays untouched, so the refund's computed `l10n_mx_edi_cfdi_state` stays `'sent'`. The chain check in `_l10n_mx_edi_check_orders_for_global_invoice` at https://github.com/odoo/enterprise/blob/5af8048f0b0956a024d7eaeb10600eec74bdf3ee/l10n_mx_edi_pos/models/pos_order.py#L184 then rejects the refund as already sent and the new GI cannot be created. opw-6181136 Forward-Port-Of: odoo/enterprise#120100 Forward-Port-Of: odoo/enterprise#117211
This update fixes a bug where 401k matching contributions were incorrectly calculated for hourly employees with zero fixed wages. The fix ensures that matching contributions are accurately determined based on actual gross pay, providing correct retirement savings calculations for all employee types. This improves payroll accuracy and compliance.
Original PR description
*= test_l10n_us_hr_payroll_account The employer matching cap for pre-retirement plans (401KMATCHING) evaluates to zero for hourly wage employees if wage is set to zero. ### **Steps to Reproduce:** 1)…
*= test_l10n_us_hr_payroll_account The employer matching cap for pre-retirement plans (401KMATCHING) evaluates to zero for hourly wage employees if wage is set to zero. ### **Steps to Reproduce:** 1) Install l10n_us_hr_payroll. 2) Create an employee with an hourly wage and set the fixed wage to 0. 3) Configure the retirement plan parameters as follows: - 401(k) = 3% - Matching Amount = 100% - Matching Yearly Cap = 100% 4) Generate a payslip for this employee and compute the sheet. ### **Observed Behavior:** The "Benefits Matching to Retirement Plans" line computes as zero for the hourly employee. ### **Expected Behavior:** The employer matching contribution should dynamically scale based on the actual gross pay period earnings instead of evaluating to zero. ### **Root Cause:** The calculation of `partial_cap` uses `version.wage` directly at [1]. For hourly employees, the fixed 'wage' field defaults to zero, causing the entire multiplication to cancel out. [1]- https://github.com/odoo/enterprise/blob/4c540f450d4de8b59b871662123f85ed54cca2a9/l10n_us_hr_payroll/data/hr_salary_rule_data.xml#L167 ### **Fix:** This commit computes the retirement matching eligibility cap from `gross annualized wages` and applies the employer matching percentage on the eligible contribution amount. This ensures retirement matching is calculated consistently regardless of the employee's contract type. **opw-6181024**
This update resolves a technical issue where VoIP registration would fail with an error message when a user left a session open and inactive. The fix ensures that registration requests are properly handled and retried, preventing the appearance of error dialogs and maintaining a stable VoIP connection. It improves the user experience by ensuring consistent registration.
Original PR description
Leaving a session open and idle (page open, no activity at all) eventually pops an error dialog: UncaughtPromiseError > RequestPendingError REGISTER request already in progress, waiting for final…
Leaving a session open and idle (page open, no activity at all) eventually pops an error dialog:
UncaughtPromiseError > RequestPendingError
REGISTER request already in progress, waiting for final response
at Registerer.register (sip.js)
at Registerer.register (registerer.js)
at UserAgent.attemptReconnection (user_agent_service.js)
When the WebSocket transport drops while a REGISTER is in flight (which happens on an idle tab: SIP.js sends a periodic re-REGISTER before the registration expires, and the socket may be closed by an idle timeout or by the machine going to sleep in the meantime), the final response never comes back. SIP.js only clears its internal `waiting` flag from the REGISTER response callbacks (onAccept/onReject/onRedirect); it is never reset on transport loss or request timeout. The Registerer is then stuck `waiting` forever, and every subsequent register() rejects with a RequestPendingError.
On top of that, our wrapper's register() did not return the SIP.js promise, and attemptReconnection() called it without awaiting, so the rejection escaped the surrounding try/catch and surfaced as an unhandled promise rejection. Worse, the WebSocket error was resolved right after, so the user appeared reconnected while VoIP registration was actually dead until the page was reloaded.
This commit makes register() recreate the underlying SIP.js Registerer when it is stuck `waiting` (a clean instance starts with waiting=false), and return the promise so callers can await it. attemptReconnection() now awaits it, so any rejection goes through the existing retry/back-off logic instead of bubbling up as an uncaught error.
The recreation is intentionally conditional: disposing a healthy registerer would send an unregister (REGISTER expires=0) racing with the fresh register (expires=600) and could leave us unregistered, so we only recreate when a request is actually stuck.
Forward-Port-Of: odoo/enterprise#120107
Forward-Port-Of: odoo/enterprise#119701This update resolves an issue where manually added by-products on manufacturing orders caused errors during production closure. The fix ensures that serial numbers are correctly assigned when a by-product is added outside of the standard BOM definition, preventing user error messages and improving production workflow.
Original PR description
**Issue** Adding a serial-tracked by-product manually on a Manufacturing Order whose BOM does not define it, can lead to inconsistencies when assigning serial numbers in the shopfloor application.…
**Issue** Adding a serial-tracked by-product manually on a Manufacturing Order whose BOM does not define it, can lead to inconsistencies when assigning serial numbers in the shopfloor application. **Steps to reproduce** - Activate by-product in the settings - Create a product with an empty BOM (final product) - Create another product tracked by serial number (by-product) - Create and confirm a MO for the final product with 1 unit of the by-product - Go to Miscellaneaous -> operation Type -> shopfloor - Activate the option "Pre fill lot/serial numbers in shop floor" - Return to the MO and open the shopfloor view - Click on the '+' button next to the by-product and assign a serial number - Try to close the production -> A user error is raised stating that the by-product requires a serial number. **Cause** When the by-product is added manually on the MO, a stock move is created with an initial move line that does not contain any serial number. Later, when assigning a serial number from the shopfloor view: https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/mrp_workorder/models/stock_move.py#L121-L122 a new move line containing the serial number is created: https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/mrp_workorder/models/stock_move.py#L116-L119 However, the original empty move line is not removed (the issue): https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/mrp_workorder/models/stock_move.py#L124-L125 Because `self.picking_type_prefill_shop_floor_lots` is True, but `self.byproduct_id` is an empty recordset since: https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/addons/mrp/models/mrp_production.py#L1304-L1311 https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/addons/mrp/models/mrp_production.py#L1279 Indeed, `byproduct_id` is only populated from BOM-defined by-products. As a result, while confirming the production, there is 2 sml and among them, the original one without SN, which triggers the error: https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/addons/stock/models/stock_move_line.py#L590 https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/addons/stock/models/stock_move_line.py#L634-L635 https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/addons/stock/models/stock_move_line.py#L658-L659 https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/addons/stock/models/stock_move_line.py#L661-L669 opw-6223158
This update resolves an error that occurred when calculating payroll for employees with contracts exceeding 35 years. The fix adjusts a key parameter in the payroll rules to accommodate Mexican labor law, specifically allowing for additional holiday days based on seniority beyond 35 years. This ensures accurate payroll calculations for all employees.
Original PR description
**Steps to reproduce:** 1. Install l10n_mx_hr_payroll. 2. Create an employee with a contract date over 35 years ago (e.g., 1985). 3. Create a payslip for this employee. 4. Click on "Compute Sheet".…
**Steps to reproduce:**
1. Install l10n_mx_hr_payroll.
2. Create an employee with a contract date over 35 years ago (e.g., 1985).
3. Create a payslip for this employee.
4. Click on "Compute Sheet".
```Error: KeyError(36) while evaluating```
**Cause:**
The rule parameter [rule_parameter_holiday_table](https://github.com/odoo/enterprise/blob/c02c4571bb7db7197b07539ba390d4d20fdce9fe/l10n_mx_hr_payroll/data/hr_rule_parameters_data.xml#L722-L758) defines values
only up to 35 years. Seniority exceeding this range causes a KeyError.
**Solution:**
Extended the `rule_parameter_holiday_2024` table from 35 to 60 years,
following the Mexican Federal Labor Law (LFT) reform formula
(+2 days every 5-year milestone from year 6 onwards).
**NOTE:**(Alternative approach)
```python
@staticmethod
def _get_mx_holiday_days(years_worked):
if years_worked <= 0:
return 0
if years_worked <= 5:
return 12 + (years_worked - 1) * 2
five_year_periods = (years_worked - 6) // 5
return 22 + five_year_periods * 2
```
This approach removes the need for XML data maintenance and handles
all future seniority values mathematically without any cap issues.
opw-6090590
Forward-Port-Of: odoo/enterprise#113536This update fixes a potential issue where negative order quantities could be entered within certified point-of-sale configurations. The change prevents this from happening both in the backend system and on the user interface, ensuring accurate order tracking and reporting. This improves data integrity and reliability for our POS partners.
Original PR description
Certified pos configs should not allow to set negative quantities on order lines. We now prevent it from both backend and frontend. see odoo/odoo#269487 task-5942777