Friday, August 21, 2026
65 changes · saas-19.1
New functionality added to Odoo
Adds Sri Lanka-specific tax invoice numbering, VAT registration detection, and invoice PDF wording so businesses can meet local tax invoice requirements. The update helps ensure qualifying invoices show the correct tax invoice title, supply date, payment mode, and compliant sequence format.
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
Polish companies can now prepare a dedicated VAT-UE report instead of relying on the generic EC Sales List. The report includes key intra-EU transaction types and supports the official XML export format, helping businesses meet local filing requirements more efficiently.
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 are now treated like mainland France when determining the electronic invoicing address type. This helps companies in these regions get the correct France FRCTC Electronic Address automatically, reducing manual setup and improving Peppol/PDP compliance.
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
Fixes an Email Marketing editor issue where image movement or deletion controls could appear on top of replacement dialogs. This keeps dialog windows clearly visible and easier to use when editing mailing content.
Original PR description
Problem: Overlay option buttons for mailing element movement, duplication & deletion are displayed in front of other modals/dialogs. Cause: `useOverlayServiceOffset` offsets all `MassMailingIframe`…
Problem: Overlay option buttons for mailing element movement, duplication & deletion are displayed in front of other modals/dialogs. Cause: `useOverlayServiceOffset` offsets all `MassMailingIframe` overlay sequences by `+1000` (default sequence `50` becomes `1050`). Dialog modals do not have that +1000 sequence and appear behind the buttons. Solution: remove the `useOverlayServiceOffset` hook as it is not needed anymore. Steps to reproduce: - Create a new Email Marketing record. - Add an Image snippet, with the image set to the right. - Click on the image so that the Replace option button appears on the side panel, and movement/deletion controls are right under the image. - Click the Replace option button. - Observe that the movement/deletion controls appear above the dialog when your mouse hovers over the editor Backport of c2e0e89ac95a51682df92e883438c7e0bce16335 , opw-6203734 Co-Authored-By: Guce <guce@odoo.com> task-5477951 X-original-commit: c2e0e89ac95a51682df92e883438c7e0bce16335
Documentation and clarification updates
Sahil Singh added an individual contributor license agreement for contributions to Odoo. This is a legal documentation update that helps ensure current and future code contributions are properly authorized.
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 users can now reprint the entire order as an order change from both the Product Screen and Ticket Screen. This makes restaurant and retail order handling smoother by avoiding the need to reprint individual changes one at a time.
Original PR description
Before this commit: ------------------------------- - From the Product Screen, users could only reprint the last order change, while from the Ticket Screen, they could reprint all previous order changes one by one. After this commit: ----------------------------- - Users can now reprint the entire order as an order change directly from both the Product Screen and the Ticket Screen. Task-6230594 Forward-Port-Of: odoo/odoo#266070
This update enables an additional automated quality check for the web module's document-related tests. It helps maintain more consistent test code and reduces the chance of test maintenance issues over time.
Original PR description
Task-5180137 Forward-Port-Of: odoo/odoo#280233
HR managers can now see and configure whether a specific time off type creates a related Calendar entry. This makes the existing setting easier to use and helps teams control when leave requests appear in calendars.
Original PR description
The `create_calendar_meeting` field on `hr.leave.type` allows users to choose if leave requests created with a given time off type generate a corresponding entry in the Calendar app. However, this field was not displayed on the form view. This commit adds `create_calendar_meeting` to the `hr.leave.type` form view inside the configuration section, along with dedicated help text explaining its behavior. Task: 6445794 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283450
Adds a dedicated view for French PDP e-reporting moves so users can see relevant reporting details without changing the standard accounting entry view. This improves visibility for compliance-related information while keeping the main accounting interface unchanged.
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 punctuation or separators differ. This helps payments reconcile automatically in cases such as an invoice reference with a slash matching a bank label without it, reducing manual follow-up 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
The General Ledger report can now include invoice dates when that column is configured. This gives finance teams more useful reporting detail without needing a separate lookup.
Original PR description
If a column is added with `expression_label` equal to `invoice_date`, include that in results of `_report_custom_engine_general_ledger`. task-5917897 Forward-Port-Of: odoo/enterprise#113774
Users can again access convenient menu actions for document folders, including exporting dynamic folder views to spreadsheets and adding them to knowledge articles. The update also improves shared-link folder behavior while protecting sensitive access tokens from being exposed through knowledge views.
Original PR description
Also impacted: test_documents_full It is convenient to export a dynamic view of a folder in both spreadsheet and knowledge links settings. * Care is taken to avoid leaking access folders tokens through the search panel/model's state in knowledge. * We also enable sharing folders shared via link through embedded views as it enables benefitting from the power of them vs. adding the link to the folder in the article. * As with other actions initiated on shortcuts, the "real" operation is done on the target. Sharing the target is simpler than patching a folder "child_of" to return the target children (shortcut as documents_unique_folder_id is not supported). Task-5180137 Forward-Port-Of: odoo/enterprise#122481
Timesheet suggestions now choose the most recent relevant project from a customer's full company/contact hierarchy rather than only a single matching contact. This makes Gmail and calendar-based timesheet entries more reliable when customers have parent or child contacts sharing related details.
Original PR description
Before this commit: - Gmail emails are resolved to a random timesheeted project linked to a partner having the same email - Calendar events are resolved to the most recent timesheeted project linked to partner_ids In this commit: - Instead of looking to the partner, the most recent timesheeted project is taken from the partner tree (child_ids, parent_id) task-6254947
Bank statement reconciliation can now recognize invoice payment references even when separators such as slashes are missing from the statement label. This helps match payments automatically in more cases, reducing manual reconciliation work for accounting teams.
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
Visitors opening a public channel page in Discuss will remain on that channel instead of being redirected away before messages load. This ensures public conversations are visible and usable for non-members as intended.
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
This fix prevents Odoo from crashing when an accrued expense entry is created for a purchase order whose quantity was changed to zero after receipt. Users can now complete the accrual process cleanly instead of seeing an RPC error, improving reliability for accounting workflows.
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
German invoices using the DIN 5008 layout can now be sent by post without failing Pingen's address validation. The change ensures the recipient address appears in the required mailing window for Snailmail 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
This fixes an issue where color labels used outside the standard color picker did not display correctly. Planning resource entries now keep their intended colors consistently, including in dark mode.
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
Blog pages now count only real public discussions as comments, instead of including internal chatter logs. This keeps visible comment counts accurate for visitors and editors when internal notes are added to a blog post.
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
The Sales app now uses clearer, grammatically correct help text for the Expiration field on sales orders. This small wording fix makes the field easier to understand when users hover over it.
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 fix prevents the delivery date on a posted invoice from being automatically changed when later deliveries are processed for the same sales order. It protects finalized invoice records from silent updates, improving reliability for billing, reporting, and audit consistency.
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
Clicking a related field in the HTML editor now selects its readable display name by default instead of the less useful technical ID. This makes dynamic placeholders clearer for users while still allowing the ID to be selected manually when needed.
Original PR description
*: project Before this commit: when clicking a field having sub fields (canFollowRelationFor is true), we just return this field's id, which is not very useful in most cases. After this commit: We created subclass of DynamicPlaceholderPopover, EditorDynamicPlaceholderPopover, which uses EditorModelFieldSelectorPopover. We use the display name of the followable field by default and if the user really want the id, they may choose the id subfield. We also show the followable field's name as the default placeholder instead of "Display name". task-6265223 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#272129
This fix ensures that read-only connections to replica databases are closed using the correct replica configuration. It prevents unused database connections from remaining open when replica settings differ from the primary database, improving reliability and resource usage.
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
This fix ensures sales orders that require a customer signature are properly checked during payment. It helps prevent orders from being paid or processed before required approval steps are completed.
Original PR description
See also: - https://github.com/odoo/enterprise/pull/127041 Forward-Port-Of: odoo/odoo#280403
This fixes an issue where users on mobile devices could not drag and drop table cells inside the HTML editor. The change prevents phone and tablet browsers from interrupting the drag action, making table editing more reliable on touchscreens.
Original PR description
Steps to Reproduce - Insert a table inside a Todo item. - Long-press the table menu to open the drag-and-drop overlay. - Try to drag and drop table cells. Issue: - Table cells cannot be dragged and dropped on mobile devices. Cause: - On mobile devices, the browser fires `pointercancel`/`pointerleave` during a drag operation, which ends the drag operation prematurely. As a result, subsequent `pointermove` events are not triggered causing the drag-and-drop operation to fail. Solution: - Add `touch-action: none` to the table menu element. This prevents the browser default touch handling from interfering with the drag operation, allowing `pointermove` events to continue and drag-and-drop to work correctly on mobile devices. task-6201176
Searching messages in Discuss now handles accidental extra spaces without causing an error. This prevents interruptions when users search conversations and improves reliability of message lookup.
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 connection or 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#281490Manual replenishment now shows the expected notification when it creates a purchase order. This helps inventory and purchasing users immediately confirm that their replenishment action succeeded, while keeping existing purchase order line merging behavior intact.
Original PR description
Currently when the user does manual replenishment no notification is displayed. ## Steps to produce: - Install Inventory and Purchase - Create a product `Chocolate Icecream` and Enable `Track…
Currently when the user does manual replenishment no notification is displayed. ## Steps to produce: - Install Inventory and Purchase - Create a product `Chocolate Icecream` and Enable `Track Inventory` - Purchase > Add a Vendor `Ice cream man` - Reordering rules > Create a new reordering rule and save: - Trigger: Manual - Min: 5 - Max:10 - Press the `Order` button ## Observed Behavior: No notification is displayed about the newly created purchase order. ## Root cause: When the Order button is pressed, the `action_replenish` method is called. This method invokes `_procure_orderpoint_confirm` at [1]. The `_procure_orderpoint_confirm` function is responsible for creating procurements from orderpoints. During this process, it retrieves the procurement values using `_prepare_procurement_values` that are later used at [2]. However, `_prepare_procurement_values` only includes the orderpoint in the procurement values when the orderpoint's trigger is set to automatic, and not when it is manual, as shown at [3]. These procurement values are then used by `_run_buy` to create a purchase order and purchase order line at [4]. Since the orderpoint is not linked to the purchase order line in this case, no matching order is found at [5], which leads to the reported issue. **Which commit caused this unintentional behavior?** This behavior was unintentionally introduced by this [commit](https://github.com/odoo/odoo/commit/2a0d2c64d0027f540101447289b4c1a10cb3ecdf) . That commit fixed an issue where purchase order lines were not being merged for temporary manual orderpoints that are created dynamically based on product demand. [1]- https://github.com/odoo/odoo/blob/c076281dedeed2e25844c43c49ec17511434e3fc/addons/stock/models/stock_orderpoint.py#L342-L349 [2]- https://github.com/odoo/odoo/blob/c076281dedeed2e25844c43c49ec17511434e3fc/addons/stock/models/stock_orderpoint.py#L737-L741 [3]- https://github.com/odoo/odoo/blob/c076281dedeed2e25844c43c49ec17511434e3fc/addons/stock/models/stock_orderpoint.py#L687-L701 [4]- https://github.com/odoo/odoo/blob/c076281dedeed2e25844c43c49ec17511434e3fc/addons/purchase_stock/models/stock_rule.py#L156-L165 [5]- https://github.com/odoo/odoo/blob/c076281dedeed2e25844c43c49ec17511434e3fc/addons/purchase_stock/models/stock.py#L276-L296 [6]- https://github.com/odoo/odoo/blob/c076281dedeed2e25844c43c49ec17511434e3fc/addons/stock/models/stock_orderpoint.py#L365 [7]- https://github.com/odoo/odoo/blob/6a84d3e519892be333552e2e0ebf8da87e0a760c/addons/purchase_stock/models/purchase_order_line.py#L380-L384 ## Solution: Instead of removing the orderpoint ID from the procurement values, we reuse the same conditions used to identify temporary orderpoints for cleanup at [6]. Based on this, we determine how purchase order lines should be merged in the `_run_buy` method. With the previous implementation, no orderpoint was included in the procurement values. As a result, the condition at [7] checking for orderpoints always evaluated to True, causing the system to identify an existing purchase order line for the same product as a merge candidate. This solution allows us to retain that fix as well as avoid the error of notifications not showing up. opw-6311520 Forward-Port-Of: odoo/odoo#271993
This fixes an issue where importing electronic invoices could fail when a line had a 100% discount and tax already included in the price. Odoo now recalculates the tax from the original price in that case, allowing these invoices to import correctly.
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
This fix ensures Odoo correctly recognizes when the email queue process is being run by a scheduled action, so progress handling works as intended. It also adds logging of the email sending limit to help support teams investigate silent email queue failures, especially when memory limits are involved.
Original PR description
The changes introduced by https://github.com/odoo/odoo/commit/19d5367862528979abdcd411095f18d36bdbe7b8 aimed at aligning the mailing cron job logic with the new `_commit_progress` system. While doing…
The changes introduced by https://github.com/odoo/odoo/commit/19d5367862528979abdcd411095f18d36bdbe7b8 aimed at aligning the mailing cron job logic with the new `_commit_progress` system.
While doing so, it accidentally added an if condition based on `self.env.get('ir_cron')`, which will always return False and never run the progress commit as intended.
To address this, in this PR:
- we change the condition to `if self.env.context.get('cron_id'):`, the cron_id context variable being set when the method was called from a scheduled action
- additionally we take the occassion to add an info log that outputs the computed send limit at the time the method was triggered. This will make it easier to investigate the logs ad-hoc in situations where the "Mail: Email Queue Manager" cron job fails silently because of a memory limit error. A high send limit (batch_size) increases the chances of memory errors proportionally. Knowing what the exact sending limit was at a given point in time makes investigation easier when trying to build a sequence of past events that could explain issues related to email sending.
OPW-6396087
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#282892This fix updates the mail compose process to use the current progress-tracking method instead of an outdated one. It prevents unnecessary warning messages from filling server logs during automated mail queue processing, making operations easier to monitor without changing user-facing behavior.
Original PR description
Since 19.0 `_notify_progress`` is deprecated in favor of `_commit_progress``. See: https://github.com/odoo/odoo/commit/ee337934f9885834d95592946f435c6e1c8ef970 Currently, the mail compose wizard still calls an explicit _notify_progress followed by an explicit commit. This leads to warning tracebacks being dumped into the server logs (for example everytime the "Mail Marketing: Process queue" cron runs). We replace it with an equivalent `_commit_progress` call, which should log the progress and implicitly take care of the cursor commit. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282926
This fix prevents the Point of Sale from showing an error when users click the cashier status icon in configurations where employee login is disabled. It keeps the cashier selector from opening unless the employee login feature is available, making the checkout screen more stable for affected Belgian fiscal setups.
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 charges or discounts now correctly connect those amounts to the matching tax totals. This prevents incorrect tax adjustments during import, improving the reliability of accounting data for affected 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
This fix prevents the online shop product page from accidentally targeting unrelated form fields when handling product option choices. It helps ensure customers see and select the correct product variants during checkout-related shopping flows.
Original PR description
The selector was matching unrelated inputs because it wasn't specific enough. Forward-Port-Of: odoo/odoo#283464
Chat windows on mobile now use a configurable display layer instead of a fixed setting. This helps other parts of Odoo control which elements appear on top, reducing visual overlap issues without changing the default behavior.
Original PR description
The z-index of chat windows on mobile views was previously fixed at `1020`, preventing other modules from adjusting their stacking order. This commit introduces a configurable z-index for chat windows, defaulting to `1020` while allowing other modules to override it when needed. task-6412411 Forward-Port-Of: odoo/odoo#283178
This fix ensures an automated blog editor test starts with the right settings so the dynamic blog snippet is available when needed. It helps prevent false test failures and supports more reliable website blog quality checks without changing the customer-facing product.
Original PR description
The blog post dynamic snippet options tour needs debug mode because the dynamic snippet belongs to the Debug snippet group. The tour used to put `debug=1`in the preview iframe path, while the initial website preview client action was opened without debug. This meant `request.session.debug` was only updated once the iframe request was handled. If the snippet template was rendered before that request, QWeb used the empty session debug value and omitted the Debug snippet group. After this commit, we open the preview action directly in edit and debug mode from the Python test instead, so the first server request sets `request.session.debug` before the website builder loads the snippets. Forward-Port-Of: odoo/odoo#281433
This fixes an attendance issue where shifts ending exactly at midnight could be counted again on the next day during overtime recalculation. Payroll and HR overtime totals are now more reliable for employees with split shifts or late shifts ending at day boundary.
Original PR description
When recomputing overtime, attendances overlapping the affected day are retrieved based on their check-in and check-out. An attendance whose check-out is exactly at the start of the following day is…
When recomputing overtime, attendances overlapping the affected day are retrieved based on their check-in and check-out.
An attendance whose check-out is exactly at the start of the following day is currently considered to overlap that day because the domain uses an inclusive lower bound on `check_out`.
This can cause overtime from the previous day to be recomputed using an incomplete set of attendances.
### Steps to reproduce:
* Configure an employee with a daily quantity overtime rule based on the expected hours from the contract.
* On the first day, create multiple attendances, with the last one ending exactly at midnight.
* Ensure the total worked hours on that day result in overtime.
* On the following day, create another attendance.
* Observe that recomputing the second day's overtime also retrieves the attendance ending at midnight.
* The previous day's overtime is then recomputed without the other attendances from that day, resulting in an incorrect overtime value.
* Regenerating the overtime ruleset restores the correct value.
For example, with 8.4 expected hours:
```
Day 1:
09:30 - 11:30
14:30 - 18:19
21:00 - 00:00
Day 2:
create/update an attendance
```
The `21:00 - 00:00` attendance is incorrectly included in Day 2's recomputation because its check-out equals the start of Day 2. The other Day 1 attendances are not included, so Day 1 is recomputed from only 3 hours of work.
To fix the issue we treat `check_out` as an exclusive interval boundary when determining overlap. An attendance ending exactly at the start of a day does not overlap that day, while attendances actually crossing midnight continue to be included.
opw-5474120
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#283226Edited chatter messages now keep contact mentions linked correctly when one contact's name or ID overlaps with another's. This prevents confusing broken or misplaced mentions after users save message edits.
Original PR description
# Introduction This PR fixes broken mention links linked to the fact that we replace strings without paying attention to the fact that some strings may contain others that we want to replace later.…
# Introduction
This PR fixes broken mention links linked to the fact that we replace strings
without paying attention to the fact that some strings may contain others
that we want to replace later. This affects both id's and names of records.
See commit messages for more details.
# How to reproduce
- Create Contact A and then Contact B and either :
- Contact B's id need to contain Contact A's id (e.g. Contact B id = 12; Contact A id = 1)
- Contact B's name need to contain Contact A's name (e.g. Contact B name = ABC; Contact A name = AB)
- In a chatter create a message mentionning first Contact B and then Contact A
> Depending on the version, you might need to reload the page here
- Edit the message and save
# The issue
We see a broken mention in the chatter
# Cause
When saving an edited message, we give the raw body of the message (without the mention links) and the mentionend partners to `generateMentionsLinks` : https://github.com/odoo/odoo/blob/f9f605b1783d252d5e005bec50a2a72dd4ae0e13/addons/mail/static/src/utils/common/format.js#L152
This method's purpose is to replace the text links ("@Contact A") with actual html links. It does so by enumerating each partner given as an argument and replace the text mention with a placeholder :
https://github.com/odoo/odoo/blob/f9f605b1783d252d5e005bec50a2a72dd4ae0e13/addons/mail/static/src/utils/common/format.js#L158
It will then replace the placeholders with actual links : https://github.com/odoo/odoo/blob/f9f605b1783d252d5e005bec50a2a72dd4ae0e13/addons/mail/static/src/utils/common/format.js#L208-L218
The issue is that in both of those steps, we can try to replace a string that is contained
in another string we want to replace.
For exemple :
"string123 some text string12"
If we try to replace "string12" first, then we will select the wrong string :
"[string12]3 some text string12".
opw-6313748
Forward-Port-Of: odoo/odoo#282354
Forward-Port-Of: odoo/odoo#272549Inventory users can now validate dropship transfers for average-cost products when landed costs are enabled. This prevents an unnecessary access error and keeps the sales-to-purchase dropshipping flow working for standard inventory users.
Original PR description
# How to reproduce - Activate the stock_landed_costs module - Enable Dropshipping - Create a product with : - Category : - Costing Method : AVCO - Inventory Valuation : Perpetual - Routes : Dropship…
# How to reproduce - Activate the stock_landed_costs module - Enable Dropshipping - Create a product with : - Category : - Costing Method : AVCO - Inventory Valuation : Perpetual - Routes : Dropship - Atleast one vendor - Create a SO for that product - Confirm the SO & then Confirm the associated PO - Login as an user with "User" rights for Inventory - Try to validate the Dropship transfer # The issue You get an access error. If the same flow is done with a product with a Standard Price costing method, then the Dropship is properly validated # Cause When validating the Dropship, we'll call `_action_done` on the moves. This will trigger an update of the standard price of the product : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/stock_move.py#L177 https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/stock_move.py#L345-L349 Since we're in avco, this will run the `_run_average_batch` method : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/product.py#L675 That will fetch the value of each moves. For the Dropship moves, it'll do so by calling the `_get_value()` method : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/product.py#L486 This method will compute the value of the move, notably by using the associated landed costs : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/stock_move.py#L431 https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_landed_costs/models/stock_move.py#L14 Now the issue is that this computation calls `_read_group` on 'stock.valuation.adjustment.lines' that are retricted to inventory administrators : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_landed_costs/models/stock_move.py#L11 https://github.com/odoo/odoo/blob/5f6fb63d5d7585805642c702d096b2f882e73761/addons/stock_landed_costs/security/ir.model.access.csv#L4 # Proposed solution Get the value of the move in sudo like previously done in the flow : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/stock_move.py#L314 opw-6323645 Forward-Port-Of: odoo/odoo#273102
Email sending now handles certain database concurrency issues more reliably by avoiding extra database lookups after a failed notification update. This helps preserve the original error information, making failures easier to diagnose without changing normal user workflows.
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#274089The point of sale now stops showing optional products after a cashier cancels the product configurator. This avoids suggesting add-ons for a product that was not added, reducing confusion during checkout.
Original PR description
When discarding the product configurator, we still showed the optional product. We no longer do that as no one wants to add optional products to a not-added product. task-6442422 Forward-Port-Of: odoo/odoo#282916
Odoo now chooses the partner whose full formatted email matches the sender, not just the shared email address. This prevents messages and invoice chatter from showing the wrong company as the 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#269509Scheduling an unassigned planning slot from the calendar no longer crashes when Studio scheduling is enabled. The fix restores the planning information needed during scheduling and prevents an additional calendar error when a date is missing, helping users plan work without interruptions.
Original PR description
Steps to reproduce: ------------------------- 1. Install `sale_planning` and `web_studio` with demo data. 2. Open the Planning calendar view. 3. Enable the "Scheduling" option from Studio and close…
Steps to reproduce:
-------------------------
1. Install `sale_planning` and `web_studio` with demo data.
2. Open the Planning calendar view.
3. Enable the "Scheduling" option from Studio and close it.
4. Drag an unscheduled slot onto the calendar.
Issues:
-----------
**Issue 1:**
```python
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 142, in write
self.assign_slot(vals)
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 159, in assign_slot
new_vals, tmp_sale_order_slots_to_plan, resource = slot._get_sale_order_slots_to_plan(vals, slot_vals_list_per_employee)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 228, in _get_sale_order_slots_to_plan
)._get_resource_work_info(vals, slot_vals_list_per_resource)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 366, in _get_resource_work_info
assert self.env.context.get('default_end_datetime')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
```
**Issue 2:**
```python
UncaughtPromiseError > TypeError
Uncaught Promise > Cannot read properties of undefined (reading 'endOf')
TypeError: Cannot read properties of undefined (reading 'endOf')
```
Cause:
----------
Since commit 1ce0dc8, the scheduling/unscheduling logic has been moved to the generic calendar implementation. However, the generic scheduling flow does not provide the `default_end_datetime` context required by sale_planning. As a result, sale_planning raises an `AssertionError` while scheduling a slot.
Additionally, when no date is available, attempting to call `endOf()` raises a `TypeError`.
Solution:
------------
Introduce a generic scheduling context hook in the calendar model and override it in Planning to provide the `default_end_datetime context when scheduling a slot.
This restores the context expected by` sale_planning`, prevents the `AssertionError`, and avoids calling `endOf()` on an undefined date to resolve `TypeError`.
**NOTE:**
This issue has already been resolved in the later versions (saas-19.4) as part of the scheduling/unscheduling refactoring. This commit backports the minimal changes required to fix the issue in this version.
References: f0f7b34 & https://github.com/odoo-dev/enterprise/commit/f82d073d17ce61a2ff39496364d3dced814ee90a
Related enterprise pr: https://github.com/odoo/enterprise/pull/127196
opw-6442889
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-prThis update keeps an image-related automated test working on Ubuntu Jammy by using a Pillow setting that is supported across older and newer versions. It helps maintain reliable test results 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#282223
This fix prevents an error when users reopen a note composer and press Escape or arrow keys in the “Continue with Full Composer?” popup. The popup now ignores those keys instead of crashing, keeping the chatter note-taking flow stable.
Original PR description
Reproduction steps: - Open a record that has a chatter where you can log notes - Start logging a note in the composer - Close the composer - Click log note again - See "Continue with Full Composer?" popup - Hit escape, up, or down - See traceback This shouldnt really do anything, so this fix makes it do nothing instead of crashing. opw-6476660 Forward-Port-Of: odoo/odoo#282803
This fix reduces unnecessary database work when opening Sign templates with limited user access. It batches related data loading instead of handling each template item separately, improving responsiveness without changing user-facing behavior.
Original PR description
Steps to reproduce: - with a user with "Sign / User: Own Templates" access rights - go to Sign / Templates - click on a template to open it => reading `sign.item.role.item_ids.template_id` triggers the computation of the related field `sign.item.template_id`, whose inverse `sign.template.sign_item_ids` carries a domain. When applying the domain, the sign.item's fields need to be fetched but they are fetched with one query per sign.item instead of a single batched one. task-6478942 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282994
This fixes an incorrect duplicate mention in a French VAT report identifier field that was reintroduced during a previous update. It helps ensure the generated VAT reporting file uses the expected wording and avoids potential filing confusion.
Original PR description
While forward-porting https://github.com/odoo/enterprise/commit/9b31a9cb65f1a37f953cf296cbc6cfa361cf7f62 ("[FIX] l10n_fr_reports: only attach a telereglement when VAT is due") to saas-19.1, I wrongly
rebased and resolved a conflict incorrectly. The resulting commit,
https://github.com/odoo/enterprise/commit/90059d0c39b1c385c057318112b146e4b7a77efc, reintroduced the express-mention-in-T-IDENTIF bug
previously fixed
opw-6275695The TikTok Shop order sync no longer tries to fetch orders from shops that have not completed authorization. This prevents scheduled sync failures and keeps authorized shops syncing normally while pending shops wait for setup to finish.
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#127148Bank journal pages now hide the "send now" action and connection request when the selected bank statement source is not online synchronization. This prevents users from seeing irrelevant banking options after changing how statements are imported.
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
Fixed an issue where the General Ledger could show an incorrect or missing currency for initial balances when multiple companies with different currencies shared the same chart of accounts. This prevents misleading balance amounts in multi-company accounting reports.
Original PR description
**Steps to reproduce:** * Install the **Accounting** module. * Create two companies with different currencies (e.g. **USD** and **CAD**) sharing the same **Chart of Accounts**. * Open a shared…
**Steps to reproduce:** * Install the **Accounting** module. * Create two companies with different currencies (e.g. **USD** and **CAD**) sharing the same **Chart of Accounts**. * Open a shared account and: * Add both companies in the **Company** field. * Under the **Mappings** tab, configure a mapping for each company. * In each company, create and post a journal entry on the same shared account (for example, a receivable account) using the company's own currency. * Set the journal entry dates to the **current month**. * Open **Accounting → Reporting → General Ledger**. * Change the reporting period to the **following month** so the posted entries are shown as the **Initial Balance**. * Open the report separately for each company. **Observed behavior:** * From the **CAD company**, the Initial Balance displays **USD 2,000** instead of the expected **USD 1,000**. * From the **USD company**, the **Currency** column on the Initial Balance is **blank**. **Cause:** * The SQL query for the `id_with_accumulated_balance` groupby used `SUM(amount_currency)` and `MIN(currency_id)` to aggregate all pre-period lines into a single Initial Balance row. * In a multi-company shared Chart of Accounts, lines from different companies (each with their own currency) were collapsed into the same group, causing `SUM(amount_currency)` to add amounts across currencies and `MIN(currency_id)` to return an arbitrary currency ID. * Additionally, the Python accumulation loop incorrectly performed **integer addition** on `currency_id` (a foreign key), further corrupting the displayed currency. **Fix:** * Replace `SUM(amount_currency)` and `MIN(currency_id)` with `CASE` expressions `MIN = MAX` is a uniformity check that works for **any number of currencies**: if every row in the group shares the same currency the condition is true and the correct sum is returned; if even one row differs the condition is false and both fields return `NULL`. The original three-column `GROUP BY (id, date, account_id)` is preserved. * The Initial Balance row now correctly shows a **blank** currency column, consistent with the Odoo 18 behavior, instead of an incorrect aggregated foreign currency amount. opw-6375310 Forward-Port-Of: odoo/enterprise#124823
Fixed an issue where opening spreadsheet version history could trigger an unnecessary retry behind the scenes. This makes access to version history more direct and reliable for users, without changing spreadsheet features or data.
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
Fixes an error that could block reconciliation of internal bank transfers between a branch and its parent company when the branch transaction used a reconciliation model. This helps accounting teams complete legitimate internal transfers 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
Fixed an issue that prevented PDF generation for Colombian vendor bills after the electronic invoicing acceptance flow. The system now correctly reads wrapped invoice attachments, avoiding server errors and allowing users to print invoice PDFs as expected.
Original PR description
**Steps to reproduce:** * Install the **l10n_co_dian** module. * Go to **Settings** and, under **Colombian Electronic Invoicing**: * Disable **Testing Mode**. * Enable **DIAN Demo**. * Create a…
**Steps to reproduce:**
* Install the **l10n_co_dian** module.
* Go to **Settings** and, under **Colombian Electronic Invoicing**:
* Disable **Testing Mode**.
* Enable **DIAN Demo**.
* Create a vendor bill with a tax and confirm it.
* Click **Acknowledge Reception**.
* Click **Receive Goods**.
* Click **Accept**.
* From the gear menu, click **Print → Invoice PDF**.
**Observed behavior:**
* A server error is raised:
```
lxml.etree.XMLSyntaxError: Start tag expected, '<' not found, line 1, column 1
```
* The PDF cannot be generated.
**Cause (two-step):**
1. **ZIP not unwrapped:** The original code called `etree.fromstring(self.l10n_co_dian_attachment_id.raw)` directly for all move types. For vendor bills (`in_invoice`) the attachment is stored as a ZIP file, so `raw` is compressed binary data — not XML. Passing it to `etree.fromstring` directly produces the `XMLSyntaxError` above.
2. **AttachedDocument wrapper not unwrapped:** Once the ZIP is correctly decompressed with `xml_utils._unzip`, the resulting XML is an `AttachedDocument` wrapper, not a plain `Invoice`. The actual invoice XML is embedded as CDATA inside `cac:Attachment/cac:ExternalReference/cbc:Description`. `_get_qr_code_value` expects the inner document and searches for nodes like `cac:AccountingSupplierParty`, `cac:LegalMonetaryTotal`, and `sts:QRCode` — none of which exist on the outer wrapper, so the QR code was blank or the method crashed.
**Fix:**
* In `_l10n_co_dian_get_invoice_report_qr_code_value`, for vendor bills (`in_invoice`/`in_refund` without support document), unzip the attachment and immediately attempt to extract the inner invoice XML from `cbc:Description` using `findtext('.//{*}Description')` (lxml namespace wildcard). If the node is present, parse its text as the actual document; otherwise fall back to the unzipped bytes directly.
**Note:**
* A unit test for the `AttachedDocument` unwrapping path was not added because the test would require a zipped fixture file (the vendor bill attachment is stored as a ZIP) which is not appropriate to commit.
* A regression test was added in `test_accept_by_customer`: after the full commercial event flow the method is called inside a `try/except etree.XMLSyntaxError` block so that any XML parse failure surfaces as a proper test *failure* rather than an unhandled test *error*.
opw-6417422
Forward-Port-Of: odoo/enterprise#126463When warehouse staff scan an unreserved serial number during a delivery, Odoo now uses the serial item’s real storage location instead of defaulting to the parent stock location. This prevents inventory from being deducted from the wrong place and avoids incorrect negative or stale stock records.
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#121375
When choosing appointment-related placeholder fields, the system now defaults to the readable field name instead of a technical ID when available. This makes generated content easier to understand while still allowing users to select the ID if needed.
Original PR description
Before this commit: when clicking a field having sub fields (canFollowRelationFor is true), we just return this field's id, which is not very useful in most cases. After this commit: We created subclass of DynamicPlaceholderPopover, EditorDynamicPlaceholderPopover, which uses EditorModelFieldSelectorPopover. We use the display name of the followable field by default and if the user really want the id, they may choose the id subfield. We also show the followable field's name as the default placeholder instead of "Display name". task-6265223 Forward-Port-Of: odoo/enterprise#121785
This change updates automated checks for the subscription sales area so they match related platform changes. It helps keep future subscription updates reliable without changing the customer-facing subscription experience.
Original PR description
See also: - https://github.com/odoo/odoo/pull/280403 Forward-Port-Of: odoo/enterprise#127041
Social users can now like Facebook and Twitter stream posts without running into an access error. This keeps social engagement actions working smoothly for users who do not have broader editing rights on the post.
Original PR description
Bug === When a social user like a stream post, an access error is raised because he has no write access on it. Task-6425391 Forward-Port-Of: odoo/enterprise#125973
A website rental planning purchase test has been temporarily disabled because the related sales rental flow is still changing quickly. This avoids unstable automated test results while the final process is being defined, with no direct change to customer-facing features.
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#128170Auto-planning now correctly schedules work through the last day of a selected month. This prevents valid working days from being missed, helping sales and planning teams allocate the full ordered workload as expected.
Original PR description
Steps to reproduce: --------------------------- 1. Install `sale_planning` with demo data. 2. Create a SO with a planning product, set the quantity to 100 hours, and confirm the SO. 3. Click the "To…
Steps to reproduce: --------------------------- 1. Install `sale_planning` with demo data. 2. Create a SO with a planning product, set the quantity to 100 hours, and confirm the SO. 3. Click the "To Plan" button, then click "Auto Plan". 4. Make sure the "Month" filter is selected in the scale options and observe the planned slots. Issue: -------- When auto planning slots for a month, the last day of the month is excluded. For example, slots are scheduled only until July 30th, even though July 31st is a working day. Cause: -------- While preparing the context, `stopDate` is set to July 31st at 00:00. It is then passed to [serializeDateTime()](https://github.com/odoo/odoo/blob/dacaad91bba8f959daf5d89a046c5a1c11e48eec/addons/web/static/src/core/l10n/dates.js#L553-L560), which converts the datetime to UTC. Depending on the user's timezone, this can shift the date to the previous day, causing the last day of the month to be excluded. Solution: ------------ Use `localEndOf()` to set `stopDate` to the local end of the selected range before passing it to `serializeDateTime()`. This ensures the last day of the month is preserved during UTC conversion. **NOTE:** Forward-port the solution from the 18.0 version, which was adapted to the publish shift use case in 18.3 and introduced this issue. Add a HOOT test case to prevent this regression in future versions. References: [18](https://github.com/odoo/enterprise/commit/bc3db24f83473d5646f7c2cfca8ed1c5b064ea2e) and [saas-18.3](https://github.com/odoo/enterprise/commit/c81fba31780869940f726b695ad46a87f69798fb) opw-6391495 Forward-Port-Of: odoo/enterprise#127950
Australian payroll batches now handle employees without leave allocations correctly. This prevents payslip generation from failing when a batch includes a mix of employees with and without unused leave balances.
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#127623German SEPA credit transfer files now exclude company LEI information when using an older XML format that does not allow it. This keeps exported payment files compliant with bank requirements and avoids rejected 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
On mobile screens, AI chat windows now open in front of existing chats instead of being hidden behind them. This makes the AI assistant easier to access when using fullscreen message composition in Odoo.
Original PR description
AI chats opened on mobile views could appear behind other chats. This was inconsistent with the expected stacking behavior, where newly opened chats should appear on top of existing ones. To reproduce: * Open the chatter of any module. * Open the message composer in fullscreen mode. * Click the AI button. This commit increases the z-index of AI chats on mobile views so they are displayed on top of other chats. task-6412411 Forward-Port-Of: odoo/enterprise#128346
This fixes a test issue in the Peru electronic invoicing module caused by small wording differences between software library versions. It helps keep automated validation reliable across different server environments without changing customer-facing invoicing 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
Fixed an issue in the Timesheet Assistant where unselecting one suggestion could leave its project or task selected when choosing another suggestion. This helps users create timesheets with the intended project details and avoids accidental entries on the wrong project.
Original PR description
Steps to reproduce: ------------ - install timesheet_grid. - activate assistant. - select a suggestion and then unselect it. - select a different project suggestion. Issue: ----------- the project from the previous suggestion remains selected. cause: --------- currentRecord is not reset when a suggestion is unselected, so the previous suggestion's project and task are still reused. Fix: --------- reset currentRecord to null when the suggestion is unselected and showCreateForm is false. Effected pr-https://github.com/odoo/enterprise/pull/126450 task-6482312
Fixed an issue where dragging an unscheduled planning slot onto the calendar could fail when Studio scheduling was enabled. This keeps the planning calendar usable and prevents users from encountering blocking error messages during scheduling.
Original PR description
Steps to reproduce: ------------------------- 1. Install `sale_planning` and `web_studio` with demo data. 2. Open the Planning calendar view. 3. Enable the "Scheduling" option from Studio and close…
Steps to reproduce:
-------------------------
1. Install `sale_planning` and `web_studio` with demo data.
2. Open the Planning calendar view.
3. Enable the "Scheduling" option from Studio and close it.
4. Drag an unscheduled slot onto the calendar.
Issues:
-----------
**Issue 1:**
```python
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 142, in write
self.assign_slot(vals)
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 159, in assign_slot
new_vals, tmp_sale_order_slots_to_plan, resource = slot._get_sale_order_slots_to_plan(vals, slot_vals_list_per_employee)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 228, in _get_sale_order_slots_to_plan
)._get_resource_work_info(vals, slot_vals_list_per_resource)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/odoo/enterprise/sale_planning/models/planning_slot.py", line 366, in _get_resource_work_info
assert self.env.context.get('default_end_datetime')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
```
**Issue 2:**
```python
UncaughtPromiseError > TypeError
Uncaught Promise > Cannot read properties of undefined (reading 'endOf')
TypeError: Cannot read properties of undefined (reading 'endOf')
```
Cause:
----------
Since commit [1ce0dc8,](https://github.com/odoo/odoo/commit/1ce0dc86f8ae52b21a5a889aacd1efce0e27c722) the scheduling/unscheduling logic has been moved to the generic calendar implementation. However, the generic scheduling flow does not provide the `default_end_datetime` context required by sale_planning. As a result, sale_planning raises an `AssertionError` while scheduling a slot.
Additionally, when no date is available, attempting to call `endOf()` raises a `TypeError`.
Solution:
------------
Introduce a generic scheduling context hook in the calendar model and override it in Planning to provide the `default_end_datetime context when scheduling a slot.
This restores the context expected by` sale_planning`, prevents the `AssertionError`, and avoids calling `endOf()` on an undefined date to resolve `TypeError`.
**Note:**
This issue has already been resolved in the later versions (saas-19.4) as part of the scheduling/unscheduling refactoring. This commit backports the minimal changes required to fix the issue in this version.
References: f82d073 & https://github.com/odoo-dev/odoo/commit/f0f7b342895734d2151c0c106940006e37a5fd86
Related community pr: https://github.com/odoo/odoo/pull/281178
opw-6442889