Tuesday, May 26, 2026
14 changes · saas-18.4
Resolved issues and error corrections
Automatic checkout now accounts for approved time off when deciding when an employee should be checked out. This prevents time off at the end of a workday from being incorrectly counted as extra hours, improving payroll and attendance accuracy.
Original PR description
# Steps to reproduce 1. Set the Working schedule 40h/week 2. Employee takes 2 hours off from 15:00 to 17:00 and enable automatic check-out 3. Odoo will automatically checks out at 17:06 (scheduled end + tolerance) # Issue - This leads to 2h06 of extra hours being incorrectly recorded. # Fix - Use employee._get_expected_attendances instead, so contract-aware calendar resolution, leaves, and break time handling stay centralized in HR. task-5052044 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#235442
Sale orders now correctly convert combo product extra charges into the selected pricelist currency. This prevents undercharging or incorrect totals when customers buy combo products using a foreign-currency pricelist.
Original PR description
The total of a sale order containing a combo product that has an extra price is not correclty converted to the sale order's pricelist currency Steps to reproduce: 1. Install Sales 2. Go to Invoicing…
The total of a sale order containing a combo product that has an extra price is not correclty converted to the sale order's pricelist currency Steps to reproduce: 1. Install Sales 2. Go to Invoicing > Configuration > Accounting > Currencies and activate currency MXN 3. Go to Sales > Products > Pricelists and create a new pricelist for currency MXN 4. Go to Sales > Products and create a new combo product "test" 5. Create a combo choice "combo" with options "Large Cabinet" and extra price 10000$ 6. Go to Sales and create a new quotation for customer Acme Corporation with product "test" (total is $10,001) 7. Change the pricelist to MXN and update prices 8. The total is ~MX$10,018 (it should be ~MX$186,682) Issue: The extra price of a combo product is not converted to the sale order's pricelist currency, so we end up adding the price of the product in the order's currency with the extra price not converted Solution: Convert the extra price of the combo product to the sale order's pricelist currency opw-6192935 Forward-Port-Of: odoo/odoo#265876 Forward-Port-Of: odoo/odoo#265008
UBL invoice imports now correctly match taxes that are configured as included in the price. It also fixes line price calculations when imported invoices use quantities above one, preventing incorrect totals or tax adjustments.
Original PR description
**PROBLEMS** 1. On a company with taxes with price_include = True, we fail to retrieve a tax when importing a ubl. 2. The price_unit adjustement for when importing price-included taxes doesn't account for quantity. **STEP TO REPRODUCE** 1. Have a setup where the tax are only price_include. 2. Import a ubl, the taxes will not be retrieved. 3. Run odoo only with the fix for problem 1, and import the same ubl with some invoice quantity != 1 4. Notice the price unit are messed up for lines with quantity != 1 (odoo tries to correct the untaxed amount with a line, but this doesn't fixes the tax). opw-6159394 Forward-Port-Of: odoo/odoo#265347 Forward-Port-Of: odoo/odoo#262686
Corrects how Spanish TicketBAI purchase bills report the VAT regime for agricultural taxes. This prevents eligible supplier bills from being rejected due to an invalid tax regime code, helping companies submit compliant electronic tax records without manual workarounds.
Original PR description
…hase bills **STEP TO REPRODUCE** 1. Create a bill with a invoice line with a regimen agricultura tax. 2. send the bill using TicketBAI. 3. You will get the following error: Error:cvc-enumeration-valid: Value '19' is not facet-valid with respect to enumeration '[01, 02, 03, 04, 05, 06, 07, 08, 09, 12, 13]'. It must be a value from the enumeration. opw-6200686 Forward-Port-Of: odoo/odoo#265785 Forward-Port-Of: odoo/odoo#264037
This fix prevents manually adjusted tax totals on vendor bills from being overwritten when the bill is confirmed and purchase cost differs from the product's standard cost. It helps businesses keep intended tax corrections intact while still posting the necessary stock valuation price difference entries.
Original PR description
Issue is really similar to commit 21877c09863222a237fe99334787ac46935dcca4 except here the compensations amls are created in _stock_account_prepare_anglo_saxon_in_lines_vals because of a difference…
Issue is really similar to commit 21877c09863222a237fe99334787ac46935dcca4 except here the compensations amls are created in _stock_account_prepare_anglo_saxon_in_lines_vals because of a difference between bill price and product cost **Steps to reproduce:** - create storable product with category standard auto - on the category, set an account in the field 'price difference account' - set a cost of 10 - set a purchase tax - confirm a PO for 1 @ 15 and validate receipt - create bill, set a date, save - on the Bill set the total tax at 100 (it's bellow 'untaxed amount' on the bottom right of the bill and should be 2.25 before you change it, if the tax is 15%) - confirm the bill **Current behavior:** tax was reset to 2.25 **Expected behavior:** It should stay 100 as it was manually set **Cause of the issue:** The total tax amount is computed based on the tax lines in Journal Items https://github.com/odoo/odoo/blob/2744396733bb3ad60813e9e093d67192c0d38b36/addons/account/models/account_move.py#L1171 So the problem is actually that a recomputation of the balance of the tax account.move.line (the one with the account "tax paid" in journal items) is triggered when we confirm the Bill. That's because: When we confirm the bill, _stock_account_prepare_anglo_saxon_in_lines_vals() creates two amls : - one debiting 5 on the account set in the field 'price difference account' - one crediting 5 in the stock interim received account (This makes sense and is there to realign with the fact that, on the account move linked to the svl, the amount credited from stock interim received is rightfully 10 because that's cost of the product and it's a standard price product) When we create those amls from, the create method from account.move.lines calls super() inside a context manager calling _sync_dynamic_lines(). https://github.com/odoo/odoo/blob/5583cbcebae00d8122dce5ce929b650929966a90/addons/account/models/account_move_line.py#L1628-L1635 the yield of sync_dynamic_lines() is inside a context manager calling _sync_tax_lines. https://github.com/odoo/odoo/blob/5583cbcebae00d8122dce5ce929b650929966a90/addons/account/models/account_move.py#L3250 Therefore, the first half of sync_tax_lines() (untill the yield) is ran before the call to super and the rest (from the yield) is ran after the call to super. Because we added two lines in the account.move, get_changed_lines will return those 2 new line and because there is a tax_ids on the new lines round_from_tax will be False. https://github.com/odoo/odoo/blob/5583cbcebae00d8122dce5ce929b650929966a90/addons/account/models/account_move.py#L3034-L3041 Therefore we won't reach continue. https://github.com/odoo/odoo/blob/73d73c5c6606e0b34c754bfc4de035840951dd3b/addons/account/models/account_move.py#L3055-L3059 And the tax line will be recomputed using _prepare_tax_line() https://github.com/odoo/odoo/blob/73d73c5c6606e0b34c754bfc4de035840951dd3b/addons/account/models/account_move.py#L3065 Here is why there is a tax_ids on the new lines : The field is precompute so if we don't set a value for it, _compute_tax_ids will be ran to compute it. As the account move on which the lines are added is a bill, the tax_ids will the supplier_tax_id of the product. https://github.com/odoo/odoo/blob/73d73c5c6606e0b34c754bfc4de035840951dd3b/addons/account/models/account_move_line.py#L898-L901 **fix** There is no need for a tax_ids on these lines as they are not meant to (and should'nt) impact the taxes. opw-6014710 Forward-Port-Of: odoo/odoo#265684 Forward-Port-Of: odoo/odoo#263306
Website carousels are easier to use with a keyboard: focus moves more predictably, arrow controls are visibly highlighted, and Home/End keys jump to the first or last slide. Carousels also pause while users are focused inside them, making interactive content less likely to move unexpectedly.
Original PR description
[FIX] website: improve carousel accessibility To improve keyboard accessibility on carousels, the indicators container is not focusable anymore, and the indicators themselves now only have one…
[FIX] website: improve carousel accessibility
To improve keyboard accessibility on carousels, the indicators container
is not focusable anymore, and the indicators themselves now only have
one focusable button at a time.
The tab order is thus: previous button > active indicator > next button.
(The previous and next button may both appear before.)
You can still navigate among indicators with the left and right arrows,
which also moves the focus to the newly targetted indicator.
Note that other accessibility improvements remain to be done on the
carousels (add a pause/play button on auto-sliding carousels, place the
buttons before the carousel slide in the tab order, add some aria
attributes (roledescription, live), adaptative labels...).
[FIX] website: make focus visible on carousel arrows
When focusing manually (with tab / shift+tab) the previous/next arrows
in carousels, it is hard to follow where the focus is, because there is
no outline and the contrast is too small.
This commit adds a specific outline if the button is `:focus-visible`
(with both black and white to work on any background).
[IMP] website: pause carousel on focus within
Carousels are paused on hover (or touchstart), but if the focus is
inside one, it won't pause. That can make it hard to navigate within
interactive carousels during the sliding interval (e.g. the dynamic
products, which by default has 4 different products with 3 different
focusable links/buttons).
This commit applies the same behavior on focusin as Bootstrap's default
on mouseover, and on focusout as on mouseout.
[IMP] website: support home/end keys on carousels
When the focus is in a carousel, pressing the "home" key displays the
first slide and pressing the "end" key displays the last slide.
task-5470023
Forward-Port-Of: odoo/odoo#265651
Forward-Port-Of: odoo/odoo#244939This update preserves consistent HTML rendering and document processing when Odoo runs with newer underlying XML libraries. It reduces the risk of emails, views, translations, or Italian e-invoicing flows behaving differently after operating system or dependency upgrades.
Original PR description
## [FIX] core: lxml compatibility v2.14.0+ (HTML parsing) In version 2.14.0, libxml2 fixed a long standing quirk in its HTML handling where it always implies `<p>` start tags [1]. As a result, there…
## [FIX] core: lxml compatibility v2.14.0+ (HTML parsing) In version 2.14.0, libxml2 fixed a long standing quirk in its HTML handling where it always implies `<p>` start tags [1]. As a result, there is a difference in behavior between pre and post 2.14.0 produced HTML when no start tag is provided: - pre: always has a `<p>` tag - post: depending on the case, could have either a `<span>` or `<p>` tag. This commit introduces a monkeypatch of the lxml's HTML parser when built with libxml2 2.14.0+ to maintain a similar behavior with older versions. [1]: https://gitlab.gnome.org/GNOME/libxml2/-/commit/8cf6129bbd836e666e7eda8c9e61c00387ae388b ## [FIX] base,l10n_it_edi: catch TypeError/ValueError for lxml 6+ compat Updates exception handling to account for stricter type checking introduced in lxml 5/6 and libxml2 2.12+. Note: Ubuntu 26.04 (Resolute) provides lxml 6.9.2/libxml2 2.15 while Debian Trixie has lxml 5.4.0/libxml2 2.9.14. Don't be fooled by the version `2.12.7+dfsg+really2.9.14-2.1+deb13u1` which actually means that Debian has reverted/held back the core engine to 2.9.14 while adding commits from 2.12.7. Forward-Port-Of: odoo/odoo#259348
Refreshing a timesheet form opened from the grid view now keeps the intended timesheet-specific layout instead of falling back to a generic form. This prevents users from losing the proper context and fields when they reload the page.
Original PR description
…m view * Go to Timesheets > My Timesheets > switch to Grid view. * Hover over a cell with a timesheet entry and click the magnifier (search) icon. * The list opens; click a record to open its form view. * Observe the URL: `/odoo/timesheets/account.analytic.line/<id>`. * Refresh the page (F5). Before this commit, the generic form view was shown instead of the timesheet-specific form view. This occurred because, when reloading a page with a dynamic action and a resId, a generic view layout [false, "form"] was requested instead of the action-defined view. Now, the dynamic action is properly restored on refresh, ensuring the correct specific view is loaded for the form. opw-6133602 Forward-Port-Of: odoo/odoo#265815 Forward-Port-Of: odoo/odoo#265552
Fixed an issue where some delivery transfers created from Point of Sale orders were not connected back to the original sale when stock was unavailable for serial-numbered products. This keeps all related deliveries visible and traceable from the POS order, improving inventory follow-up and customer service accuracy.
Original PR description
The delivery transfer for a product tracked by serial number is not linked to the POS order when there is no available stock. When validating a POS delivery in real time, stock can split the transfer…
The delivery transfer for a product tracked by serial number is not linked to the POS order when there is no available stock. When validating a POS delivery in real time, stock can split the transfer into a completed picking and a backorder (e.g. one line fully delivered with lots, another serial-tracked line with no stock and no serial number). Steps to reproduce: ------------------- * Setup two products: one tracked by qunatity with some quantity on-hand an other tracked by SN but no quantity on-hand * Open Pos * Sell in one order, both products without providing SN * Validate payment * Open Inventory: two deliveries sould exist under Inventory Overview of PoS Orders > Observation: The first picking shows the POS order as Source Document but the backorder has no source document and is not linked to the POS order. Why the fix: ------------ Pos Origin (Source Document, POS order, session) was only written on the pickings returned by `_create_picking_from_pos_order_lines`, which did not include pickings created during `_action_done()`. Extend the write to the initial pickings and their backorders so every transfer stays tied to the originating `pos.order`. opw-6090606 Forward-Port-Of: odoo/odoo#265301 Forward-Port-Of: odoo/odoo#259370
This fixes an issue where saving an outgoing stock transfer could automatically add the wrong serial number when users manually selected specific serial numbers. The change helps keep transfer quantities and selected serial numbers aligned, reducing inventory mistakes during delivery operations.
Original PR description
**Problem**: When we set the quantity of a move to zero, then add serial numbers manually, if the serial numbers are not the first ones in the list of available serial numbers, The first few…
**Problem**: When we set the quantity of a move to zero, then add serial numbers manually, if the serial numbers are not the first ones in the list of available serial numbers, The first few available serial numbers will be added to the move, which causes a mismatch of quantity and the number of serial numbers. **Before this commit:** If we have three serial number SN-001, SN-002, SN-003 created in order, and we set the quantity of the move to zero, then add SN-002 and SN-003 manually, SN-001 will be added automatically while saving. **After this commit:** Only SN-002 and SN-003 will be added to the move, which matches the quantity. **Steps to reproduce:** 1. Create a product with tracking by unique serial number, and create 3 lots SN-001, SN-002, SN-003 for this product. 2. Create a picking and add a move for this product, set the demand to 3 and quantity to 0. 3. Set the quantity to 2, and add SN-002 and SN-003 to the move, then save the picking. 4. SN-001 will be added to the move automatically, but the quantity stays at 2. opw-6121208 Forward-Port-Of: odoo/odoo#265908 Forward-Port-Of: odoo/odoo#263080
This fix corrects the conformance level used for German XRechnung and ZUGFeRD PDF invoices. It helps ensure generated invoices are accepted by external validators used by German customers, reducing compliance issues and manual follow-up.
Original PR description
**PROBLEM** xrechnung pdf invoices are not compliant with some validators used german clients. **STEP TO REPRODUCE** 1. Create an invoice for a german customer. 2. Set the edi format on the customer as Xrechnung. 3. Download the invoice pdf, and verify it on https://www.portinvoice.com/ 4. Notice the pdf is not valid. To verify my fix works, you need to have the fontTools python package installed (for pdfa conversion). opw-6030481 Forward-Port-Of: odoo/odoo#259318
Purchase matching no longer crashes when a vendor bill line has a description and unit of measure but no product. This helps users process imported or manually entered vendor bills more reliably without needing to add placeholder products.
Original PR description
### Issue before this commit: Opening the Purchase Matching wizard would crash if the vendor bill contained lines with a description and a Unit of Measure (UoM), but no product selected. ### Steps to…
### Issue before this commit: Opening the Purchase Matching wizard would crash if the vendor bill contained lines with a description and a Unit of Measure (UoM), but no product selected. ### Steps to reproduce the issue: 1. Enable Units of Measure in Settings 2. Create and confirm a Vendor Bill setting a description and a UoM, but leave the Product field empty. 3. Click on "Purchase matching" smart button 4. The system throws a traceback with the error: "The unit of measure Unit defined on the order line doesn't belong to the same category as the unit of measure False defined on the product." ### Cause of the issue: In the purchase.bill.line.match model, the field product_uom_qty was computed by calling _compute_quantity using line.product_uom_id. Since product_uom_id is a related field on product_id.uom_id, it returns False when no product is set. The UoM conversion logic cannot handle a False destination category, leading to the crash. ### Reason to introduce the fix: Make purchase matching robust when imported vendor bills contain lines identified only by their description and not by a product. Note that for `purchase.bill.line.match` corresponding to an account.move.line but not related to any product, the `product_uom_qty` should match the quantity of the `aml_id` instead of attempting a UoM conversion based on a missing product UoM for the behavior to be consistent with the inverse method: https://github.com/odoo/odoo/blob/59d6232979b8499fde6cb700df1870e2e38d0d3e/addons/purchase/models/purchase_bill_line_match.py#L45-L54 opw-5911526 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#263848 Forward-Port-Of: odoo/odoo#257827
Odoo now handles Polish KSeF authentication more reliably when companies use certain foreign qualified certificates. Instead of failing during login, the system selects the appropriate certificate identifier so the request can be accepted by KSeF.
Original PR description
### Description of the issue/feature this PR addresses: **Issue**: During KSeF authentication, some foreign qualified certificates causes a crash with error _"Failed to authenticate with XAdES: 400…
### Description of the issue/feature this PR addresses: **Issue**: During KSeF authentication, some foreign qualified certificates causes a crash with error _"Failed to authenticate with XAdES: 400 Client Error: Bad Request for url: https://api.ksef.mf.gov.pl/v2/auth/xades-signature"_ This is due to Odoo not handling different `SubjectIdentifierType` **Solution**: Implement a try/except block to safely check for the NIP in the certificate's subject string, defaulting the identifier type to `certificateFingerprint` when the NIP is missing or a ValueError is caught. ### Current behavior before PR: The `SubjectIdentifierType` is hardcoded as `certificateSubject`, and does not handle `certificateFingerprint` at all. This causes there to be an error when trying to authenticate with the KSeF server using XAdES signature. ### Desired behavior after PR is merged: The sign_authentication_challenge method will now safely evaluate the subject string. It assigns `certificateSubject` only if the NIP is verified to be in the subject string. If the NIP is absent or a ValueError occurs during parsing, the system safely falls back to using `certificateFingerprint`. This prevents tracebacks and ensures the correct XML payload is sent to the KSeF server. opw-6125243 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#265390
This update resolves a bug where the Gantt view incorrectly displayed working hours for flexible employees during public holidays. The fix converts all time zone calculations to UTC, ensuring accurate representation of unavailable hours and preventing employees from being incorrectly scheduled to work during holiday periods. This improves the accuracy of employee scheduling and time tracking.
Original PR description
[FIX] hr_attendance_gantt: fix gantt view with public holidays Bug reproduction: 1 - Select flex schedule employee (or change its schedule to 40h flex one) and make its contract before 01/01/2026 2 -…
[FIX] hr_attendance_gantt: fix gantt view with public holidays
Bug reproduction:
1 - Select flex schedule employee (or change its schedule to 40h flex one) and make its contract before 01/01/2026
2 - Create a new public holiday on 01/01/2026 (from 00.00 to 23.59 or 23.55 (depends on version, it does not matter))
3 - in attendance app the cell from 00.00 to 01.00 seems white for that day and for selected employee (this cell seems like not holiday and employee can work)
Bug cause:
1 - After a long traceback, _gantt_unavailability in hr_attendance_gantt/HrAttendance, if an employee is flexible then unavailable_intervals is calculated with the Brussel time zone
2 - All other unavailable intervals are converted to the UTC in the function of _gantt_unavailability except in the final lines of the function.
3 - When the employee is flexible and since the conversion is not done in the final lines, it remains 1 hour more (UTC+1), it is from 1 am to 1 am of next day instead of 0 am to 23.59.
Bug solution:
1 - I converted the timezone to UTC to solve the problem.
task - 6067070
Forward-Port-Of: odoo/enterprise#112493