Friday, August 21, 2026
60 changes · saas-19.3
New functionality added to Odoo
Adds Sri Lanka-specific tax invoice handling so businesses can issue invoices in the required local format. The update introduces compliant invoice numbering, VAT registration detection, and report wording changes for tax invoices, helping Sri Lankan companies meet local invoicing rules.
Original PR description
This commit introduces the `l10n_lk_invoice` module to support specific tax invoicing requirements for the Sri Lankan localization. Key features include: * Custom Sequence Format: Implements the…
This commit introduces the `l10n_lk_invoice` module to support specific tax invoicing requirements for the Sri Lankan localization. Key features include: * Custom Sequence Format: Implements the mandatory Sri Lankan tax invoice sequence format `YYMMM_QQQQ_XXXXX` (e.g., `26MAY_BRN01_00001`), utilizing the journal code as the `QQQQ` component. * VAT Registration Tracking: Adds a `l10n_lk_vat_registered` boolean field to `res.partner` and `res.company`. This auto-computes based on the Sri Lankan VAT format (requiring >= 13 digits and ending in the "7000" suffix). * PDF Report Modifications: * Replaces the "Invoice" title with "Tax Invoice" when both the supplier and the customer are VAT registered, AND the invoice contains taxable supplies (excludes fully exempt invoices). * Replaces "Delivery Date" with "Supply Date" on tax invoices. * Injects "Mode of Payment" into the document header when a preferred payment method is selected on a tax invoice. * Resequencing Wizard Support: Overrides `account.resequence.wizard` to seamlessly handle Sri Lanka's specific month abbreviation formatting during mass resequencing. Task-6209151 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#273592
Adds a dedicated, responsive schedule page for each event location so attendees can see current and upcoming sessions in the event’s local timezone. The display updates automatically and keeps the latest schedule visible during temporary connection problems, improving on-site event communication.
Original PR description
Add a dedicated responsive schedule page for each event location, showing live and upcoming published tracks in the event's timezone. The display refreshes automatically without a full page reload and keeps the last known schedule visible during network issues. Task-6357556
Polish companies can now prepare a dedicated VAT-UE report instead of relying on the generic EC Sales List. The report includes relevant EU sales and purchase transaction types and supports XML export in the official Polish structure, helping streamline compliance filing.
Original PR description
Description of the issue this commit addresses: Polish companies only have the generic EC Sales List without purchase transactions or an XML export matching the official VAT-UE structure. --- Desired behavior after this commit is merged: This commit adds a Polish VAT-UE report covering intra-Community supplies, acquisitions, services, triangular transactions, and the official XML export. --- task-6368808 Forward-Port-Of: odoo/enterprise#127083
Enhancements to existing features
Guadeloupe, Martinique, and Réunion now follow the same French e-invoicing address setup as mainland France. This helps businesses in these regions automatically use the correct France FRCTC electronic address for Peppol/PDP processing.
Original PR description
In France, some drom-com (Guadeloupe, Martinique and Réunion) needs to use pdp just like France. So we should add those 3 for the computation of `peppol_eas`, so it will autocomplete to **France FRCTC Electronic Address**. task-6344558 Forward-Port-Of: odoo/odoo#282995 Forward-Port-Of: odoo/odoo#278272
Resolved issues and error corrections
Starting a new chat now only broadcasts it to active users, preventing archived accounts from causing access errors. This keeps chat creation reliable for current users and ensures one invalid recipient does not block notifications to others.
Original PR description
Before this commit, starting a chat with a partner that has an archived user broadcast the new channel to that user too. This happens because _get_or_create_chat searches the partners with active_test=False, only to check that the given ids exist, and a recordset union keeps the environment of its left operand. The flag therefore reaches user_ids, which stops filtering on active. Archived users only reach the broadcast since "[REF] mail, im_livechat: use user in channel._broadcast", as the main_user_id it replaced is always active. The problem is that _broadcast passes each user to with_user, so the whole channel payload is computed with the rights of a user the caller never asked for, and lands on a bus channel no session can subscribe to. This commit fixes the issue by keeping active_test=False on the search alone, and by asking for active users at the broadcast. task-6483179
Documentation and clarification updates
Sahil Singh has signed Odoo's individual contributor license agreement, confirming permission for recent and future contributions. This is a legal housekeeping update and does not change product functionality or user workflows.
Original PR description
Signing the Individual Contributor License Agreement to authorize my recent and future code contributions to the Odoo repository. Forward-Port-Of: odoo/odoo#281366
Point of Sale now continues after payment validation without waiting for the receipt to finish printing. This reduces delays at checkout and keeps cashier workflows moving, while tests were updated to match the new behavior.
Original PR description
- Stop awaiting the receipt print in the POS after order payment validation - Adapt tours to this behavior change task-id: 6425204 enterprise PR: https://github.com/odoo/enterprise/pull/127818 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282990 Forward-Port-Of: odoo/odoo#280002
The Point of Sale product configuration window now shows product reference codes again, making it easier for staff to identify the correct items. Searches using a variant reference now open the matching variant directly, reducing selection mistakes during sales.
Original PR description
We reintroduce the default code on the product configuration modal, and ensure that searching for a variant reference opens the right variant. task-6463377 Forward-Port-Of: odoo/odoo#282889 Forward-Port-Of: odoo/odoo#282707
French PDP e-reporting entries now have a dedicated view that highlights information specific to those records. This helps users review relevant details more easily without changing the standard accounting move view used elsewhere.
Original PR description
This commit will add a new view for the ereporting moves to be able to see some specific info without touching the base move view. task-6274213 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270101
Bank statement matching now recognizes invoice payment references even when formatting characters such as slashes are missing. This helps payments reconcile automatically in more cases, reducing manual matching work for accounting teams.
Original PR description
Backport of: https://github.com/odoo/odoo/commit/1a737a654e1f51ae4979a95a960c33770cf0746d Before this commit, the "try_auto_reconcile" algorithm was finding moves when there was a perfect match with either the ref of a move line, the move name, the payment reference and now a sanitize version of the payment ref. For example if an invoice had SO12/1234 as the payment reference, if the statement line has a label SO121234 nothing was found. This commit will then add a new non stored computed field to sanitize the payment ref on the invoice level to help those cases task-6119841 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283508
Turkish payroll settings have been updated for 2026, including clearer minimum wage naming and new configurable social security contribution parameters. This helps payroll teams calculate employee contributions more accurately using the applicable minimum and maximum contribution bases.
Original PR description
- Update the Turkish payroll rule parameters for 2026. - Rename the minimum wage parameter to 'Turkiye Minimum Net Wage'. - Add configurable parameters for the SSI minimum contribution base and employee contribution rate. - Update the SSI contribution computation to account for both the minimum and maximum contribution bases. **task-6397284** Forward-Port-Of: odoo/enterprise#128520 Forward-Port-Of: odoo/enterprise#125985
The Belgian POS blackbox flow now avoids waiting for receipt printing when an order is canceled. This helps staff move on faster after cancellations and reduces unnecessary delays at checkout.
Original PR description
Stop awaiting the receipt print in the POS for canceled orders task-id: 6425204
Amazon order syncing has been updated to use Amazon's newer order service before the old one is retired in 2027. This should keep Amazon sales integrations working reliably and improve sync efficiency by receiving order details in fewer steps.
Original PR description
Amazon has announced the deprecation of the Orders v0 API, with a removal date of March 27, 2027. In this commit, we migrate to the new v2026-01-01 API. This new version restructures how order data is queried and delivered, shifting from a multi-request architecture to a nested consolidated payload. This optimizes our sync performance by eliminating the N+1 query problem when fetching order items. Key changes: - Operation Consolidation: `getOrders` is replaced by `searchOrders`. Because Amazon now embeds orderItems directly inside each order object natively, we remove our secondary item-fetching loops. - Financial aggregation: Item prices, taxes, shipping, and discounts are no longer flat fields on the item but are centralized into a `proceeds` object. - Replacing of deprecated flags. - Reorganization of order-related fields. task-5972714 Forward-Port-Of: odoo/enterprise#128568 Forward-Port-Of: odoo/enterprise#114591
Bank reconciliation can now recognize invoice payment references even when formatting characters such as slashes are missing from the bank statement label. This helps payments match invoices more reliably and reduces manual reconciliation work.
Original PR description
Backport of: https://github.com/odoo/enterprise/commit/e0d3591c9c03077f01cb8c93979d610ab99a833c Before this commit, the "try_auto_reconcile" algorithm was finding moves when there was a perfect match with either the ref of a move line, the move name, the payment reference and now a sanitize version of the payment ref. For example if an invoice had SO12/1234 as the payment reference, if the statement line has a label SO121234 nothing was found. This commit will then add a new non stored computed field to sanitize the payment ref on the invoice level to help those cases task-6119841 Forward-Port-Of: odoo/enterprise#128574
Accrued expense entries no longer fail when a purchase order line has been reduced to zero quantity. This prevents an error popup and lets users complete the accounting process for received goods more reliably.
Original PR description
### Steps to Reproduce: 1. Have a product where Track Inventory is enabled and the product category is FIFO and Perpetual 2. Create a PO for the product 3. Validate the receipt 4. Update the quantity…
### Steps to Reproduce: 1. Have a product where Track Inventory is enabled and the product category is FIFO and Perpetual 2. Create a PO for the product 3. Validate the receipt 4. Update the quantity on the PO to 0 5. Create Accrued Expense Entry > Traceback ### Description of the issue/feature this PR addresses: **Issue:** Currently when generating an Accrued Expense Entry for a PO where quantity on the line is updated to 0, the system crashes with an RPC error. This happens because reducing the line quantity to 0 sets the overall order amount to 0.0. Then. when the accrued orders wizard tries to calculate line-item ratios, it triggers a `ZeroDivisionError`. **Solution:** We can add a zero-check fallback condition when computing the line ratio inside `_compute_move_vals` in the `AccountAccruedOrdersWizard` class. The ratio calculation now defaults to 0.0 if the order total is zero, preventing division by zero. ### Current behavior before PR: Triggering the Accrued Expense Entry wizard on a PO with a changed quantity of 0.0 causes a `ZeroDivisionError` server error. The user receives an RPC error dialog and cannot proceed with creating the journal entry. ### Desired behavior after PR: The wizard should be able to process Purchase Orders with a line quantity of 0 without throwing an RPC error. The system should now cleanly generate the accrual entry based on received quantities. opw-6459403 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281808
Daily time off accruals based on worked time now respect the employee's local working calendar. This prevents employees on Monday-Friday schedules from incorrectly earning leave on Saturdays in some timezones.
Original PR description
## Current behavior: On a Monday–Friday working schedule, a Daily accrual plan that is based on worked time grants accrued time on Saturday as well, even though Saturday is not a working day. The…
## Current behavior: On a Monday–Friday working schedule, a Daily accrual plan that is based on worked time grants accrued time on Saturday as well, even though Saturday is not a working day. The employee accrues on 6 days per week instead of 5 (Sunday is correctly skipped. Only Saturday is wrong). ## Expected behavior: The employee accrues only on the 5 working days (Mon–Fri) → 5 grants per week. Saturday and Sunday should add nothing. ## Setup: - Working schedule: Standard 40h/week, Monday–Friday, 08:00–17:00. - All timezones set to Australia/Brisbane (UTC+10) and matching: employee, working schedule, and user are all the same timezone. - Accrual plan milestone: accrue 5 Hours, Daily, "At the end of the accrual period", "Based on worked time = Yes". ## Steps to reproduce: - Create the working schedule and accrual plan above, with the calendar timezone set to Australia/Brisbane. - Assign the accrual allocation to an employee, Starting on a Monday. - On the Time Off dashboard, use "Balance at the (date)" to project the balance day by day across a weekend (Friday → Saturday → Sunday → Monday). ## Cause of the issue: Accrual period boundaries were built as naive UTC midnights instead of local calendar midnights. ## Fix: Localize accrual period boundaries in the employee/resource timezone before calling resource calendar APIs. This bug is reproducible in multiple versions. PRs for: - v19.0: https://github.com/odoo/odoo/pull/279029 - v18.0: https://github.com/odoo/odoo/pull/279036 opw-6316062 Forward-Port-Of: odoo/odoo#283215 Forward-Port-Of: odoo/odoo#279029
Visitors viewing a public channel they have not joined will no longer be unexpectedly moved away from that channel. This ensures they can see the intended conversation and messages instead of being redirected to another pinned chat or an empty view.
Original PR description
Before this commit, a visitor opening the public page of a channel they are not a member of sees the channel for a moment, then Discuss leaves it on its own, and the messages of that channel never appear. This happens because every member of a channel notifies it that its pin state changed, self member or not. The member panel loads the members of the displayed channel right after the page, and as the visitor has no member there, the channel then concludes it is not pinned and Discuss opens the first pinned channel of the sidebar, or none at all. This commit fixes the issue by notifying the channel only when the pin state of self member changes. Backport of odoo/odoo@32f6eafea7071345622a068556c367bcf3169f79 Forward-Port-Of: odoo/odoo#283587
The Point of Sale combo configurator now keeps separate choices for identical products with different combo options. This prevents an extra burger line from appearing when staff apply a combo order, making restaurant tickets clearer and more accurate.
Original PR description
Steps: --- - Open the Burger combo choice. - Set the maximum quantity to 2. - Open the restaurant. - Add a cheese burger with Belgian fresh homemade fries. - Add another cheese burger with sweet potato fries. - Add Coca-Cola. - Click Apply. Issue: --- - A new cheese burger line is created even though a cheese burger line already exists. Cause: --- - When the same product has different configurations but belongs to the same `combo_item`, the last configuration overrides the previous one due to using the same key. Fix: --- - Differentiate configurations by appending `lineUuid` to the configuration key. - Ensure each product configuration is handled independently when computing included and extra combo items. task-5480195 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282988 Forward-Port-Of: odoo/odoo#243954
This fixes an intermittent issue where the website editing toolbar could disappear after a very quick edit-and-save action. Users can reliably return to editing, publishing, and preview controls without needing to reload or restart their workflow.
Original PR description
### Issue: `test_19_website_page_options` and `test_32_website_background_colorpicker` fail intermittently on RunBot on the "Click Edit" tour step: FAILED: [27/43] Tour website_background_colorpicker…
### Issue: `test_19_website_page_options` and `test_32_website_background_colorpicker` fail intermittently on RunBot on the "Click Edit" tour step: FAILED: [27/43] Tour website_background_colorpicker → Step <b>Click Edit</b> to start designing your homepage. (trigger: body .o_menu_systray .o_menu_systray_item.o_edit_website_container button) Element (body .o_menu_systray .o_menu_systray_item.o_edit_website_container button) has not been found. TIMEOUT step failed to complete within 10000 ms. The whole website systray (Edit, Publish, mobile preview, ...) disappears permanently after a save, with no way to bring it back ### Cause: https://github.com/odoo/odoo/blob/68066eaf3505bbf9ccaf60069072b9a62a49d4e4/addons/website/static/src/client_actions/website_preview/website_builder_action.js#L178-L207 1. Entering edit mode schedules a 200ms delayed removal of `website.WebsiteSystrayItem` from the systray registry to avoid a visual pop during the navbar hide animation 2. Leaving edit mode re-adds the entry only if not already present 3. If a full edit -> save cycle completes in under 200ms, the timer from step 1 is still pending when step 2 runs The re-add is skipped since the entry still looks present 4. The stale timer fires and removes the entry permanently Nothing re-adds it until the next edit-mode transition, which requires clicking the button that just disappeared runbot-240723 Forward-Port-Of: odoo/odoo#280064
German DIN5008 invoice PDFs sent by post now place the recipient address correctly in the required window. This prevents Pingen from rejecting Snailmail letters while keeping the normal report layout unchanged for other uses.
Original PR description
**Steps to reproduce:** - Install l10n_din5008 and accountant. - Enable Snailmail from Accounting → Settings. - Create a German customer (Fiscal Country: Germany). - Create and post a customer…
**Steps to reproduce:** - Install l10n_din5008 and accountant. - Enable Snailmail from Accounting → Settings. - Create a German customer (Fiscal Country: Germany). - Create and post a customer invoice using the DIN5008 report layout. - Select Send by Post. - Enable Developer Mode and navigate to `Settings → Technical → Email → Snailmail Letters`. - Open the generated letter and send it. **Current behavior:** The letter fails to be sent to Pingen with the following error: An error occurred when sending the document by post. Error: ` The attachment of the letter could not be sent. Please check its content and contact the support if the problem persists.` **Cause:** For Snailmail documents, Pingen validates that the recipient address is located within the DIN5008 address window. The current l10n_din5008 report renders additional document information instead of the address in the address area, preventing the compliance validation to fail. **Fix:** When rendering the report for Snailmail, ensure that only the recipient address is displayed in the DIN5008 address window while suppressing the additional information that would otherwise occupy this area. This preserves the standard DIN5008 layout for regular reports while generating a Snailmail-compliant PDF that passes Pingen’s validation. **Reference:** [Pignen Recipient Address Validation Rule](https://help.pingen.com/en/fix-and-enhance-letters/issue-with-recipient-address#040201) Ticket [link](https://www.odoo.com/odoo/project.task/6387869) opw-6387869 Forward-Port-Of: odoo/odoo#280320
The calendar now shows the correct weekday names when a user's timezone changes daylight saving time at midnight. This prevents confusing duplicate day labels in day, week, and month calendar views for affected regions such as Egypt.
Original PR description
Current behaviour: In the Calendar view (day/week/month scale), when the user's timezone observes a DST transition that starts exactly at local midnight (e.g. Africa/Cairo, since 2023), the day…
Current behaviour: In the Calendar view (day/week/month scale), when the user's timezone observes a DST transition that starts exactly at local midnight (e.g. Africa/Cairo, since 2023), the day column right after the transition gets the wrong weekday name, duplicating the previous day's name. For ex. it renders "... THU THU FRI ..." instead of "... THU FRI SAT ...", for the week surrounding April 30th 2027. To fix this we add 1 hour to the Date before reading its weekday/day from it, mirroring the workaround FullCalendar itself adopted for this same bug. It has no effect on any ordinary day (adding 1h to a correct local midnight stays within the same calendar day), and it cannot overshoot into the next day since no real-world DST gap exceeds that margin. Note: This is a known bug (https://github.com/fullcalendar/fullcalendar/issues/7633), fixed in FullCalendar v6.1.17, a major version ahead of the v4.4.0, so the fix can't be applied directly without a full library upgrade. opw-6370140 Forward-Port-Of: odoo/odoo#280253 Forward-Port-Of: odoo/odoo#279343
Blog pages now count only real public discussions, not internal chatter logs added by editors. This keeps displayed comment totals accurate for visitors and prevents back-office activity from inflating engagement numbers.
Original PR description
Issue: The internal chatter logs were being counted as regular comments in the blog. Steps to reproduce: Create a website with a blog. Create a page for the blog and activate comments. While editing go into blog post. Send a log in the chatter, and the blog will show one more message than it should. Cause: Both logs and comments have the same type: `Comment` and when doing the counting of comments we used this broader type, encompassing all of them. Fix: Corrected it to use the subtype `Discussions` as this one seems to be more relevant to actual blog post comments. opw-6287196 Forward-Port-Of: odoo/odoo#270998
Posted customer invoices no longer have their delivery date changed automatically when later deliveries are completed for the same sales order. This prevents already-finalized invoice information from shifting unexpectedly and keeps invoice records stable after posting.
Original PR description
Steps to Reproduce: 1. Confirm a Sales Order with one line -> creates delivery P1. Validate P1 with date_done = Day_A. 2. Create an invoice from the SO and post it -> invoice.delivery_date = Day_A.…
Steps to Reproduce: 1. Confirm a Sales Order with one line -> creates delivery P1. Validate P1 with date_done = Day_A. 2. Create an invoice from the SO and post it -> invoice.delivery_date = Day_A. 3. Add a new line to the same SO -> creates delivery P2. 4. Validate P2 with date_done = Day_B, where Day_B is earlier than Day_A. Issue: The already-posted invoice's `delivery_date` silently changes from Day_A to Day_B after step 4, even though nobody edited the invoice. This only happens when a delivery validated after posting has an earlier `date_done` than what was already used. Root Cause: `account.move.delivery_date (sale_stock)` is computed in `_compute_delivery_date()`, which depends on `sale.order.effective_date.effective_date` is itself computed as the earliest `date_done` among all done, facing deliveries on the order. Neither compute method checks whether the invoice is posted, so validating P2 triggers a chain reaction: the delivery is saved -> the sale order recalculates -> the invoice recalculates -> delivery_date gets overwritten on an already-posted invoice. `sale_stock` also marks `delivery_date` as protected, but this protection only works when the invoice itself is saved (write/create). Here, the change starts from saving the delivery (stock.picking), which never goes through the invoice's save method, so the protection never kicks in. `delivery_date` is also not on the list of fields Odoo normally blocks from editing after posting. Fix: `_compute_delivery_date()` now splits invoices into posted and non-posted before running. Non-posted invoices work exactly as before. Posted invoices are skipped from the sync and simply keep their current value instead of taking the newly calculated one. `sale.order.effective_date` itself is untouched only its effect on an already-posted invoice is blocked. Result: Once an invoice is posted, its `delivery_date` now stays fixed no matter what happens with later deliveries on the same sale order. `effective_date` keeps updating normally either way, confirming the fix only affects the invoice. Verified with both a script and a manual UI test. opw-6409171 Forward-Port-Of: odoo/odoo#283069 Forward-Port-Of: odoo/odoo#280978
This fix ensures read-only database connections using a separate replica configuration are closed when the database is closed. It prevents leftover database connections from accumulating, improving reliability for deployments that use database replicas.
Original PR description
close_db matched readonly connections against the primary DSN. When db_replica_* differs, those connections were left open. 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#282671
Forms now keep the desktop-style layout on tablet-sized screens instead of switching too early to the mobile view. This makes data entry and review more comfortable on tablets by preserving the wider layout users expect.
Original PR description
Change the breakpoints to keep the desktop configuration on tablet resolutions instead of switching to the mobile display. task-6379696 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
Manufacturing orders no longer show consumption warnings for service or combo components, since those items are not physically consumed like goods. This prevents confusing alerts during validation and keeps warnings focused on relevant product components.
Original PR description
Issue: Consumption Warnings were appearing for services in manufacturing orders, but services are not consumed therefore the warning makes no sense. Steps to reproduce: Create a bill of materials with at least 1 service type component line Create a manufacturing order for that product Validate the manufacturing order Why: Consumption warning was appearing because we have no amount of quantity of products of type service / combo. Since these types are not supposed to have a quantity in the same way goods do, this check does not make sense for products other than goods and therefore we should remove the check for products other than `'consu'` opw-6420932 Forward-Port-Of: odoo/odoo#278745
Date and time changes shown in the chatter now use the current user's timezone instead of defaulting to UTC. The displayed format also matches the standard date/time style and includes the timezone, reducing confusion for teams working across languages and regions.
Original PR description
Although the code was attempting to render the tracking values in the current user's timezone. It did not work. While fixing this, we also wanted to make the time clearer in multi-language/timezone settings: - Date(time) is now formatted in the same way it is for datetime fields in the frontend - The timezone is added after it, so we know exactly what time is meant - Those two fields are reprocessed in the chatter so that we simply show them in the user's locale task-6456370
Fixed an issue in Discuss where searching messages with a word followed by many spaces could crash the conversation view. The search highlight now ignores empty search terms, making message search more reliable for users.
Original PR description
**Steps to reproduce:**
- Go to Discuss app
- Open a conversation
- Click on the Search Messages button
- Enter a word, then a lot of spaces
- `RangeError: Maximum call stack size exceeded`
**Issue:**
During highlighting, if the search term contains multiple spaces, `searchTerm.split(" ")` produces empty terms `""`. Then the empty regex will match on every character, creating a lot of highlight `<span>` elements and eventually causing the error on `element.replaceChildren(...newNode);`.
**Fix:**
Filter out empty terms before processing.
opw-6446173
Forward-Port-Of: odoo/odoo#282059Odoo now shows the specific error details returned by Serbia's eFaktura service when an invoice submission fails. This helps users understand why an invoice was rejected instead of seeing only a generic technical HTTP error.
Original PR description
**Steps to reproduce:** - Install the Serbian EDI module `l10n_rs_edi`. - Configure eFaktura credentials on the company. - Create and confirm a Serbian customer invoice. - Send the invoice to…
**Steps to reproduce:**
- Install the Serbian EDI module `l10n_rs_edi`.
- Configure eFaktura credentials on the company.
- Create and confirm a Serbian customer invoice.
- Send the invoice to eFaktura.
**Observed Behavior:**
When the eFaktura API returns an HTTP error, Odoo only displays the generic exception generated by `requests`, for example an HTTP 400/500 error.
The actual error information returned by eFaktura in the response body is not shown to the user, making it difficult to understand why the invoice was rejected.
**Cause:**
`_l10n_rs_edi_send` catches `HTTPError`, `Timeout`, and `ConnectionError`, but the error message is built only from the Python exception.
For HTTP errors, the eFaktura API may return a response containing more precise information such as:
```json
{
ErrorCode: ...,
Message: ...
}
```
This response was not being used when displaying the error in Odoo.
**Fix:**
When an HTTP response is available and contains an eFaktura error payload, use the returned `ErrorCode` and `Message` as the error displayed on the invoice. Fallback to the existing connection/HTTP exception message when no usable API response is available.
opw - 6453653
Forward-Port-Of: odoo/odoo#281490This fixes a mobile scheduling issue where entering a time could close the time picker after the first digit and shift focus behind the panel. Users can now enter times more reliably in the Planning date/time field on touch devices.
Original PR description
Steps to reproduce ================== - Use a mobile viewport - Go to planning - Click on an empty cell - Click on the date - On the bottomsheet, click on the time at the bottom - Try to type 12:34…
Steps to reproduce ================== - Use a mobile viewport - Go to planning - Click on an empty cell - Click on the date - On the bottomsheet, click on the time at the bottom - Try to type 12:34 => Only 1 is entered and then the start_datetime field is focused behind the bottom sheet Cause of the issue ================== The <input type="time"/> listen to the onchange event. We listen to rawPickerProps changes using a reactive call. shouldFocus is then set to true, and the focus is done after the next render. The onchange event is called at a different time depending on the platform. On IOS and Firefox desktop: after changing hours or minutes On Android: once the apply button is clicked On Chrome desktop: After entering a single char Solution ======== The bottomsheet is only displayed when env.isSmall && hasTouch(). It doesn't make sense to focus the input, since we don't handle the keyboard in that case. opw-6386252 Forward-Port-Of: odoo/odoo#276617
This fixes a styling issue where color labels used outside the standard color selector could appear incorrectly. Planning resource entries now use the intended colors consistently, including with enterprise dark mode styling.
Original PR description
The `o_colorlist_item_color_*` classes were scoped to `.o_colorlist > button` by 1aa9b957afdd , but they are also used standalone outside any colorlist, e.g. in Planning's `many2one_avatar_resource` field. `web_enterprise`'s dark-mode counterpart also defines them unscoped, so the two stylesheets disagreed. Move the color rules back to the root scope. The colors themselves and the `color-contrast()` text color introduced by the refactoring are kept. Steps to reproduce: - Go to "Planning" - Open "Configuration" => the resources in the "Resources" column. 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#283232
Intercompany deliveries can now automatically unpack stock before it is received by the other company. This prevents confusing leftover package-based inventory records and makes intercompany stock balances easier to reconcile.
Original PR description
Issue: Compare to lot and serial number package are not multi company. It means that the package don't pass from a company to the other. So when a company deliver to another. The delivery will create a quant with the package. However the receipt in the other company will create a new quant without package (or a new package). It means that the quants are never reconcile and it could become difficult to understand what remains in intercompany location and what are artifact from past movements. In order to fix it, we introduce a new system parameter to directly unpack after the delivery. This way the receipt is always without source package and will automatically decrease the quant. opw-6376983 Forward-Port-Of: odoo/odoo#282448 Forward-Port-Of: odoo/odoo#277133
Argentinian delivery operations for Class X document types can now be saved without entering CAI authorization details or an expiration date. This aligns Odoo with local government rules, which only require those fields for Class R delivery notes.
Original PR description
Currently, when the user attempts to create a delivery operation for a class X document type, the system prompts the user to provide values for the CAI and CAI Expiration Date fields. ## Steps to…
Currently, when the user attempts to create a delivery operation for a class X document type, the system prompts the user to provide values for the CAI and CAI Expiration Date fields. ## Steps to produce: - Install `l10n_ar_stock` with demo data - Switch Company to `(AR) Exento` - Create a warehouse - Configuration > Operation Types > Delivery Orders - Set Document Type to `'(94) MAILING X' `and try to save ## Observed Behavior: The fields 'CAI' and 'CAI Expiration Date', which represent the authorization code and expiration date issued by the government, are currently configured as required fields. **Expected Behavior:** As specified on the [government site](https://www.argentina.gob.ar/normativa/nacional/resoluci%C3%B3n-1415-2003-81316/actualizacion#:~:text=Los%20datos%20indicados%20en%20el%20inciso%20a%29%2C%20puntos%207%2C%2010%2C%2011%2C%2012%20y%2013%2C%20s%C3%B3lo%20ser%C3%A1n%20para%20los%20remitos%20clase%20%27R%27%2E): > > 12. Printing authorization code, preceded by the acronym 'CAI No. ...'. > 13. Expiration date of the receipt, preceded by the legend 'Expiration Date ...' > > 'The data indicated in section a), points 7, 10, 11, 12 and 13, will only be for 'R' class delivery notes.' These statements indicate that the information mentioned in points 12 and 13, including the **CAI** and **CAI Expiration Date** fields, is applicable only to **'R'** class delivery notes. Therefore, for class X delivery notes, these fields should be optional rather than required. ## Root Cause: According to [1], the field is configured as a required field when a Document Type ID is selected. This configuration causes the **CAI** and **CAI Expiration Date** fields to become mandatory, regardless of the document type requirements defined by the government specification. [1]- https://github.com/odoo/odoo/blob/62b05c4ea61942072b6b1fb420fe3efedb11ed14/addons/l10n_ar_stock/views/stock_picking_type_views.xml#L11-L16 ## Solution: Apply constraints that align with the government specifications, allowing the CAI and CAI Expiration Date fields to remain optional for document types where they are not required. opw-6359503 Forward-Port-Of: odoo/odoo#283024 Forward-Port-Of: odoo/odoo#275533
This fixes a filtering issue that allowed temporary wizard screens to appear where users select UTM-related references. The change keeps those reference choices cleaner and prevents users from selecting records that should not be available.
Original PR description
Various places mistakenly used `model.is_transient()` to filter the transient models, where the model is `ir.model` record itself, which always returns False since `ir.model` is a regular persistent model. As a result, transient models (wizards) were never filtered out and allowed into the `utm_reference` Reference field selection. This commit fixes it by using `self.env[model.model].is_transient()` to call `is_transient` on the actual model. Task-6458883 Forward-Port-Of: odoo/odoo#282711
This fix ensures that sales orders configured to require a customer signature are checked correctly during payment. It helps prevent orders from being paid before required approval steps are completed, improving process compliance for sales and point-of-sale flows.
Original PR description
See also: - https://github.com/odoo/enterprise/pull/127041 Forward-Port-Of: odoo/odoo#280403
Clicking the cashier status icon in Point of Sale could cause an error when Belgian Blackbox was enabled but employee login was disabled. This fix prevents that crash by only opening the cashier selector when employee login is available, keeping sessions usable in that setup.
Original PR description
Steps: ----------- - Install pos_blackbox_be. - Configure a PoS with Blackbox Belgium enabled and `Log in with Employees` disabled. - Open a PoS session and click exactly on the session status circle on the cashier icon. Issue: ----------- - A traceback is raised with the following error: `this.cashierSelector is not a function`. Cause: ----------- - Installing pos_blackbox_be makes the cashier icon appear clickable by adding the `pe-auto` class to the cashier icon's session status circle, even when `Log in with Employees` is disabled. In this configuration, the cashier selector is unavailable, causing the click handler to fail. Fix: ----------- - Add a dedicated onClick handler to the CashierName button. - Return early when `module_pos_hr` is not enabled before calling `selectCashier`, ensuring that `selectCashier` is called only when the `module_pos_hr` configuration is enabled. Task-6369404 Forward-Port-Of: odoo/odoo#282656
Imported UBL invoices with document-level discounts or extra charges now correctly match those amounts to the right tax totals. This prevents incorrect tax adjustments during invoice import and improves accounting accuracy for affected supplier or customer invoices.
Original PR description
When importing UBL invoices that contain document-level allowances or charges with percentage taxes, the tax values were not linked to their corresponding `TaxSubtotal` group (`related_taxes_values`). As a result, the tax correction step (`_import_ubl_invoice_fix_taxes_amounts`) was unaware of document-level taxes, which caused wrong tax corrections. opw-6388544 Forward-Port-Of: odoo/odoo#279350
The wording shown when users hover over the Sales Order Expiration field has been corrected to remove a grammatical error and make the guidance easier to understand. This is a minor usability improvement that helps sales users interpret the field more confidently.
Original PR description
Steps to produce: --- - Install the Sales module. - Create a new Sales Order. - Hover over the `Expiration` field. Issue: --- - The help text of the Expiration field contains a grammatical error and the overall sentence is slightly awkward. Improve the help text to make it grammatically correct and more natural. opw-6481226 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283187
This fixes an issue where simultaneous email notification updates could cause email sending to report a misleading follow-up error instead of the real conflict. The change preserves the original error information, making failures easier to diagnose and reducing confusion during mail processing.
Original PR description
When updating mail notifications during `mail.mail._send()`, a `SerializationFailure` raised while flushing the notification recordset leaves the current transaction in an aborted state. As `_send()`…
When updating mail notifications during `mail.mail._send()`,
a `SerializationFailure` raised while flushing the notification recordset leaves the current transaction in an aborted state.
As `_send()` continues handling the exception, accessing fields:
- https://github.com/odoo/odoo/blob/127f1316540ec6cc6880ea3515e6923ba3903bc7/addons/mail/models/mail_mail.py#L816
So, any subsequent SQL query fails with
`InFailedSqlTransaction`, masking the original concurrency error.
Avoid accesing to `mail.message_id` with aborted cursor, preserving the original `SerializationFailure`.
A regression test is added to simulate a concurrency failure during
`flush_recordset()` and verify that the cursor is no longer used dirty
The logger for the unittest without the fix is the following:
```log
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "odoo/addons/mail/models/mail_mail.py", line 719, in _send
notifs.flush_recordset(['notification_status', 'failure_type', 'failure_reason'])
File "<string>", line 3, in flush_recordset
File "unittest/mock.py", line 1139, in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "unittest/mock.py", line 1143, in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "unittest/mock.py", line 1204, in _execute_mock_call
result = effect(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/addons/test_mail/tests/test_message_post_concurrent.py", line 93, in mocked_mail_notification_flush_recordset
return original_flush_recordset(self, *vals, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/models.py", line 6788, in flush_recordset
self._flush(fnames)
File "odoo/odoo/models.py", line 6852, in _flush
model.browse(some_ids)._write_multi(vals_list)
File "odoo/odoo/models.py", line 4938, in _write_multi
self.env.execute_query(SQL(
File "odoo/odoo/api.py", line 993, in execute_query
self.cr.execute(query)
File "odoo/odoo/sql_db.py", line 371, in execute
res = self._obj.execute(query, params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
psycopg2.errors.SerializationFailure: could not serialize access due to concurrent update
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "odoo/addons/test_mail/tests/test_message_post_concurrent.py", line 107, in test_mail_send_dirty_cursor
mails.send()
File "odoo/addons/mail/models/mail_mail.py", line 652, in send
self.browse(batch_ids)._send(
File "odoo/addons/mail/models/mail_mail.py", line 818, in _send
mail.id, mail.message_id)
^^^^^^^^^^^^^^^
File "odoo/odoo/fields.py", line 1309, in __get__
self.compute_value(recs)
File "odoo/odoo/fields.py", line 1491, in compute_value
records._compute_field_value(self)
File "odoo/odoo/models.py", line 5302, in _compute_field_value
fields.determine(field.compute, self)
File "odoo/odoo/fields.py", line 113, in determine
return needle(records, *args)
^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/fields.py", line 710, in _compute_related
record[self.name] = self._process_related(value[self.related_field.name], record.env)
~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/models.py", line 7083, in __getitem__
return self._fields[key].__get__(self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/fields.py", line 1272, in __get__
recs._fetch_field(self)
File "odoo/odoo/models.py", line 4120, in _fetch_field
self.fetch(fnames)
File "odoo/addons/mail/models/mail_message.py", line 756, in fetch
return super().fetch(field_names)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/models.py", line 4158, in fetch
fetched = self._fetch_query(query, fields_to_fetch)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/models.py", line 4245, in _fetch_query
rows = self.env.execute_query(query.select(*sql_terms))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/api.py", line 993, in execute_query
self.cr.execute(query)
File "odoo/odoo/sql_db.py", line 371, in execute
res = self._obj.execute(query, params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
psycopg2.errors.InFailedSqlTransaction: current transaction is aborted, commands ignored until end of transaction block
```
Real error in production:
```log
2023-04-15 10:43:14,978 106451 ERROR my_db odoo.sql_db: bad query: UPDATE "mail_notification" SET "failure_reason" = "__tmp"."failure_reason"::text, "failure_type" = "__tmp"."failure_type"::VARCHAR, "notification_status" = "__tmp"."notification_status"::VARCHAR FROM (VALUES (4426629, 'Error without exception. Probably due to concurrent access update of notification records. Please see with an administrator.', 'unknown', 'exception')) AS "__tmp"("id", "failure_reason", "failure_type", "notification_status") WHERE "mail_notification"."id" = "__tmp"."id" ERROR: could not serialize access due to concurrent update
```
```log
2023-04-14 10:43:14,978 106451 ERROR my_db odoo.sql_db: bad query: UPDATE "mail_mail" SET "failure_reason"='Error without exception. Probably due do sending an email without computed recipients.',"headers"='{''X-SMTPAPI'': ''{"ip_pool": "Transactional"}'', ''X-Odoo-Objects'': ''sale.order-1436960''}',"state"='exception',"write_uid"=1,"write_date"=(now() at time zone 'UTC') WHERE id IN (2548540)
ERROR: current transaction is aborted, commands ignored until end of transaction block
```
Description of the issue/feature this PR addresses:
Current behavior before PR:
Desired behavior after PR is merged:
# UPDATE 2026-07-22
The reviewer requested to remove the large docstring
For record, the docstring was
```python
"""Reproduces a concurrency scenario where `mail_mail._send()` fails with a PSQL SerializationFailure after
flushing `mail.notification` records. After such a failure, the cursor is left in an aborted
(`InFailedSqlTransaction`) state, so any further SQL access (e.g. reading `mail.message_id` like
https://github.com/odoo/odoo/blob/127f1316540ec6cc68/addons/mail/models/mail_mail.py#L816)
would raise a new error masking the original SerializationFailure.
Setup:
- Uses a separate `cursor()` to create and commit a message with its `mail.mail` and `mail.notification`
records, so they are visible to a second, concurrent transaction.
Concurrency simulation:
- `MailNotification.flush_recordset` is patched so that, right before the real flush runs, a second cursor
updates the same `mail.notification` records (`failure_reason`). This forces PSQL to raise a
SerializationFailure when the original transaction tries to flush those rows.
Assertions:
- `SerializationFailure` is raised confirming the concurrency conflict.
- `mail_mail._send()` logs the expected error message containing the mail `id` and `message-id`
Cleanup: created records are unlinked in `finally`
"""
```
# UPDATE 2026-07-23
The reviewer requested to remove the unittest
For record, the unittest was
```diff
diff --git a/addons/test_mail/tests/test_message_post.py b/addons/test_mail/tests/test_message_post.py
index 53dd5b9eec52..46a3958a5bff 100644
--- a/addons/test_mail/tests/test_message_post.py
+++ b/addons/test_mail/tests/test_message_post.py
@@ -7,17 +7,21 @@ from datetime import datetime, timedelta
from freezegun import freeze_time
from itertools import product
from markupsafe import escape, Markup
+from psycopg2.errorcodes import SERIALIZATION_FAILURE as SERIALIZATION_FAILURE_CODE
+from psycopg2.errors import SerializationFailure
from unittest.mock import patch
-from odoo import tools
+from odoo import SUPERUSER_ID, api, tools
from odoo.addons.base.tests.test_ir_cron import CronMixinCase
-from odoo.addons.mail.tests.common import mail_new_test_user, MailCommon
+from odoo.addons.mail.models.mail_notification import MailNotification
+from odoo.addons.mail.tests.common import mail_new_test_user, MailCommon, MockEmail
from odoo.addons.test_mail.data.test_mail_data import MAIL_TEMPLATE_PLAINTEXT
from odoo.addons.test_mail.models.test_mail_models import MailTestSimple
from odoo.addons.test_mail.tests.common import TestRecipients
from odoo.api import call_kw
from odoo.exceptions import AccessError
-from odoo.tests import tagged
+from odoo.modules.registry import Registry
+from odoo.tests import TransactionCase, get_db_name, tagged
from odoo.tools import mute_logger, formataddr
from odoo.tests.common import users
@@ -2244,3 +2248,49 @@ class TestMessagePostLang(MailCommon, TestRecipients):
self.assertIn('html lang="es_ES"', email['body'])
else:
self.assertIn('html lang="en_US"', email['body'])
+
+
+@tagged('database_breaking')
+class TestMessagePostConcurrent(MockEmail, TransactionCase):
+ """Mail concurrency edge cases that require real, separately committed transactions
+ instead of the usual rollback-based TransactionCase isolation.
+ """
+
+ def test_mail_send_dirty_cursor(self):
+ """Reproduces SerializationFailure `mail_mail._send()` fails,
+ the cursor is left in an aborted state, so any further SQL access would raise a new error
+ (e.g. reading `mail.message_id` like
+ https://github.com/odoo/odoo/blob/127f1316540ec6cc68/addons/mail/models/mail_mail.py#L816)
+ """
+ original_flush_recordset = MailNotification.flush_recordset
+
+ def mocked_mail_notification_flush_recordset(self, *args, **kwargs):
+ with Registry(get_db_name()).cursor() as cr:
+ cr.execute('UPDATE mail_notification SET failure_reason = %s WHERE id IN %s', ('Forced Concurrent Update', tuple(self.ids)))
+ return original_flush_recordset(self, *args, **kwargs)
+
+ recs2unlink = []
+ with Registry(get_db_name()).cursor() as cr:
+ env = api.Environment(cr, SUPERUSER_ID, {})
+ partner = env.ref('base.user_admin').partner_id
+ try:
+ message = partner.message_post(body='Hello', message_type='comment', partner_ids=[partner.id], mail_auto_delete=False, force_send=False)
+ notifs = env['mail.notification'].search([('notification_type', '=', 'email'), ('mail_mail_id', 'in', message.mail_ids.ids)])
+ self.assertTrue(notifs)
+ mails = message.mail_ids
+ recs2unlink.extend([notifs, mails, message])
+ cr.commit()
+
+ mails = self.env[mails._name].browse(mails.ids)
+ with (
+ mute_logger('odoo.sql_db'), self.assertRaises(SerializationFailure) as exc, self.mock_mail_gateway(),
+ patch(f'{MailNotification.__module__}.{MailNotification.__name__}.flush_recordset', autospec=True, side_effect=mocked_mail_notification_flush_recordset),
+ self.assertLogs('odoo.addons.mail.models.mail_mail', level='ERROR') as log_capture,
+ ):
+ mails.send()
+ finally:
+ for rec2unlink in recs2unlink:
+ env[rec2unlink._name].browse(rec2unlink.ids).unlink()
+
+ self.assertEqual(exc.exception.pgcode, SERIALIZATION_FAILURE_CODE)
+ self.assertIn(f'Exception while processing mail with ID {mails.id} and Msg-Id \'{mails.message_id}\'.', [record.message for record in log_capture.records])
```
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#279897
Forward-Port-Of: odoo/odoo#274089This fix prevents already approved employee leave from having its payroll work entry type recalculated unexpectedly. Only draft leave requests are updated, helping preserve accurate payroll and time-off records after validation.
Original PR description
We should recompute the work entry type of only draft leaves. 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#283512
This fix prevents invoice imports from failing when a line has a 100% discount and tax is included in the price. Odoo now recalculates the tax from the original price in that case, improving reliability for electronic invoice processing.
Original PR description
Due to the following commit: 01efd8cfcce3269ca6b88d549a670b08a90298cb, a division by zero error is raised when a 100% discount is used with a price-included tax. When the discount is 100%, it is impossible to retrieve the original tax amount before discount using a simple multiplication as the current raw_tax_amount_currency is zero. In that case, we need to recompute taxes using the original unit price before discount. opw-6242701 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282430
Odoo now better identifies the correct partner or company when a formatted email address includes both a name and email. This prevents messages, such as invoice emails, from showing the wrong sender when multiple companies use the same email address.
Original PR description
### Issue: When multiple partners share the same email address, `_mail_find_partner_from_emails` may resolve to the wrong partner when the input is a formatted email like "`Name <email>`" This…
### Issue:
When multiple partners share the same email address, `_mail_find_partner_from_emails` may resolve to the wrong partner when the input is a formatted email like "`Name <email>`"
This affects use cases like email templates using `{{object.company_id.email_formatted}}` as sender, where the wrong company partner could be selected
### Cause:
The lookup in `done_partners` only matched on `email_normalized`, which cannot distinguish partners sharing the same email but with different names
The `email_formatted` field carries both name and email, allowing an exact match when the input is a formatted email
### Steps to reproduce:
- Install `account`
- Create an Email Template (Applies to: account.move, From: {{object.company_id.email_formatted}})
- Create a second company B with the same email as the default (e.g. info@yourcompany.com)
- In Settings (logged in as company B), set a Fiscal Position (e.g. US Taxable)
- Create an Invoice on company B
- In the chatter, click Send message, click the expand arrows button, use the three dots menu to select the template
- Send and check the Sender in the chatter
Before the fix, the sender resolves to the default company even though the invoice belongs to company B
opw-6260992
Forward-Port-Of: odoo/odoo#282966
Forward-Port-Of: odoo/odoo#269509This update ensures image-related tests run reliably on Ubuntu Jammy by using a Pillow option available across supported versions. It reduces environment-specific test failures without changing business functionality.
Original PR description
`Image.Palette.ADAPTIVE` is not available in the Pillow version provided by Ubuntu Jammy, causing the animated GIF test to fail. Use `Image.ADAPTIVE` instead, which is compatible with both older and newer Pillow versions. 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#282388 Forward-Port-Of: odoo/odoo#282223
Australian payroll batch processing now handles employees without leave allocations correctly. This prevents errors when payroll teams generate mixed batches of payslips, allowing processing to continue smoothly with zero unused leave where applicable.
Original PR description
Creating a batch of payslips mixing employees with and without leave allocations raises a KeyError: ``` File "l10n_au_hr_payroll/models/hr_payslip.py", line 869, in _add_unused_leaves_to_payslip…
Creating a batch of payslips mixing employees with and without leave allocations raises a KeyError:
```
File "l10n_au_hr_payroll/models/hr_payslip.py", line 869, in _add_unused_leaves_to_payslip
annual_gross = leaves_totals[payslip.id]['annual'] * daily_wage
~~~~~~~~~~~~~^^^^^^^^^^^^
KeyError: 22
```
Current Issue:
`_l10n_au_get_unused_leave_by_type` only materialises leaves_by_date[payslip.id] inside the allocation loop, so a payslip whose employee has no matching allocation never gets a key. `_l10n_au_get_unused_leave_totals` then rebuilt a plain dict out of those entries and only fell back to a defaultdict when leaves_by_date was completely empty. A mixed batch is not empty, so the plain dict was returned and `_add_unused_leaves_to_payslip` raised on the payslips that were missing from it.
This never showed up in the UI, **where payslips are created one at a time**: a single slip either has an allocation, or produces an empty mapping that hits the fallback.
Approach:
Build the totals on a defaultdict and update it instead of returning a plain dict, so any payslip without allocation resolves to 0 rather than being absent. This also drops the need for the empty special case, and keeps the mapping consistent with the defaultdict returned by `_l10n_au_get_unused_leave_by_type`, which `_l10n_au_get_leaves_for_withhold` indexes the same way.
task-6465229
Forward-Port-Of: odoo/enterprise#127623TikTok order synchronization now skips shops that have not completed authorization. This prevents scheduled order syncs from failing when a newly connected shop is still waiting for TikTok authorization, improving reliability for sales operations.
Original PR description
Currently, an error occurs when orders are being fetched from shops with pending authorization. Steps to replicate: - Install `sale_tiktok`. - Open Sales > Configuration > Shops (Under the title…
Currently, an error occurs when orders are being fetched from shops with pending authorization.
Steps to replicate:
- Install `sale_tiktok`.
- Open Sales > Configuration > Shops (Under the title tiktok shops).
- Click `Connect New Shop` > Give values for `App key, App secret, Service ID`.
- Click `Connect Shop & Authorize` and then Return back to Odoo.
- Run the Scheduled Action `TikTok Shop: sync orders`.
Error:
```
File '/home/odoo/src/enterprise/saas-19.4/sale_tiktok/utils.py', line 171, in make_tiktok_api_request
if now > shop.access_token_expire_datetime - timedelta(minutes=5):
TypeError: unsupported operand type(s) for -: 'bool' and 'datetime.timedelta'
ValueError: TypeError('unsupported operand type(s) for -: 'bool' and 'datetime.timedelta'') while evaluating
'model._sync_orders()'
```
Cause:
- Since the shop has not yet been authorized with TikTok, the `access_token_expire_datetime` field is not set. This field is only populated after the shop is successfully authorized (see [this]).
- Later, when the `TikTok Shop: sync orders` cron runs, the flow reaches [here], where we checks whether the access token is expired and needs to be refreshed. At this point, `access_token_expire_datetime` is still False because the shop has not been authorized yet.
Solution:
- The orders should only be fetched from those shops that are authorized with TikTok.
- Used the `access_token` field to determine whether a shop is authorized, as it is only populated after the authorization flow is successfully completed.
[this]: https://github.com/odoo/enterprise/blob/593409ca160863d3e985f3cf5e2aadda1d450b41/sale_tiktok/controllers/onboarding.py#L52-L54
[here]: https://github.com/odoo/enterprise/blob/593409ca160863d3e985f3cf5e2aadda1d450b41/sale_tiktok/utils.py#L171
sentry-7631179329
Forward-Port-Of: odoo/enterprise#127148Opening spreadsheet version history now uses the correct type of database access from the start. This avoids an unnecessary retry behind the scenes, making the action more reliable for users viewing document history.
Original PR description
The get_spreadsheet_history method is marked as readonly, causing RPC requests to use a read-only transaction. However, retrieving the metadata of a document spreadsheet updates its spreadsheet contributors. Opening the version history consequently attempts an UPDATE in a read-only transaction and forces the request to be retried with a read-write cursor. Remove the readonly decorator so the request uses a read-write cursor directly. Task-6176364 Forward-Port-Of: odoo/enterprise#126626
Fixed an issue where WhatsApp message templates with many mixed variable types could send values in the wrong placeholder positions. This helps ensure customers receive messages with the correct personalized information, especially in larger templates.
Original PR description
**Issue**:
Sending a WhatsApp template with 10 or more variables can assign values to the wrong placeholders when the body contains mixed variable types, such as free text, field, or user name variables.
Templates containing only free-text variables are not affected.
**Reason**:
Meta consumes template parameters positionally, but for mixed variable types, Odoo built the parameter list using the template variable recordset order.
That order can differ from the numeric placeholder order, notably placing {{10}}, {{11}}, {{12}}... before {{1}}
when sending the message, as the payload parameters are not ordered by their numeric placeholder index.
**Fix:**
Sort body variables by their numeric placeholder index before preparing the Meta payload.
Task-6401501
Forward-Port-Of: odoo/enterprise#128492
Forward-Port-Of: odoo/enterprise#125671Bank journal screens now hide the send-now button and connection request when the journal is not set to the online synchronization statement source. This prevents users from seeing bank sync actions that are no longer relevant after changing the statement source.
Original PR description
Before this commit, the "send now" button and the connection request were shown as soon as we had an account online account link to the journal. But when changing the bank statement source, the information would still be there. Changing the invisible condition to hide it when the bank statement source is different from only_sync no task id Forward-Port-Of: odoo/enterprise#128367
Regular Timesheets users can now access Assistant Rules when the assistant feature is enabled, even if billing rate indicators are turned off. This prevents users from being blocked from needed configuration options unless they use debug mode.
Original PR description
Steps to reproduce --- 1. Install sale_timesheet_enterprise. 2. Go to Timesheets > Configuration > Settings (as Admin). 3. Disable the "Billing Rate Indicators" setting and enable timesheet…
Steps to reproduce --- 1. Install sale_timesheet_enterprise. 2. Go to Timesheets > Configuration > Settings (as Admin). 3. Disable the "Billing Rate Indicators" setting and enable timesheet assistant. 4. Log in as a normal user with "Timesheets > Own timesheet" access. 5. Open the Timesheets app. Issue --- The "Configuration" menu is completely hidden. The regular user cannot access the "Assistant Rules" menu unless they turn on debug mode. Cause --- When the billing rates feature is turned off, the _load_menus_blacklist function hides the Enterprise Configuration menu. The logic used an and condition, meaning the menu was only kept visible if the user was an Assistant AND had Manager/Admin rights. This locked out standard users. Fix --- Change the blacklist condition. Allow the Enterprise Configuration menu to stay visible if the user needs it for the Assistant feature (when UoM is not Days), OR if the user is a Manager/Admin. References to check other issues https://github.com/odoo/enterprise/pull/120609 https://github.com/odoo/enterprise/pull/117984 task - 6470183
This fix prevents the timesheet assistant from failing when saving or updating suggestions in helpdesk-related timesheet workflows. It keeps selections and suggestions consistent, reducing interruptions for users entering timesheets.
Original PR description
The `helpdesk_timesheet` override of `_getLocalConfigValsOnTake` called `this._is_record()`, a method that does not exist. This PR makes it call `_getResId` instead Task-6385031 Forward-Port-Of: odoo/enterprise#124075
Accounting users can now export the German Datev ATCH zip even when an invoice's main image attachment was added through a log note by someone else. This prevents an access error that blocked non-admin users from completing the export.
Original PR description
### Issue: When an invoice has an image as its main attachment added via a log note, any non-admin user who did not create the attachment gets an `AccessError` when exporting the Datev ATCH zip ###…
### Issue: When an invoice has an image as its main attachment added via a log note, any non-admin user who did not create the attachment gets an `AccessError` when exporting the Datev ATCH zip ### Cause: Since commit `e7c93e5a6f`, attachments uploaded via certain flows can be "orphaned" — their `res_model` is set to `False` and `res_id` to `0` via `_fix_attachments_on_record_from_files_data` This allows the attachment to appear in the chatter without being linked to the move's attachment list However, `_message_set_main_attachment_id` can still set such an orphaned attachment as `message_main_attachment_id` When a user without system rights tries to read it, the ORM access check uses `res_model=False` and `res_id=0`, which does not match the move the user has access to, raising an `AccessError` ### Steps to reproduce: - Install `l10n_de_reports` and switch to the DE company - Create and confirm an Invoice (any lines, any customer) - Add a Log Note with an image - Set Demo user's Accounting rights to `Invoicing & Banks` - Log in as Demo - Open the General Ledger - In the cog menu, choose `Datev ATCH (zip)` Before the fix, an `AccessError` is raised opw-6397804 Forward-Port-Of: odoo/enterprise#128484 Forward-Port-Of: odoo/enterprise#127787
Fixed an issue that blocked reconciliation of internal transfers between a parent company and a branch when the branch transaction used a reconciliation model. This prevents unnecessary company inconsistency errors and lets accounting teams complete valid intercompany bank matching without manual workarounds.
Original PR description
When reconciling an internal transfer between a branch and its parent company, an User Error is raised if the branch transaction has been processed via reconciliation model. Steps to reproduce: -…
When reconciling an internal transfer between a branch and its parent company, an User Error is raised if the branch transaction has been processed via reconciliation model. Steps to reproduce: - Have a company with branch both selected - On the branch, create a reconciliation model "Internal transfer" that assigns the whole balance to the liquidity transfer account - Have a Bank journal on the company and a Bank journal on the branch - On the branch bank journal, create a -100 transaction 'testb' and reconcile it using the branch internal transfer model - On the company bank journal, creata a 100 transaction, open the reconciliation widget and select the branch transaction to match it Issue: The reconciliation is refused with a company inconsistency error ``` Uh-oh! You’ve got some company inconsistencies here: - “BNK1/2026/00011 test” belongs to company “YourCompany” while “Reconciliation Model” (reconcile_model_id: 'Internal Transfer branch') belongs to another company. To avoid a mess, no company crossover is allowed! ``` However, if user manually assign the transfer account to the branch transaction, the reconciliation proceed as expected Analysis: When reconciling, we build the counterpart journal item by cloning the values of the matched move line, copying also the reconcile model. That field is company dependent and flagged copy=False, so it should not be propagated. opw-6365856 Forward-Port-Of: odoo/enterprise#127517
When generating a payroll payment file with the option to include unpaid payslips, all payslips actually included in that file are now marked as paid. This prevents payroll records from incorrectly remaining validated after payment processing, reducing manual follow-up for payroll teams.
Original PR description
Steps to reproduce: - Open the payment report wizard on a payslip or a pay run - Tick "Include Unpaid" and keep the extra payslips selected - Generate the SEPA file, then click "Mark as Paid" Issue: the extra payslips listed in the file stay in state "validated". Cause: mark_as_paid() paid payslip_ids, while the file is built from unpaid_payslips. Fix: pay the payslips that are actually listed in the file. Task 6428919 Forward-Port-Of: odoo/enterprise#127889
Odoo now saves successful UrbanPiper delivery provider status confirmations as soon as they reach the server. This prevents providers from appearing disabled in the POS when they were actually enabled through UrbanPiper, improving operational accuracy for restaurants using online delivery integrations.
Original PR description
When a POS session opens, every configured delivery provider is enabled through UrbanPiper. UrbanPiper later confirms the operation through a store_action webhook. Previously, Odoo only forwarded this confirmation to the POS frontend. The frontend was then responsible for persisting the new provider state. If no POS client received the notification, the confirmed state was not saved. Consequently, a provider could be enabled in UrbanPiper while appearing disabled in the POS. Persist successful store_action callbacks directly when they are received by the server. Update only the affected provider and let the frontend consume the resulting state without writing its potentially stale provider-state mapping back to the server. opw-6455361
German SEPA credit transfer files now leave out a company identifier that is not accepted in the older German bank file format. This keeps exported payment files valid and helps prevent banks from rejecting vendor payment batches.
Original PR description
### Issue before this commit: When generating a SEPA Credit Transfer batch using the German XML format (pain.001.001.03), the <LEI> tag is erroneously included in the exported file if an LEI is…
### Issue before this commit: When generating a SEPA Credit Transfer batch using the German XML format (pain.001.001.03), the <LEI> tag is erroneously included in the exported file if an LEI is configured on the company. This invalidates the XML, causing banks to reject the file. ### Steps to reproduce the issue: 1. Download Accounting and l10n_de 2. Go to Settins > Vendor Payments > SEPA Credit Transfer / ISO20022 and set Name Identification as 529900T8BM49AURSDO55 and Issuer as LEIMAN 3. Go to companies and set 529900T8BM49AURSDO55 as LEI in the DE company 4. Go to Accounting dashboard and click the 3 dots of the bank group, go to Configuration and set the Account Number and be sure in the Outgoing Payments tab XML Format is German 5. Create a new German company from Contacts with: 1. Country as Germany 2. VAT 3. Account Number in the Bank Accounts by adding one line: 1. example Account Number: DE65100500007201811026 2. example Bank: BNP Paribas 3. activate the Send Money button 7. Then go to Vendor > Payments and create a new one with Payment Method as SEPA Credit Transfer for the German company created 8. Go back and select the new payment from the list and click create batch and print it 9. In the XML of pain.001.001.03.(DE) file, the LEI tag should not be included. ### Cause of the issue: The XML generation logic does not filter out the <LEI> element for older schema versions like pain.001.001.03, which do not support this tag. ### Reason to introduce the fix: To ensure strict schema compliance and prevent bank rejections. The <LEI> element is now properly omitted from pain.001.001.03 files and restricted only to newer formats (e.g., pain.001.001.09) where it is valid. opw-6428150 Forward-Port-Of: odoo/enterprise#127758
The timesheet timer in the top menu no longer disappears when the server reaches midnight before the user does. This keeps ongoing time tracking visible and accurate for employees working in time zones behind the server timezone.
Original PR description
**Problem:** The running timesheet timer in the systray stops when the Odoo server clock passes UTC midnight, even though it is still the same day in the user's own timezone. **Steps to reproduce:**…
**Problem:** The running timesheet timer in the systray stops when the Odoo server clock passes UTC midnight, even though it is still the same day in the user's own timezone. **Steps to reproduce:** 1. Set the user's timezone to one behind UTC (e.g. America/Guadeloupe, UTC-4). 2. Start a timesheet timer while it is before local midnight but after the server has passed UTC midnight (e.g. 20:00 local = 00:00 UTC). 3. Look at the running timer in the systray. **Current behavior:** At server (UTC) midnight the running timer disappears and its ongoing count is lost. **Expected behavior:** The timer keeps running until the user's own local midnight, regardless of the server timezone. **Cause of the issue:** The systray controller derives its reference day from `date.today()`, which returns the server's (UTC) local date. The running timer's timesheet is created dated the user's local day (`hr_timesheet` uses `fields.Date.context_today`). Once the server crosses UTC midnight, `date.today()` advances to the next day while the user's local day has not, so `timesheet_systray_user_data` (searching `date == today`) and `get_timer_start_time` (searching `date` within today's bounds) no longer match the running timesheet, and the systray reports no running timer. **Fix:** Deriving the reference day from `fields.Date.context_today` aligns the systray's notion of "today" with the timezone the timesheet was recorded in, so the timer is tied to the user's local day rather than the server's. This keeps recording and retrieval consistent, since the timesheet is already dated with the user-local day on creation. opw-6343442 Forward-Port-Of: odoo/enterprise#125635
A test that checks the online purchase flow for rental planning products has been temporarily disabled. This avoids repeated false failures while the related rental planning process is still changing, with no direct impact on customers using the feature.
Original PR description
Given the rapid changes in spec for `{website_}sale_renting_planning` it doesn't make sense to fix the tour only for the flow to break right away after. Therefore, the tour is temporarily disabled until the flow of the module(s) is finalized.
task-6389324
Forward-Port-Of: odoo/enterprise#128170This update adjusts internal subscription-related tests so they stay aligned with related platform changes. It helps maintain release quality and reduces the risk of false test failures, with no expected direct impact on day-to-day users.
Original PR description
See also: - https://github.com/odoo/odoo/pull/280403 Forward-Port-Of: odoo/enterprise#127041
This fixes a test inconsistency in Peru electronic invoicing caused by different wording output from dependency versions. It helps keep automated validation reliable across supported Python environments without changing business behavior.
Original PR description
### Issue: `test_invoice_down_payment_with_withholding_tax` fails on RunBot when using `num2words==0.5.10` (Python < 3.12) The expected XML contains `DIECISÉIS` but older versions of `num2words` generate `DIECISEIS` without the accent ### Cause: The accent on `DIECISÉIS` was added in `num2words` PR #443, between versions `0.5.10` and `0.5.13` RunBot uses different versions depending on the Python version: `num2words==0.5.10` for Python < 3.12 (Jammy / Bookworm) `num2words==0.5.13` for Python >= 3.12 ### Steps to reproduce: - Run the test with `num2words==0.5.10` Before the fix, the test fails on the `cbc:Note` comparison runbot-945461 Forward-Port-Of: odoo/enterprise#127356
Barcode deliveries now use the actual storage location of a scanned serial-numbered item when no source location is scanned. This prevents stock from being deducted from the wrong parent location and avoids incorrect inventory balances.
Original PR description
Steps to reproduce --- 1. Enable Storage Locations and Lots/Serial Numbers. 2. Set the delivery operation type's "Source Location" to "Do not scan". 3. Create a serial-tracked product with a serial…
Steps to reproduce --- 1. Enable Storage Locations and Lots/Serial Numbers. 2. Set the delivery operation type's "Source Location" to "Do not scan". 3. Create a serial-tracked product with a serial stored in a sublocation (e.g. WH/Stock/Section 2). 4. Confirm a sale order for it, open the delivery in Barcode, and scan an unreserved serial. Issue --- Scanning the unreserved serial creates a new move line that falls back to _defaultLocation() because the decoded scan carries no source location (the operation type does not require scanning one) and never carries the serial's quant location. https://github.com/odoo/enterprise/blob/f42cfa7265ce32bd95f7f805cf4fb54d8b79cce1/stock_barcode/static/src/models/barcode_model.js#L937-L944 For a delivery, that default resolves to the picking's own source location (the parent WH/Stock), so the line is sourced from the parent instead of the sublocation where the serial physically sits. https://github.com/odoo/enterprise/blob/f42cfa7265ce32bd95f7f805cf4fb54d8b79cce1/stock_barcode/static/src/models/barcode_picking_model.js#L1542-L1544 On validation the unit is deducted from the parent location instead of the sublocation, leaving a stale quant of the serial in the sublocation and a negative quant at the parent. opw-5864414 Forward-Port-Of: odoo/enterprise#128523 Forward-Port-Of: odoo/enterprise#121375