Monday, February 2, 2026
17 changes · saas-18.2
Resolved issues and error corrections
Selecting a tax while adding a journal item in mobile view no longer crashes the Accounting app. Tax choices are now saved when the user closes the dialog, making journal entry editing more reliable on mobile devices.
Original PR description
**Steps to reproduce:** * Install the **Accounting** module. * Open a **Journal Entry** in mobile view. * Add a **Journal Item**, opening it in a form dialog. * Select a tax in the **Taxes Applied**…
**Steps to reproduce:** * Install the **Accounting** module. * Open a **Journal Entry** in mobile view. * Add a **Journal Item**, opening it in a form dialog. * Select a tax in the **Taxes Applied** field. * Click on the **Save and close** button. **Observed behavior:** * The web client crashes with `TypeError: Cannot read properties of undefined (reading 'resId')`. **Cause:** * The `autosave_many2many_tags` widget triggers `model.root.save()` immediately when a tag is selected. * When executing this save from within a transient dialog (common in mobile views), the client fails to correctly handle the record reload/synchronization leading to a crash when accessing `resId`. **Fix:** * Replace the autosave widget with the standard `many2many_tags` widget for **Journal Items** for form. * Tax changes are now kept locally in the dialog and saved only when the user explicitly saves and closes it. **Note:** * The crash happens when `autosave_many2many_tags` calls `model.root.save()` on a **new** `account.move` from within a dialog. * In QUnit tests, the mock server (`mockWebSave`) [1](https://github.com/odoo/odoo/blob/ca4d74c2a5d749ff8d41cd2fff80a73ba550a843/addons/web/static/tests/helpers/mock_server.js#L627). which creates records [2.](https://github.com/odoo/odoo/blob/ca4d74c2a5d749ff8d41cd2fff80a73ba550a843/addons/web/static/tests/helpers/mock_server.js#L1936) in-memory and does not trigger form reloads or component destruction. * As a result, the crash cannot be reproduced in tests. opw-5497342 Forward-Port-Of: odoo/odoo#246084
This fixes a checkout issue where validating a mobile barcode could prevent the selected carrier type from being saved on the sales order. Customers can now keep their carrier choice while the carrier number field is safely locked after validation and becomes editable again when the carrier type changes.
Original PR description
carrier type After the user clicks on "Validate" button to validate the mobile barcode, the carrier type selection is disabled and the carrier type pass to the SO is None. This commit fixes the issue by instead of disabling the carrier type selection, we just set the input box of carrier number to readonly and set back the carrier number to not readonly when the user changes the carrier type to ensure the carrier number input is editable. task-5880421 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 Forward-Port-Of: odoo/odoo#246053
This fix prevents tax amounts from being mistakenly reported as discounts in Malaysia POS consolidated e-invoices when prices include tax. It improves MyInvois XML accuracy and helps businesses avoid incorrect discount reporting for tax-included products.
Original PR description
The _add_consolidated_invoice_base_lines_vals method computed the gross subtotal using `price_unit * quantity`. When taxes are configured as "Included in Price", `price_unit` contains the…
The _add_consolidated_invoice_base_lines_vals method computed the gross subtotal using `price_unit * quantity`. When taxes are configured as "Included in Price", `price_unit` contains the tax-included amount, but `total_excluded` (used for the discounted amount) is tax-excluded.
This caused the tax amount to be incorrectly reported as an AllowanceCharge (discount) in the MyInvois XML, because:
discount_amount = price_unit * qty - total_excluded
= tax_included - tax_excluded
= TAX AMOUNT (not a discount!)
Example: Product priced at 110 MYR with 10% tax included:
- price_unit = 110 (tax-included)
- total_excluded = 100 (tax-excluded: 110 / 1.10)
- discount_amount = 110 - 100 = 10 ← incorrectly reported as discount
refs:
The cac:AllowanceCharge element in UBL is specifically for discounts and surcharges, NOT for taxes. According to the Peppol Malaysia e-Invoice specification:
https://docs.peppol.eu/poac/my/pint-my-sb/bis/#_allowances_and_charges
https://docs.peppol.eu/poacc/billing/3.0/codelist/UNCL5189/ (For this case we are interested in code 95)
Steps to Reproduce:
1. Configure a tax as "Included in Price" with Malaysia tax type
2. Create a product with that tax.
3. Create POS orders without any discount
4. Generate consolidated invoice and XML
5. XML incorrectly shows <cac:AllowanceCharge> with tax amount as discount
The fix uses `raw_total_excluded / discount_factor` (always tax-excluded) instead of `price_unit * quantity` (may be tax-included), consistent with the parent method:
https://github.com/odoo/odoo/blob/d645361a95037ac580d55e80bcb61d1eeb293efd/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_20.py#L1846-L1876
Ticket [link](https://www.odoo.com/odoo/project.task/5476526)
opw-5476526
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#245535This fix keeps question timers visible for participants during live survey sessions after they submit an answer. It prevents confusion in timed live surveys by ensuring participants can still see the countdown on the next question until the host ends the session.
Original PR description
Description of the issue/feature this PR addresses: Fixes timer not showing to participants of a live session after submitting an answer. Current behavior before PR: Steps to reproduce: Create a…
Description of the issue/feature this PR addresses: Fixes timer not showing to participants of a live session after submitting an answer. Current behavior before PR: Steps to reproduce: Create a Survey with two questions of any type. Set some Question Time Limit on both. Create Live Session for that Survey. Access Live Session as a participant. Start Survey as the host. The first question appears to the participant, with the timer on the top right. Answer the question as the participant and click Submit or press Enter. On the host side move to the next question. Bug: timer for the second question does not appear to the participant. Desired behavior after PR is merged: Fix: The logic to hide the timer is on `survey_form.js`. In the `_nextScreen` function. Specifically, when `options.isFinish` is set to `true`. It is set to `true` in the `_onSubmit` function. This function triggers when the participant submits an answer. The fix: only set that flag to `true` if there isn't a session in progress. This aligns with the other calls to `_nextScreen` for live sessions. That is, when a timer expires and the form is submitted: `'isFinish': !this.options.sessionInProgress` and when a notification is received for the session: `isFinish: nextPageEvent.type === 'end_session'` Meaning that for live sessions the only time `isFinish` should be set to `true`, is when the `end_session` notification is received. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#241086
Point of Sale event registrations now correctly copy order-level identification answers, such as attendee name and email, onto each created registration. This prevents missing attendee details when questions are configured to be answered once per order and keeps POS behavior consistent with online event registration.
Original PR description
When pos_event was added, a difference in behavior with the existing registration flow in website_event was missed. When answering an 'identification question' (name, email, phone, company_type) with…
When pos_event was added, a difference in behavior with the existing registration flow in website_event was missed. When answering an 'identification question' (name, email, phone, company_type) with the 'once per order' enabled, the values should be used and propagated directly on the created event.registration records, with precedence over other answers given to individual questions (not once per order) STEPS ===== 1. Create a new event with name, email questions, once per order ticked. 2. Go to POS (reload data if needed) 3. Register and answer both questions 4. Continue the pos flow until both registrations are created 5. Go to backend > your event > registrations 6. You can see that even if the answers are propagated in the o2m field registration_answer_ids, which is expected, the attendee's name and attendee's email fields are empty. They should contain the values of step 3. FIX === Align the website_event flow and precedence of OPO questions over individual answers in pos_event. Now, the first non-null answer of an OPO question will be used for each of the question_type values listed above, if any, over answers to individual questions. Also align the fact that after OPO answers, precedence should be given to the first non-null answer to a given question type for identification questions, and not the last as it is currently. Task-4919080 Forward-Port-Of: odoo/odoo#231502
Users mentioned in a sub-channel they had not joined are now automatically added so the conversation appears in their sidebar. This helps prevent missed notifications and ensures important pings are visible.
Original PR description
Before this commit, when a user was mentioned in a sub-channel they were not member of, the sub-channel would not appear in their sidebar. This could lead to some missed pings. This commit fixes the issue by automatically adding mentioned users to the sub-channel, ensuring it is pinned to their sidebar. task-5233958 Forward-Port-Of: odoo/odoo#237538
This fixes an error that prevented French employees on two-week work schedules from requesting paid time off on certain days. The system now ignores visual-only schedule separators, so leave dates are calculated correctly and employees can submit PTO as expected.
Original PR description
Steps to reproduce: - Choose France as the company location, and download "France - Work Entries Time Off" module. - From Employees > Configuration > Settings > French Time Off Localization, select…
Steps to reproduce: - Choose France as the company location, and download "France - Work Entries Time Off" module. - From Employees > Configuration > Settings > French Time Off Localization, select Paid time Off. - Create a new employee and a new contract (in running state) for that employee that starts on 01/01/2025. - While in the contract screen, create a new schedule that has 2 weeks calendar and Europe/Paris timezone. - From Time Off > Management > Allocations, allocate 1+ paid time off days for the newly created employee that's valid from 01/01/2025. - From the employee's profile > Time Off, try to take a Monday off. Issue: - The user gets a Validation error stating that the "start date" is later than the "end date". Fix: - In a 2 weeks calendar, there are 2 lines that are there to separate the first week from the second week (for aesthetic purposes). These lines have "hour_from" and "hour_to" = 0, which are taken into account when calulating the minimum hour to start the day off. - Add a check to remove lines from calendar that are just there for display purposes. opw-5387347 Forward-Port-Of: odoo/odoo#246094
The Point of Sale app now handles startup failures caused by corrupted local browser data instead of leaving users stuck on an endless loading screen. Users are offered an easy way to clear local data and refresh, which is especially helpful on mobile devices.
Original PR description
Inconsistent data in the localDB or local storage can cause the POS to fail before it can get initialized. When this happens the page just stays showing the loading screen forever. Now when an error happens while initializing the POS the loader will disappear and the app will give the user the option to clear all local data and refresh the page. This should be especially useful for users on mobile where it's a pain to refresh the local data manually. Task-[5420544](https://www.odoo.com/odoo/project/1737/tasks/5420544) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Saudi e-invoicing now better supports non-Saudi customers by allowing an additional buyer identification number to be recorded when the active company is in Saudi Arabia. If no VAT number is available, this additional ID is used in ZATCA XML invoices, helping businesses stay compliant and avoid invoice generation issues.
Original PR description
This commit makes l10n_sa_additional_identification_number visible for non-Saudi individuals and company partners when the active company is in Saudi Arabia and keeps the identification scheme fixed to OTH, keeping it invisible. When generating ZATCA XML for non-Saudi partners, VAT is used as the primary buyer ID if present, and fall back to the additional identification number when VAT is missing. task-4525956 Forward-Port-Of: odoo/odoo#245832
Website editors and designers can now use the SEO optimization tools on eCommerce category pages without hitting an access error. This helps teams improve shop category visibility in search engines while keeping the intended website editing workflow intact.
Original PR description
### Issue: Due to this issue, website designer cannot optimize seo on category. #### Steps to reproduce: 1- Create a `eCommerce Category`. 2- In demo user, set Sale access to `All Documents` and Website access to `Editor and Designer`. 3- Login with demo user and navigate to website. 4- In shop page, open category. 5- From site tab, click on `Optimize SEO`. You get access error. Expected: You should be able to optimize seo with `Editor and Designer` access. ### Cause: The user needs write access on record in order to optimize seo: https://github.com/odoo/odoo/blob/ec5da99ca3f52420e7d973c3cf07167bd4104ffa/addons/website/controllers/main.py#L831-L834 opw-5443854 Forward-Port-Of: odoo/odoo#244724
Deleting a work order with a running timer now stops the related activity timer so the work center can be blocked as expected. This prevents manufacturing operations from being stuck in an active state after the underlying work order is removed.
Original PR description
Steps to reproduce: - Start the timer on the work order - Delete the work order - Try to block the work center Current behavior: - The work center is not blocked because the latest mrp.workcenter.productivity is still active Expected behavior: - The work center is blocked - mrp.workcenter.productivity is stopped opw-5475227 Forward-Port-Of: odoo/odoo#245815
This fix ensures employees using a work schedule that is not tied to a specific company still see company public holidays correctly. Time off calculations will no longer mistakenly treat those holidays as regular working days, improving accuracy in leave balances and dashboards.
Original PR description
Revert of https://github.com/odoo/odoo/commit/a95af8b78a94a795e05a0adf299837adc0ef2117 and https://github.com/odoo/odoo/commit/f4f9ecb3e4801b3d382abc24c478fe5e533c9bf8 **Steps to reproduce** 1.…
Revert of https://github.com/odoo/odoo/commit/a95af8b78a94a795e05a0adf299837adc0ef2117 and
https://github.com/odoo/odoo/commit/f4f9ecb3e4801b3d382abc24c478fe5e533c9bf8
**Steps to reproduce**
1. Remove the company of the Working Schedule (needs to be done in
a multi-company environment from the UI) used by an employee.
2. Using the company of this employee, create a Public Holiday
(for the employee's schedule or all schedules).
Issues:
- the public holiday doesn't appear in the Time Off dashboard
- when taking a time off on that day, the public holiday is
included in the duration
**Cause**
After the fix in https://github.com/odoo/odoo/commit/a95af8b78a94a795e05a0adf299837adc0ef2117 , it will lead
to a search domain for public holidays of `('company_id', 'in', [False])`
when the working schedule has no company, ignoring any public
holidays with a company set. This is especially problematic since the
company of the public holiday is always forced.
https://github.com/odoo/odoo/blob/7bce5f3f95429a4d4ba034a66c350ee2a5868567/addons/resource/models/resource_calendar_leaves.py#L49-L51
**Solution**
Since the intent of the original fix https://github.com/odoo/odoo/commit/f4f9ecb3e4801b3d382abc24c478fe5e533c9bf8
was to correct an issue related to the computation of some `project.task`
fields calling a resource method (`get_work_duration_data`), we can revert
the fix and later fix the original issue directly in `project`, without
impacting `hr`/`resource` modules.
opw-5496999
opw-5401425
Forward-Port-Of: odoo/odoo#244052Purchase orders created from sales-driven replenishment now skip vendors whose validity date has expired. This helps ensure orders are assigned to an active supplier, reducing purchasing mistakes and manual corrections.
Original PR description
_______________________________________ ## Short functional explanation of the error Let's say we have a products that has 2 suppliers. In the list of suppliers, the first one set has an expired…
_______________________________________ ## Short functional explanation of the error Let's say we have a products that has 2 suppliers. In the list of suppliers, the first one set has an expired date. If we generate a PO from an SO with the MTO route, the assigned supplier for this product will be the first one set in the list, therefore having an expired date. ## Reproduction Steps 1. Go to Inventory > Configuration > settings. Check the option Multi-Step routes. 2. Click on Configuration > routes and unarchive the Replenish On Order (MTO) route. 3. Create or use an already existing product. Go to the Inventory tab, and under Operations, check the routes Replenish on Order (MTO) and Buy. 4. Click on the Purchase tab. There, set a first vendor for which the end date is earlier than today. For the second one, set an end date for which the date is later than today. 5. Go to Sales and create a new quotation. Set a customer and add a line with the product you just set. Click confirm. A smart button 'Purchase' should appear. Click on it. ### Expected behavior The assigned vendor of the PO should be the second vendor as it isn't expired yet. ### Unexpected behavior The assigned vendor is the first vendor, expired. ## Origin of the issue When generating a PO from an SO, the partner is the person ordering the product, not the vendor. Therefore, when this code is executed: https://github.com/odoo/odoo/blob/6b677319c47baacb5bee829b7e41448dec4136eb/addons/purchase_stock/models/stock_rule.py#L62-L66 no corresponding supplier is found, as ```self._get_partner_id(procurement.values, rule)``` returns the customer and not the vendor. This leads us to the fallback: https://github.com/odoo/odoo/blob/6b677319c47baacb5bee829b7e41448dec4136eb/addons/purchase_stock/models/stock_rule.py#L68-L72 which doesn't take into account the end date of vendors. __ opw-5030849 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 Forward-Port-Of: odoo/odoo#225684
This update resolves an issue where users couldn't see all available Starshipit delivery services. The fix ensures Odoo sends complete sales order details (address, weight) to Starshipit, allowing users to select the correct and full list of services during setup.
Original PR description
Current behaviour: Users are unable to select certain Starshipit delivery services. Delivery methods are configured in Odoo before address or package data is available. Since Starshipit requires this…
Current behaviour: Users are unable to select certain Starshipit delivery services. Delivery methods are configured in Odoo before address or package data is available. Since Starshipit requires this data to determine availability, it returns an incomplete list during setup. Expected behaviour: Users should be able to view and select from the complete list of valid delivery services based on the actual Sales Order details (address, weight, and volume). Steps to reproduce: 1. Create new delivery method 2. Configure Starshipit API credentials. 3. Attempt to select a service. 4. Observe that not all service is shown from the available options. Cause of the issue: Starshipit filters services based on sender, receiver, and package info. Odoo requests the service list during initial configuration without this context, resulting in an incomplete list of methods. Fix: Introduce a mechanism in the Sales Order flow to add delivery methods. Send the complete shipment details (addresses, weight) to Starshipit to retrieve the accurate list of services and allow the user to select them. Forward-Port-Of: odoo/enterprise#103316
This update corrects a bug where canceled refunds were incorrectly included in global invoices generated from Point of Sale (PoS) orders. The fix filters out canceled refund lines, ensuring that only valid refunds are reflected in the invoice. This improves invoice accuracy and reporting for Mexican VAT compliance.
Original PR description
When generating global invoices for orders in the PoS, refund of those orders are also included in the global invoice. However, if the refund has been canceled, it should not be included in the global invoice. Steps to reproduce: ------------------- * Create a PoS order and validate it. * Go to the backend and create a refund for that order. * Cancel the refund. * Go to the PoS order list and select the original order * Click on "Generate Global Invoice" > Observation: The canceled refund is included in the global invoice. Why the fix: ------------ We simply filter out the canceled orders when searching for refunded order lines. opw-5492576 Forward-Port-Of: odoo/enterprise#105868
This update resolves a problem where long item codes were causing errors in the AvaTax integration. The system now automatically limits item codes to 50 characters, ensuring successful tax calculations and preventing transaction failures. This improves the reliability of our tax processing.
Original PR description
Link to Avalara (Avatax) documentation: https://developer.avalara.com/api-reference/avatax/rest/v2/models/LineItemModel/ Expected Behaviour: The itemCode sent to the AvaTax API should be 50…
Link to Avalara (Avatax) documentation: https://developer.avalara.com/api-reference/avatax/rest/v2/models/LineItemModel/ Expected Behaviour: The itemCode sent to the AvaTax API should be 50 characters or fewer to comply with Avalara's field length constraints. Actual Behaviour before the Fix: When an itemCode exceeded 50 characters, the system attempted to send the request as-is. This resulted in the AvaTax API returning an error, causing the transaction or tax calculation to fail. Behaviour with the Fix: The system now ensures that the itemCode adheres to the 50-character limit before the API call is made, by trancating the code to the first 50 characters if it exceeds 50 characters. This prevents API rejection and ensures successful tax processing for items with long identifiers. Steps to reproduce: 1. Create or select a product/item with a reference (or barcode if using UPC) longer than 50 characters. 2. Trigger an action that calculates tax via the AvaTax integration (e.g., creating an invoice or updating a line item). 3. Observe the API response. - Before fix: API returns a validation error regarding the itemCode length. - After fix: Request is successful as the itemCode is properly handled/validated. opw-5406451 Forward-Port-Of: odoo/enterprise#105017
This fix resolves an issue where a Sales Order Item was unexpectedly removed from a timesheet after adding a timesheet line to a Helpdesk ticket. Previously, the system incorrectly cleared the Sales Order Item field. Now, the Sales Order Item from the quotation correctly persists within the timesheet, ensuring accurate billing for timesheeted services.
Original PR description
_ ## Short functional explanation of the error Let's say a customer has made 2 sales order of 2 different serives: one service is prepaid, the other one is billed based on timesheets. When we create…
_ ## Short functional explanation of the error Let's say a customer has made 2 sales order of 2 different serives: one service is prepaid, the other one is billed based on timesheets. When we create a ticket related to this customer in the helpdesk app, the ticket automatically fills the Sales Order Item field with the prepaid service. If we remove this Sales Order Item, create a timesheet in which we set the Sales Order Item to be the timesheeted service, and press save, the Sales Order Item of the timesheet will disappear. ## Reproduction Steps 1. Create 2 products: one service based on timesheets and one service prepaid. Make sure that they create an empty project on order. 2. Create 2 quotations for the same partner: one that contains the first product you created and a second one containing the second product. Click on confirm. 3. Go to Helpdesk > Configuration > Helpdesk teams. Click on one team and under Track and Bill time, enable both Timesheets and Time Billing. 4. Create a Helpdesk ticket for the team you setup. Under customer, enter the name of the partner in the quotations. 5. Click on the ticket. The quotation related to the prepaid service should appear as the field Sales Order item on the ticket. Remove it, click on the Timesheet tab, and add a line in which, for the Sales Order Item field of the timesheet, you'd enter the quotation for the timesheeted service. 6. Hit save. ## Expected behavior The quotation entered as Sales Order Item field in the timesheet persists, while the Sales Order Item field of the ticket stays empty. ## Unexpected Behavior the Sales Order Item field in the timesheet tab disappears. ## Origin of the issue The field is_so_line_edited was kept at false as the widget used to update its value wasn't used in the view. _ opw-5384256 Forward-Port-Of: odoo/enterprise#102345