Friday, May 22, 2026
13 changes · saas-19.2
Resolved issues and error corrections
This update resolves an issue where signed PDF documents lost their original bookmarks and links. The fix ensures that signed documents remain fully navigable and preserve the original document structure and integrity, improving user experience and data consistency.
Original PR description
Version - 18.0 Steps to reproduce: 1. Upload a PDF document containing bookmarks and internal/external links. 2. Sign the document and download the signed PDF. 3. Open the downloaded file and check the bookmarks and links. Issue: When a signed document was downloaded, the original PDF bookmarks And the links were not working. This broke structured navigation and affected document integrity. Fix: The PDF signing process has been updated to preserve the original bookmarks and ensure internal and external links remain functional after signing. Impact: - Signed documents remain navigable and consistent with the original PDF. - Preserves document structure and integrity. Task- 4915124 Forward-Port-Of: odoo/enterprise#117881 Forward-Port-Of: odoo/enterprise#108684
This update resolves an issue where discount lines were incorrectly splitting taxes, resulting in duplicate tax calculations when changing the fiscal position (e.g., from GST/QST to Quebec). The fix ensures accurate tax handling on discount lines, preventing incorrect tax reporting and improving order accuracy for Canadian customers.
Original PR description
Steps to produce: --- - Install `website_sale`, `l10n_ca` & `accountant` modules with demo data. - Switch to a `Canadian (CA) company.` - Go to Settings and enable` Discounts, Loyalty & Gift Cards`.…
Steps to produce: --- - Install `website_sale`, `l10n_ca` & `accountant` modules with demo data. - Switch to a `Canadian (CA) company.` - Go to Settings and enable` Discounts, Loyalty & Gift Cards`. - Go to` Website > eCommerce > Loyalty > Discount & Loyalty.` - Create a new program > Set Program Type to Discount Code > Under Conditional Rules, set Minimum Purchase to 0 > Under Rewards, choose Discount on Order. - Go to `website > configuration > websites` > Create a new website for the CA company > Set it as default (first in sequence). - Create new product > Set Sales Taxes to` 14.975% GST + QST` > Publish the product. - Open the website in an incognito window > Add the product to the cart > Apply the discount code. - In the main tab > Go to Website > eCommerce > Orders > Open the corresponding order > In the Other Info tab, change the fiscal position to Quebec (QC) > Click to update taxes. Issue: --- - The tax on the discount line is split into: 14.975% GST + QST & 9.975% QST. Root cause: --- - When a discount is applied in the cart, the discount line initially carries split taxes: 5% GST and 9.975% QST. - After changing the fiscal position to Quebec (QC), the system replaces 5% GST with 14.975% GST + QST because 5% GST is present in replace of 14.975% GST. so at [1] it replaces 5% GST with 14.975% GST and do nothing for 9.975% QST. - In 17.0, the discount line directly uses 14.975% GST + QST (no tax splitting), so this issue does not occur. - In 18.0, at [2], taxes are explicitly split and added to the base line, and the same split taxes are reused during grouping. This leads to multiple taxes being displayed on the sale order line. Fix: --- - Avoid splitting taxes on the discount line in the sale order. - Keep the original tax structure intact to prevent duplication after fiscal position changes. [1]https://github.com/odoo/odoo/blob/c6d9fa5873eb759846e9be5b66eedb8b00c5ac11/addons/account/models/partner.py#L151-L156 [2]https://github.com/odoo/odoo/blob/c6d9fa5873eb759846e9be5b66eedb8b00c5ac11/addons/sale_loyalty/models/sale_order.py#L296 opw-6145674 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#265233 Forward-Port-Of: odoo/odoo#262147
This update corrects a bug where the Point of Sale system incorrectly applied AvaTax fiscal positions even when AvaTax wasn't activated in the POS settings. The fix ensures that AvaTax fiscal positions are only used if AvaTax is actively enabled within the POS, aligning with the user's intended tax configuration. This prevents incorrect tax calculations during sales.
Original PR description
**Steps to reproduce:** - Install Accounting and Point of Sale - In Accounting settings, activate "AvaTax" - Configure the AvaTax fiscal position and activate "Detect Automatically" option - Make…
**Steps to reproduce:** - Install Accounting and Point of Sale - In Accounting settings, activate "AvaTax" - Configure the AvaTax fiscal position and activate "Detect Automatically" option - Make sure that the other fiscal positions don't have that option set or that they are ordered after the AvaTax one - Go to the settings of a point of Sale - Activate "Flexible Taxes" and configure "Default" and "Allowed" - Make sure that AvaTax fiscal position is not allowed - Do not activate "AvaTax PoS Integration" - Open a POS session - Select a customer with an address in the US and without fiscal position - Check the fiscal position **Issue:** The selected fiscal position is the AvaTax one even though AvaTax is not activated in the POS. **Cause:** We force the use of a fiscal position if it is configured on a customer. In this case, as no fiscal position is configured on the customer, we try to retrieve one that matches the condition and the AvaTax one is selected. **Solution:** When searching for the fiscal position of a customer, if AvaTax is not configured in the POS and if its fiscal positions are not allowed in POS, we ignore the fiscal positions using AvaTax. opw-6154089 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#263342
This update fixes an issue where the Point of Sale system incorrectly applied AvaTax fiscal positions even when AvaTax wasn't activated in the POS. The change ensures that if AvaTax isn't enabled, the system will ignore AvaTax fiscal positions when determining the correct tax settings for a customer, improving accuracy and preventing unexpected tax calculations.
Original PR description
**Steps to reproduce:** - Install Accounting and Point of Sale - In Accounting settings, activate "AvaTax" - Configure the AvaTax fiscal position and activate "Detect Automatically" option - Make…
**Steps to reproduce:** - Install Accounting and Point of Sale - In Accounting settings, activate "AvaTax" - Configure the AvaTax fiscal position and activate "Detect Automatically" option - Make sure that the other fiscal positions don't have that option set or that they are ordered after the AvaTax one - Go to the settings of a point of Sale - Activate "Flexible Taxes" and configure "Default" and "Allowed" - Make sure that AvaTax fiscal position is not allowed - Do not activate "AvaTax PoS Integration" - Open a POS session - Select a customer with an address in the US and without fiscal position - Check the fiscal position **Issue:** The selected fiscal position is the AvaTax one even though AvaTax is not activated in the POS. **Cause:** We force the use of a fiscal position if it is configured on a customer. In this case, as no fiscal position is configured on the customer, we try to retrieve one that matches the condition and the AvaTax one is selected. **Solution:** When searching for the fiscal position of a customer, if AvaTax is not configured in the POS and if its fiscal positions are not allowed in POS, we ignore the fiscal positions using AvaTax. opw-6154089 Forward-Port-Of: odoo/enterprise#116626
This update ensures that event tickets are automatically generated when a customer makes a payment in POS mode while offline. Previously, a page reload would cause the ticket creation to fail. The fix prevents data loss and guarantees that event registrations are created correctly, regardless of the POS session's online status.
Original PR description
When selling event tickets in POS while offline, the order could be synced later but without creating event registrations (tickets) after a page reload. Steps to reproduce: ------------------- * Open…
When selling event tickets in POS while offline, the order could be synced later but without creating event registrations (tickets) after a page reload. Steps to reproduce: ------------------- * Open a POS session with `pos_event` * Sell an event ticket * Switch to offline mode * Validate payment while offline (order becomes paid but unsynced) * Reload/close and reopen POS, then reconnect * Let the order sync > Observation: The `pos.order` is created on the backend, but `event.registration` and `event.registration.answer` are missing so tickets are not generated. Why the fix: ------------ `pos_event` used `order.finalized` as IndexedDB cleanup condition for `event.registration` and `event.registration.answer`. For paid-but-unsynced orders, `finalized` is already true, so those records can be removed from IndexedDB too early. After reload, the order is restored/synced but without its event registration payload. Implementation: --------------- Use `order.canBeRemovedFromIndexedDB` instead of `order.finalized` for `event.registration` and `event.registration.answer` retention rules, so records are kept locally until the order is truly synced (server id assigned) or canceled. Test Note: --------------- Use case is hard to simulate exactly. Add a basic unit test to assert both registration models are kept for paid unsynced orders and only removable once synced. opw-6056079 Forward-Port-Of: odoo/odoo#265201 Forward-Port-Of: odoo/odoo#256615
This update optimizes how Odoo searches for records linked to binary attachments. Previously, searching for 'false-ish' attachments resulted in slow queries due to a large list of attachment IDs. Switching to a 'NOT EXISTS' query significantly speeds up these searches, especially on databases with many attachments, leading to a faster and more responsive system.
Original PR description
Description of the issue/feature this PR addresses: Searching for records without a binary attachment (e.g., `('binary_field', '=', False)`) previously generated a query using `NOT IN (SELECT res_id…
Description of the issue/feature this PR addresses:
Searching for records without a binary attachment (e.g., `('binary_field', '=', False)`) previously generated a query using `NOT IN (SELECT res_id FROM ir_attachment...)`. On databases with a large `ir_attachment` table, materializing this entire list of IDs causes a significant performance bottleneck.
Replacing NOT IN with a NOT EXISTS allows PostgreSQL to short-circuit the evaluation as soon as it find a matching document, drastically reducing query execution time.
Current behavior before PR:
Searching for a "false-ish" binary with attachment generates a query with a `NOT IN`, slow when `ir_attachment` is large.
```python
>>> env["ir.ui.menu"].search([("web_icon_data", "!=", False)])
2026-03-06 15:59:51,326 516177 DEBUG odoo19 odoo.sql_db: [1.076 ms] query: SELECT "ir_ui_menu"."id" FROM "ir_ui_menu" WHERE ("ir_ui_menu"."active" IS TRUE AND "ir_ui_menu"."id" IN (SELECT res_id FROM ir_attachment WHERE res_model = 'ir.ui.menu' AND res_field = 'web_icon_data')) ORDER BY "ir_ui_menu"."sequence" , "ir_ui_menu"."id"
ir.ui.menu(15, 1, 16)
>>> env["ir.ui.menu"].search([("web_icon_data", "=", False)])
2026-03-06 15:59:54,439 516177 DEBUG odoo19 odoo.sql_db: [0.665 ms] query: SELECT "ir_ui_menu"."id" FROM "ir_ui_menu" WHERE ("ir_ui_menu"."active" IS TRUE AND "ir_ui_menu"."id" NOT IN (SELECT res_id FROM ir_attachment WHERE res_model = 'ir.ui.menu' AND res_field = 'web_icon_data')) ORDER BY "ir_ui_menu"."sequence" , "ir_ui_menu"."id"
ir.ui.menu(62, 68, 3, 10, 43, 59, 4, 28, 44, 65, 6, 7, 29, 41, 45, 61, 66, 5, 18, 30, 31, 48, 49, 60, 69, 70, 9, 11, 12, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32, 33, 34, 36, 37, 38, 39, 40, 42, 46, 47, 52, 54, 56, 57, 58, 63, 71, 73, 74, 76, 78, 79, 80, 81, 50, 64, 51, 72, 75, 77, 35, 14, 13, 53, 2, 55, 67, 8)
```
Desired behavior after PR is merged:
Searching for a "false-ish" binary with attachment generates a query with a `NOT EXISTS`
```python
>>> env["ir.ui.menu"].search([("web_icon_data", "!=", False)])
2026-03-06 15:59:04,847 513555 DEBUG odoo19 odoo.sql_db: [0.945 ms] query: SELECT "ir_ui_menu"."id" FROM "ir_ui_menu" WHERE ("ir_ui_menu"."active" IS TRUE AND "ir_ui_menu"."id" IN (SELECT res_id FROM ir_attachment WHERE res_model = 'ir.ui.menu' AND res_field = 'web_icon_data')) ORDER BY "ir_ui_menu"."sequence" , "ir_ui_menu"."id"
ir.ui.menu(15, 1, 16)
>>> env["ir.ui.menu"].search([("web_icon_data", "=", False)])
2026-03-06 15:59:08,323 513555 DEBUG odoo19 odoo.sql_db: [0.628 ms] query: SELECT "ir_ui_menu"."id" FROM "ir_ui_menu" WHERE ("ir_ui_menu"."active" IS TRUE AND NOT EXISTS (SELECT 1 FROM ir_attachment WHERE res_model = 'ir.ui.menu' AND res_field = 'web_icon_data' AND res_id = "ir_ui_menu"."id")) ORDER BY "ir_ui_menu"."sequence" , "ir_ui_menu"."id"
ir.ui.menu(62, 68, 3, 10, 43, 59, 4, 28, 44, 65, 6, 7, 29, 41, 45, 61, 66, 5, 18, 30, 31, 48, 49, 60, 69, 70, 9, 11, 12, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32, 33, 34, 36, 37, 38, 39, 40, 42, 46, 47, 52, 54, 56, 57, 58, 63, 71, 73, 74, 76, 78, 79, 80, 81, 50, 64, 51, 72, 75, 77, 35, 14, 13, 53, 2, 55, 67, 8)
```
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#265702
Forward-Port-Of: odoo/odoo#252525This update resolves an issue where removing a general note from a restaurant orderline caused the preparation display to incorrectly mark the line as cancelled and create a new one. The fix ensures that note history is recorded regardless of whether the note is confirmed, allowing the system to update existing orderlines instead of creating duplicates.
Original PR description
Steps to reproduce: --------- 1. Create an order with an orderline general note. 2. Send the order to the preparation display. 3. Remove the note from orderline. 4. Resend the order Issue: --------------- Removing the note changes the preparation line key, so the preparation display marks the old line as cancelled and creates a new one instead of updating the existing line. Cause: ----------- The note history was only recorded when the note was confirmed. If the user simply removes/clears the note, no note history entry is generated, so the backend cannot match the previous key with the updated key. Fix: ---------- Record note history even when the note is discarded (not only when confirmed. This allows the backend to match the old and new keys and update the line instead of cancelling it. Task-6101501 Related PR - https://github.com/odoo/odoo/pull/258632 Forward-Port-Of: odoo/enterprise#117943 Forward-Port-Of: odoo/enterprise#113514
This update resolves an issue where PDFs with multiple XML attachments (using the /Kids structure) weren't being correctly extracted in Odoo bills. The fix ensures that all embedded XML files are now properly recognized, preventing empty bills and ensuring accurate data retrieval. This improves the reliability of bill generation.
Original PR description
Steps to reproduce: - From the accounting dashboard, upload a PDF containing intermediate /Kids nodes representing separate xml attachments Issue: No xml will be extracted, as result the bill will be empty. However, in the chatter pdf preview, the js pdf toolkit correctly show the xml attachemnts. Analysis: The PDF spec defines two ways to organize embedded files under /EmbeddedFiles in the document's name dictionary: - /Names: a flat array of pairs located directly under /EmbeddedFiles - /Kids: an array of child nodes, each of which carries its own /Names array. The extractor currently only handled the /Names case, not detecting embedded attachments in case of PDF using a /Kids tree. This change add lookup for both structures. opw-5929274 Forward-Port-Of: odoo/odoo#255798 Forward-Port-Of: odoo/odoo#252523
This update resolves an issue where UBL invoices sent via Peppol were failing due to an incorrect VAT number format for Norwegian suppliers. The fix ensures the VAT number is correctly formatted ('NO179728982MVA') during invoice creation, allowing successful export and delivery.
Original PR description
**Steps to reproduce:** * Install a Norwegian localization (e.g. **l10n_no**). * Set up a company with a valid Norwegian VAT number (e.g. **NO179728982MVA** or just **179728982**). * Create a…
**Steps to reproduce:**
* Install a Norwegian localization (e.g. **l10n_no**).
* Set up a company with a valid Norwegian VAT number (e.g. **NO179728982MVA** or just **179728982**).
* Create a customer invoice and send it via **Peppol** (format: **UBL BIS Billing 3.0.12**).
**Observed behavior:**
* The EDI document creation fails with: "The VAT number of the supplier does not seem to be valid. It should be of the form: NO179728982MVA."
* The error occurs even when the VAT number is correctly formatted.
**Cause:**
* Commit 186ad1db refactored the party node building by removing `_get_party_node()` and replacing it with granular `_ubl_add_party_*_nodes()` methods. The Norwegian VAT normalization block (introduced in task-5448941) that set `supplierCompanyID` on the party node lived inside `_get_party_node()` and was not ported to the new architecture, leaving `supplierCompanyID` never set.
* The NO-R-001 constraint in `_invoice_constraints_peppol_en16931_ubl()` reads the VAT from `party_node.get('supplierCompanyID')`, which now always returns `None`, causing `mva.is_valid(None)` to return `False` and the constraint to always fail.
**Fix:**
* Port the missing normalization logic into `_ubl_add_accounting_supplier_party_tax_scheme_nodes()`: prepend `NO` and append `MVA` to the VAT if not already present, then assign the normalized value to `party_node['supplierCompanyID']` and update `PartyTaxScheme[0]/CompanyID` when a VAT node exists.
* Handle the `NO` case in `_ubl_add_party_legal_entity_nodes()` to write the normalized VAT into `PartyLegalEntity/CompanyID`.
opw-6215400This update resolves an issue where timesheet data wasn't consistently synchronized between Odoo tabs. The fix ensures that changes made in one tab's timesheet are accurately reflected in all other tabs, improving data accuracy and user experience. This was a bug related to how the system saved and retrieved timesheet data across different windows.
Original PR description
This PR reworks the implementation of https://github.com/odoo/enterprise/pull/116007 Task-6180394
This update fixes an error in how overtime hours are calculated for employees with flexible schedules. Previously, the system incorrectly generated excessive overtime hours. The fix ensures that overtime is accurately calculated based on the employee's actual working hours, addressing a discrepancy in the overtime rule logic.
Original PR description
__ ## Short functional explanation of the error When setting attendances on several consecutive days for a flexible employee, with an overtime ruleset containing a single rule. This rule being based…
__ ## Short functional explanation of the error When setting attendances on several consecutive days for a flexible employee, with an overtime ruleset containing a single rule. This rule being based on week and quantity. When regenerating overtimes for this ruleset, the overtime hours generated isn't correct. ## Reproduction Steps 1. Create an employee. In the Payroll tab, set a start date for their contract. Set Work Entry Source as Attendances. Set their Working Hours as a flexible schedule. Set their weekly hours at 40. 2. Create an Overtime Ruleset. Add a single rule, based on Quantity, if the worked hours on a `Week` differs `from the amount defined on the contract`. Check Pay Extra Hours and leave the Work Entry Type to use as Overtime Hours. 3. Go back to the employee. In Settings, set the Overtime Ruleset field as the new Overtime Ruleset you just created. 4. Create 5 attendances, each from 8 am to 6 pm, from Monday to Friday. 5. Go to the Overtime Ruleset you just created and click on Regenerate Overtimes. 6. Go back to Attendances. Search for your employee, and click on the list view. ### Expected behavior The employee's schedule is 40 hours per week. They worked 50 hours. 10 hours should be considered as Worked Extra Hours. ### Unexpected behavior 18 hours are considered as extra hours. ## Origin of the issue To compute the expected duration of the day, we run: https://github.com/odoo/odoo/blob/7fc5edc29f854d619dbcb5fcc3503fb18ca05335/addons/hr_attendance/models/hr_attendance_overtime_rule.py#L303-L304 where `schedule['work']` will contain intervals on 5 consecutive days, from 8 am to 4 pm. However, the last day of the employee's attendances isn't contained in these intervals. As a result, `period_schedule` will contain 4 days (the common days between the employee's Attendance days and `schedule['work']` ) and thus, `expected_duration` will be set at 36 hours instead of 40. In the case where overtimes are computed based on hours from the contract, for flexible employees, the expected hours are the ones indicated on their schedule. __ opw-6131543 Forward-Port-Of: odoo/odoo#263335
This update fixes an issue where barcode settings for Manufacturing Orders weren't consistently applied, allowing users to bypass mandatory scan requirements. The change ensures that barcode configurations, including lot/serial tracking, are correctly utilized when creating Manufacturing Orders through the Barcode app. This improves data accuracy and traceability in the production process.
Original PR description
Before this commit, the "Allow full order validation" were partially ignored in the Barcode app when used for Manufacturing Orders, and the "Mandatory scan" settings didn't work very well. For…
Before this commit, the "Allow full order validation" were partially ignored in the Barcode app when used for Manufacturing Orders, and the "Mandatory scan" settings didn't work very well. For example, setting the scan of lot/serial as mandatory didn't prevent the user to set automatically a SN on consummed component by generating a lot/serial on the produced product or by clicking on "Produce All" button. This commit adds some conditions to avoid to update barcode lines in case they should depending of the config. This commit also fixes a related issue where the MRP operation type's config wasn't used at all when a MO is created directly from the Barcode app. As the config is get from the MO's picking type and no MO exists when a new one is created from the Barcode app, there is no MO's config returned in the data send by the server. To fix that, the config is now updated clientside when the data are fetched after a save. [Task-5420762](https://www.odoo.com/odoo/project/966/tasks/4655907/project.task/5420762) [opw-5223507](https://www.odoo.com/odoo/project/49/tasks/5223507) Forward-Port-Of: odoo/enterprise#117816 Forward-Port-Of: odoo/enterprise#113318
This update fixes a problem where POS orders weren't correctly calculating payments and invoices, leading to errors. The fix ensures that order details are properly updated during validation and payment processing, resolving the 'entry not balanced' error when generating invoices. This improves the reliability of the Point of Sale system.
Original PR description
### Steps to reproduce: - Download 'Point of Sale' and 'Contacts' app - Create a customer with a pricelist that includes a percentage discount - Create a shop with the following properties: - Default…
### Steps to reproduce:
- Download 'Point of Sale' and 'Contacts' app
- Create a customer with a pricelist that includes a percentage discount
- Create a shop with the following properties:
- Default preset = 'Takeout' with a standard 40hr/week schedule
- Payments = 'Card' and 'Customer Account'
- Pricelists = a 'Default' and the discounted pricelist
- Create a POS order (without choosing a customer)
- Add products to the order, and select 'Customer Account' payment method
- Select the created customer
* Pricelist applies → Order total decreases.
* Payment now exceeds total → Negative change shown.
- Disable 'Invoice' checkbox.
- Click 'Validate' → Show popup 'No cash statement found for this session.'
- Again click 'Customer Account' → Add another payment line (negative).
- Process the order payment
- Close Session
- Try to create an invoice for the order
> Error: Entry not balanced
### Cause of Issue:
When 'Validate' is clicked for the first time and `syncAllOrders()` is called, `serializeForORM()`
clears the `_dirty` state tracking after the serialization. https://github.com/odoo/odoo/blob/418b103dab782d81a33d2a7afd8ec3767d7a82df/addons/point_of_sale/static/src/app/services/pos_store.js#L1501-L1534
When the 'No cash statement found' error occurs, the backend rolls back the changes made to the
order lines.
Then, since the `order.lines` weren't marked as dirty (no changes occured to them) and `payment.ids`
were marked as dirty, when 'Validate' is clicked for the second time, the js side doesn't send the
`order.lines` again, so the backend uses the existing, undiscounted lines.
The mismatch happens because while the product lines are undiscounted, the `payment.ids` are
correct (because a new 'Customer Account' line was added, so `payment.ids` were sent again).
Hence, `amount_total` and `amount_paid` were calculated with discounts applied, while individual
`line.price_unit` values remained at list price, resulting in invoice line amounts not matching
the amount paid and causing "entry not balanced" errors during invoice generation.
### Fix:
Preserved the `_ dirty` state commands, ensuring that when the order is reserialized on retry,
the line data are included in the second `sync_from_ui` payload.
opw-6080597
Forward-Port-Of: odoo/odoo#259636