Tuesday, September 22, 2026
18 changes · 18.0
New functionality added to Odoo
Swiss payroll now supports net compensation variants for insurance-paid daily allowances, helping employers keep an employee's net pay the same during events such as sickness, accident, maternity, military service, disability, or loss of earnings. The system automatically calculates the employer adjustment needed, while preserving manual overrides and matching accounting setup to the standard allowance types.
Original PR description
Daily allowances paid by an insurance (sickness, accident, maternity, military service, disability, loss of earnings) are entered as third party payments: the automatic 2050 line deducts them from…
Daily allowances paid by an insurance (sickness, accident, maternity, military service, disability, loss of earnings) are entered as third party payments: the automatic 2050 line deducts them from the payslip so the employee is not paid twice, and takes them out of the insurance bases. Because that insurance money is free of social contributions, the deductions no longer match a normal month and the final net drifts away from what the employee normally receives. Some employers promise the employee the exact same net as if nothing had happened. This commit adds a "(Net compensation)" variant of each daily allowance wage type to express that promise: when one of them is used, the payslip computes wage type 4900 (Net/Gross compensation) on its own, with the amount that brings the final net back to exactly what it would be without the allowance lines. The employer takes over, or gets back, the whole difference in contributions and source tax. There is no direct formula for that amount, since the 4900 line changes its own deductions (source tax brackets, insurance ceilings, 5 cents rounding). The payslip is therefore recomputed a few times with different amounts until the net matches, each attempt running inside a database savepoint that is rolled back afterwards, so nothing of those attempts is kept. A manual 4900 input keeps priority over the automatic computation, the same way a manual 2050 input does. The new wage types get the same accounting mapping as their normal counterparts. Forward-Port-Of: odoo/enterprise#128243
Enhancements to existing features
This update adds a missing automated test to confirm that imported invoices correctly handle approximate tax matching. It helps reduce the risk of invoice import errors when tax details are not an exact match.
Original PR description
A TC was missing to test fuzzy tax logic on invoice import related-task-id-6307992
Resolved issues and error corrections
This fixes an inventory issue where users splitting dropshipped products by lot could be shown a misleading "Pick From" option. The change ensures the lot number and expiration date entered on the operation line are the values used, preventing silent delivery of the wrong lot details.
Original PR description
Steps to reproduce 1. In Inventory Settings, enable Lots & Serial Numbers and Expiration Dates. 2. Create a storable product tracked by lot with Expiration Date enabled. 3. Create and confirm a…
Steps to reproduce 1. In Inventory Settings, enable Lots & Serial Numbers and Expiration Dates. 2. Create a storable product tracked by lot with Expiration Date enabled. 3. Create and confirm a dropship of it, then open the move's Detailed Operations. 4. The pre-filled line works: typing a batch and an expiration date creates exactly that lot at validation. 5. To split the quantity, click "Add a line": it opens the "Pick From" popup, and choosing "Create" there creates the lot and attaches it to the line. 6. On that line, edit "Lot/Serial Number" and "Expiration Date", then Validate. -> Expected: the values entered on the line are used. -> Actual: they are ignored; the lot created through "Pick From" is delivered with its own expiration date, the name and date typed on the line have no effect. Issue --- On a create-lots-only non-incoming operation like `Dropship`, `show_quant` is derived from `picking_code` alone, so the "Pick From" quant picker is shown even though the operation only creates lots. Adding a line goes through "Pick From", which creates the lot and attaches its `lot_id` to the move line; from then on the line's `lot_name` and `expiration_date` stay editable but do nothing, since a set `lot_id` is used as-is at validation and the `expiration_date` is recomputed from the lot, so anything typed there is silently dropped. Such an operation has no existing stock to pick from, so `show_quant` is now gated on the lot settings too: "Pick From" is hidden and the line's `lot_name` and `expiration_date` become the only inputs, created into the lot at validation like receipts already do. https://github.com/odoo/odoo/blob/68dcb950df83d70ff2aea0e05c96cc9b57c1a8a9/addons/stock/models/stock_move.py#L646-L647 opw-6530563
Helpdesk SLA deadlines now correctly ignore time spent in stages that are excluded from an SLA policy, including when a ticket starts in one of those stages. This prevents deadlines from being set too early and gives teams a more accurate view of SLA commitments.
Original PR description
**Problem:** An SLA policy that excludes some stages should not count the time a ticket spends in those stages towards its deadline. When the excluded stage is the one the ticket is created in, that…
**Problem:** An SLA policy that excludes some stages should not count the time a ticket spends in those stages towards its deadline. When the excluded stage is the one the ticket is created in, that waiting time is not deducted and the deadline is too early. **Steps to reproduce:** 1. In a Helpdesk team using SLA policies, create an SLA whose target stage is a later stage and that excludes the first stage. 2. Create a ticket and leave it in the first (excluded) stage a while. 3. Move it to the next stage. 4. Check the SLA deadline. **Current behavior:** The deadline is the creation date plus the SLA time only, ignoring the time spent in the excluded starting stage. **Expected behavior:** The deadline should be pushed back by the time spent in the excluded stage, as it already is for stages excluded later in the ticket's life. **Cause of the issue:** `_get_freezed_hours` rebuilds the stage history from the ticket's `message_ids.tracking_value_ids`. The deadline is recomputed while the stage change is being written, but the tracking value for that change does not exist yet at that point. When the ticket leaves its creation stage there is no earlier tracking value either, so the method returns 0 and no freeze time is added. **Fix:** The stage the ticket is leaving is read from the pending tracking values (`cr.precommit.data`), the same source the duration mixin uses to bridge this exact gap. This makes the freeze time available while the stage change is being written, so it is added to the deadline whether or not that stage's entry was already tracked. opw-6352099
This update cleans up how website project forms are processed, making the underlying logic easier to maintain for future changes. The change is internal and should help reduce the risk of issues when the form workflow is updated later.
Original PR description
Clean up and move some of the form processing logic for future updates opw-6560036
Kenyan eTIMS reporting now excludes taxes that do not have a KRA tax code from the reported tax-inclusive total. This prevents levies owed to other authorities, such as tourism levies, from being incorrectly reported to the Kenya Revenue Authority.
Original PR description
Issue: Non KRA taxes are sent in eTIMS tax total Steps to reproduce: - With l10n_ke_edi_oscu - Create a 2% Tax named CTL with no KRA Tax Code - Create an invoice - Add a line with 16% and CTL taxes - Confirm - Send to eTIMS Current behavior: - eTIMS JSON (unaccessible) is created with 'totAmt' (total_tax_included) including non-KRA Taxes Expected Behavior! - eTIMS JSON 'totAmt' doesn't incldue non-KRA Taxes Note: eTIMS is used to report to Tax Autority, however some "taxes" (e.g. Tourism levy) are paid to other authorities (e.g. Tourism Fund) and shouldn't be included in eTIMS. opw-6456478
The update fixes an issue that could prevent Belgian payroll accounting demo data from loading through the interface. This helps users evaluating or testing the Belgian payroll setup avoid setup errors caused by the wrong company context.
Original PR description
Before the fix, loading the module's demo data through the UI triggered an error. task-6581013
This fixes an issue where email links copied or pasted into the HTML editor could lose their link behavior or be inserted as plain text. Users can now paste mail links more reliably, reducing broken links in edited content.
Original PR description
**Current behavior before PR:** **Issue 1:** Steps to reproduce the issue: - Create a mail link in editor. - Copy link via link popover. - Paste it anywhere in editor. - Click on it to open popover. Notice that newly created link is not a correct mail link. **Issue 2:** Steps to reproduce the issue: - Copy a mail URL from google docs or from some other website. - Paste the copied link in editor. Notice that URL is pasted as plain text instead of link. This happens because when a link is copied with HTML content, `handlePasteText` is called after `handlePasteHtml`, and it ends up being pasted as html content. **Desired behavior after PR is merged:** This PR aims to ensure that mail URLs are being pasted correctly in editor. task-6462998 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Fixes a server error that could appear when users saved a form with new related lines, then continued editing without reloading. The system now handles temporary line references correctly, so users can keep working without interruption or needing to refresh the page.
Original PR description
### Problem The web client refers to a line it has not saved yet by a virtual id, a string such as `"virtual_42"`. It may send that id back to the server in an `UPDATE` or a `SET` command on an…
### Problem
The web client refers to a line it has not saved yet by a virtual id, a string such as `"virtual_42"`. It may send that id back to the server in an `UPDATE` or a `SET` command on an x2many field, for instance after saving a record and editing the same form again without reloading it.
`Base.onchange` adds that string to the ids it prefetches for the lines, and PostgreSQL rejects the query:
```
File "/odoo/addons/web/models/models.py", line 936, in onchange
lines.fetch(sub_fields_spec.keys())
...
psycopg2.errors.InvalidTextRepresentation: invalid input syntax for type integer: "virtual_42"
LINE 1: ...res_partner" WHERE ("res_partner"."id" IN (64534, 'virtual_4...
```
The record is saved, so no data is lost, but the user gets a server error on the next change and has to reload the page.
**Steps to reproduce**
1. Open a contact, add a new line under *Contacts & Addresses*, and save.
2. Keep editing the same form without reloading the page.
3. The next onchange returns the error above.
### Fix
Only `CREATE` can carry a virtual id, as the `ref` of a new record. `NewId()` reads such a string as an *origin* instead, so filtering the prefetch is not enough: the id reaches the database a few frames later, through the cache.
The commands are normalized before they are used: an `UPDATE` on a line that is not saved yet becomes a `CREATE` with the same ref, and `SET` and `LINK` drop the ids they cannot resolve.
Unchanged in 18.0, saas-18.4, 19.0 and master.
I hereby agree to the terms of the CLA available at: https://www.odoo.com/claThis fix resets the website editor's page structure data when switching views, preventing outdated layout information from being reused. It helps ensure editors see and work with the correct page content after toggling views.
Original PR description
Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
The attachment button on Job pages now opens correctly instead of triggering an error. This improves reliability for HR teams using referrals and job records to access related documents.
Original PR description
Issue: ---------------------------------------- When clicking the attachment button from a Job page there is a traceback. Cause: ---------------------------------------- The domain of the action have a Query inside it. The communication with the frontend transforms it into a String which then becomes unreadable by the ORM when the domain is applied. Solution: ---------------------------------------- Apply the query and input the resulting ids in the domain. opw-6446049 Forward-Port-Of: odoo/enterprise#132390
This change restores the French PDP e-invoicing profile so Odoo can send the extended French UBL format again. It aligns Odoo with updated platform-side mapping, helping French electronic invoices and related credit notes use the expected format.
Original PR description
This reverts commit aae3830fde3bc7727c228bf9e9dbf3dbd293acd1. The mapping was updated on the iap side meaning we can send extended-ctc-fr ubl now. opw-6420924
This fixes Saudi e-invoicing submissions involving 0% VAT and down payments so invoices are less likely to be rejected by ZATCA. It restores the correct taxable amount handling and ensures down payment lines are reported with the expected positive value.
Original PR description
Commit: https://github.com/odoo/odoo/commit/68343a56ef0e727c8ac0be4875bedd0fb7460d04 added the possibility of having a negative value for taxable amount in zatca xml to fix this warning: [202]…
Commit: https://github.com/odoo/odoo/commit/68343a56ef0e727c8ac0be4875bedd0fb7460d04 added the possibility of having a negative value for taxable amount in zatca xml to fix this warning: [202] BR-O-08 : [BR-O-08]-In a VAT breakdown (BG-23) where the VAT category code (BT-118) is ' Not subject to VAT' the VAT category taxable amount (BT-116) shall equal the sum of Invoice line net amounts (BT-131) minus the sum of Document level allowance amounts (BT-92) plus the sum of Document level charge amounts (BT-99) where the VAT category codes (BT-151, BT-95, BT-102) are 'Not subject to VAT'. It was removed by https://github.com/odoo/odoo/commit/46ede829705dd043d60e4928d14a1ccfa71ffe9b but this was not the right approach. This commit reintroduce https://github.com/odoo/odoo/commit/68343a56ef0e727c8ac0be4875bedd0fb7460d04 This commit also fix the error when sending an invoice with a 0% or a sale order with a down payment: - Create a sale order with a 0%. - Create a downpayment and process it by zatca - Process the delivery on the SO and create the final invoice. - The invoice will be rejected by zata with the error: BR-KSA-80 If Pre-Paid amount (BT-113) is provided then the Pre-Paid amount (BT-113) must equal to the sum total of the Prepayment VAT category Taxable Amount (KSA-31) and Prepayment VAT Category Tax Amount (KSA-32) If the line is a downpayment, then we take the absolute value. opw-5072577 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283009
Messages now show a more accurate visible record name based on the related record. This helps users recognize the context of messages more easily and reduces confusion when reviewing conversations or records.
Original PR description
Adapt the way we compute the visible record name to the related record. opw-6560141
Resetting an expense report now also clears any Studio approval already granted for posting journal entries. This prevents old approvals from being reused after a report is rolled back, ensuring the required approval is requested again.
Original PR description
Currently, when resetting to draft an expense sheet, approval steps added with studio are not reset, silently granting the change Steps to reproduce: - In Studio, add an approval rule on the "Post Journal Entries" button on expense sheet - Create an expense report, submit it and approve it - Click "Post Journal Entries" and approve approve it - Open the journal entry and reset it to draft - Back to the expense sheet, reset it to draft too Issue: The "Post Journal Entries" button is still approved, showing the previous approver with the same approval date. Analysis: Approval entries are dropped on state change by a base automation that Studio builds when the rule is created. However it is only done for sale order, account move and purchase order. opw-6530689
The Mexican payroll contract form now shows the wage period label that matches the selected pay schedule. This prevents users from seeing a misleading weekly or monthly caption and helps them enter the correct wage amount.
Original PR description
Issue: On the Mexican localization the caption shown next to the wage on the contract form did not match the selected "Schedule Pay": a contract set to be paid monthly could still display the wage as…
Issue: On the Mexican localization the caption shown next to the wage on the contract form did not match the selected "Schedule Pay": a contract set to be paid monthly could still display the wage as "/ weekly", which is misleading and led users to enter a weekly minded amount for a wage the engine actually treats as a monthly base. Steps to reproduce: - Install the l10n_mx localization - Open a Mexican contract and set Schedule Pay to Monthly - Check the caption displayed next to the Wage field Cause: The wage period caption is driven by the base schedule_pay field, while MX contracts drive their frequency through `l10n_mx_schedule_pay_temp` and keep the base field hidden. When both get out of sync the caption reflects the hidden base value instead of the frequency the user selected. https://github.com/odoo/enterprise/blob/5f9cf56372a7faa1bce55c418454d3aacbff04c4/hr_payroll/views/hr_contract_views.xml#L36-L48 Solution: Drive the wage caption from `l10n_mx_schedule_pay_temp` for Mexican contracts so it always matches the schedule pay actually selected, and hide the base schedule_pay captions for MX to avoid the mismatch. opw-6564087
Fixed an issue where some printed GS1 lot barcodes could include an invisible extra character that caused inventory scans to crash. This improves reliability for warehouse teams scanning lot and serial number labels during inventory counts.
Original PR description
When scanning a barcode with elements in stock the gs1 barcode generated with have a gosh trailing control character \x00 that will create a traceback when scanned. Steps to reproduce:…
When scanning a barcode with elements in stock the gs1 barcode generated with have a gosh trailing control character \x00 that will create a traceback when scanned. Steps to reproduce: ------------------- * Enable GS1 * Create a Product tracked by lot, with barcode 11614141111417 * Create a lot 1111 for this product with units in stock * Inventory>Products>Lots / Serial Numbers - select the lot number and print it * From barcode > Inventory Count > scan the printed barcode -> it will trigger a traceback because of a trailling \x00 character Observation: ------------- **Create the DataMatrix** When downloading the barcode it will call report_download from there it will render the template: https://github.com/odoo/odoo/blob/d4f592a6b81b83543fcbf644564b992adddc8684/addons/web/controllers/report.py#L120 The template is stock.report_lot_label where it will print the barcode as a ECC200DataMatrix: https://github.com/odoo/odoo/blob/90ee4710064f39086150045ee3f82023eafb224f/addons/stock/report/report_lot_barcode.xml#L34 The final barcode comes from the view directly: https://github.com/odoo/odoo/blob/90ee4710064f39086150045ee3f82023eafb224f/addons/stock/report/report_lot_barcode.xml#L25-L26 To create the barcode image it will use createBarcodeDrawing from reportlab: https://github.com/odoo/odoo/blob/d4f592a6b81b83543fcbf644564b992adddc8684/odoo/addons/base/models/ir_actions_report.py#L721 **Scan the barcode** - Scanning from Inventory Count: When scanning the barcode it will use detectCode: https://github.com/odoo/odoo/blob/bbbd7b6581c1c84d655b1ad96fdab3bd9e40e3c5/addons/web/static/src/core/barcode/barcode_video_scanner.js#L152 where in our case the incorrect character is already present is code.rawValue. Since we scanned from the count inventory, it will directly recognise the nomenclature and decompose it as a gs1 barcode, during this process there is a small sanitization of the value in cleanBarcode: https://github.com/odoo/odoo/blob/3c34811649b9454a58015f5521d63bdebb5b857d/addons/barcodes_gs1_nomenclature/static/src/js/barcode_parser.js#L157-L167 But it doesn't clean the final character. It will try to process the barcode, where it will retrieve the record from cache: https://github.com/odoo/enterprise/blob/242111580da73aa432ff87b3c14a95441ea643b0/stock_barcode/static/src/models/barcode_model.js#L735-L738 Since the final character is still present, it will send it and this will trigger an error: https://github.com/odoo/enterprise/blob/068dc8a241150ac2a05791482ab7549596df8816/stock_barcode/controllers/stock_barcode.py#L185 - Scanning from main barcode page dialog: This process is a little different if we scan directly from the main barcode page, when clicking on the barcode image on the main page, it will openManualBarcodeDialog: https://github.com/odoo/enterprise/blob/c98e5b092481d6334f38c1fbbcfb00e453add958/stock_barcode/static/src/main_menu/main_menu.xml#L24-L25 From there when it detectCode it will call _onBarcodeScanned using the Dialog, barcodeDetected>onResult>onResult: https://github.com/odoo/enterprise/blob/b2e8429065202cd3c788bf4f1da8559ca75dbe93/stock_barcode/static/src/main_menu/main_menu.js#L57-L62 and it will make an rpc call to scan_from_main_menu: https://github.com/odoo/enterprise/blob/adc40f2ee0cdbcaf5d6a2a26dc5a3a949aaf7559/stock_barcode/static/src/main_menu/main_menu.js#L98-L99 that will trigger a similar error. **Reportlab** ReportLab always encodes Data Matrix payloads using C40 encoding, that encoding is documented in ISO/IEC 16022 §7.2.5.3. the c40 encoding works by: 1) converting the data values into c40 value or pair of values 2) grouping c40 value by three 3) packing each triplet into two codewords. The ReportLab implementation correctly performs the C40 packing but does not correctly implement the end-of-data rules - Rule b: if two C40 values remain, append a Shift 1 value (0) to complete the final triplet. - Rules c and d: if only one C40 value remains, the final character must be encoded using ASCII (with or without an explicit unlatch, depending on the remaining symbol capacity). The current ReportLab implementation applies the behavior from rule b to the use case of rule "c" and "d". Which mean it add two Shift 1 value (0) to complete the triplet, as a result the final it decode into an incorrect character. Also an interesting point to notice is that in this implementation the size is harcoded to 144 (cw_data), this means it falls into the "larger symbol size" case and can ignore several edge cases where the limited size matters (like difference between rule "c" and "d", which it's also not implemented). Since it add padding, before the padding it unlatch (value 254). opw-6268062
Fixed an issue where certain printed GS1 lot barcodes could include an invisible trailing character that caused barcode scans to fail. Inventory users can now scan affected lot labels without triggering an error, improving reliability during stock counts.
Original PR description
When scanning a barcode with elements in stock the gs1 barcode generated with have a gosh trailing control character \x00 that will create a traceback when scanned. Steps to reproduce:…
When scanning a barcode with elements in stock the gs1 barcode generated with have a gosh trailing control character \x00 that will create a traceback when scanned. Steps to reproduce: ------------------- * Enable GS1 * Create a Product tracked by lot, with barcode 11614141111417 * Create a lot 1111 for this product with units in stock * Inventory>Products>Lots / Serial Numbers - select the lot number and print it * From barcode > Inventory Count > scan the printed barcode -> it will trigger a traceback because of a trailling \x00 character Observation: ------------- **Create the DataMatrix** When downloading the barcode it will call report_download from there it will render the template: https://github.com/odoo/odoo/blob/d4f592a6b81b83543fcbf644564b992adddc8684/addons/web/controllers/report.py#L120 The template is stock.report_lot_label where it will print the barcode as a ECC200DataMatrix: https://github.com/odoo/odoo/blob/90ee4710064f39086150045ee3f82023eafb224f/addons/stock/report/report_lot_barcode.xml#L34 The final barcode comes from the view directly: https://github.com/odoo/odoo/blob/90ee4710064f39086150045ee3f82023eafb224f/addons/stock/report/report_lot_barcode.xml#L25-L26 To create the barcode image it will use createBarcodeDrawing from reportlab: https://github.com/odoo/odoo/blob/d4f592a6b81b83543fcbf644564b992adddc8684/odoo/addons/base/models/ir_actions_report.py#L721 **Scan the barcode** - Scanning from Inventory Count: When scanning the barcode it will use detectCode: https://github.com/odoo/odoo/blob/bbbd7b6581c1c84d655b1ad96fdab3bd9e40e3c5/addons/web/static/src/core/barcode/barcode_video_scanner.js#L152 where in our case the incorrect character is already present is code.rawValue. Since we scanned from the count inventory, it will directly recognise the nomenclature and decompose it as a gs1 barcode, during this process there is a small sanitization of the value in cleanBarcode: https://github.com/odoo/odoo/blob/3c34811649b9454a58015f5521d63bdebb5b857d/addons/barcodes_gs1_nomenclature/static/src/js/barcode_parser.js#L157-L167 But it doesn't clean the final character. It will try to process the barcode, where it will retrieve the record from cache: https://github.com/odoo/enterprise/blob/242111580da73aa432ff87b3c14a95441ea643b0/stock_barcode/static/src/models/barcode_model.js#L735-L738 Since the final character is still present, it will send it and this will trigger an error: https://github.com/odoo/enterprise/blob/068dc8a241150ac2a05791482ab7549596df8816/stock_barcode/controllers/stock_barcode.py#L185 - Scanning from main barcode page dialog: This process is a little different if we scan directly from the main barcode page, when clicking on the barcode image on the main page, it will openManualBarcodeDialog: https://github.com/odoo/enterprise/blob/c98e5b092481d6334f38c1fbbcfb00e453add958/stock_barcode/static/src/main_menu/main_menu.xml#L24-L25 From there when it detectCode it will call _onBarcodeScanned using the Dialog, barcodeDetected>onResult>onResult: https://github.com/odoo/enterprise/blob/b2e8429065202cd3c788bf4f1da8559ca75dbe93/stock_barcode/static/src/main_menu/main_menu.js#L57-L62 and it will make an rpc call to scan_from_main_menu: https://github.com/odoo/enterprise/blob/adc40f2ee0cdbcaf5d6a2a26dc5a3a949aaf7559/stock_barcode/static/src/main_menu/main_menu.js#L98-L99 that will trigger a similar error. **Reportlab** ReportLab always encodes Data Matrix payloads using C40 encoding, that encoding is documented in ISO/IEC 16022 §7.2.5.3. the c40 encoding works by: 1) converting the data values into c40 value or pair of values 2) grouping c40 value by three 3) packing each triplet into two codewords. The ReportLab implementation correctly performs the C40 packing but does not correctly implement the end-of-data rules - Rule b: if two C40 values remain, append a Shift 1 value (0) to complete the final triplet. - Rules c and d: if only one C40 value remains, the final character must be encoded using ASCII (with or without an explicit unlatch, depending on the remaining symbol capacity). The current ReportLab implementation applies the behavior from rule b to the use case of rule "c" and "d". Which mean it add two Shift 1 value (0) to complete the triplet, as a result the final it decode into an incorrect character. Also an interesting point to notice is that in this implementation the size is harcoded to 144 (cw_data), this means it falls into the "larger symbol size" case and can ignore several edge cases where the limited size matters (like difference between rule "c" and "d", which it's also not implemented). Since it add padding, before the padding it unlatch (value 254). opw-6268062