Wednesday, September 23, 2026
47 changes · saas-19.4
Enhancements to existing features
This change improves the speed and reliability of accounting-related test setup by helping the system make better database decisions after loading test account data. It reduces long delays in localization test runs, helping development and validation pipelines complete much faster without changing normal user behavior.
Original PR description
The default order of account.account joins an aggregate over the ancestor paths of every account. A company created in a test only exists in the test transaction, so the planner assumes it has a…
The default order of account.account joins an aggregate over the ancestor paths of every account. A company created in a test only exists in the test transaction, so the planner assumes it has a single account and reruns that aggregate for each of its accounts, including the dead rows left by earlier test classes. On runbot this turned 5s L10n class setups into minutes and got buckets killed. Analyzing the account tables after the chart load gives the planner the real counts, as test_balance_sheet_balanced already does. Order changes alone are not enough, and rewriting the query slows down production searches. The journal default account lookup only needs a code length, so it now uses the id order. | Benchmark | without | with | |--------------------------------------------|----------|----------| | Peru TestSequence, 25k dead rows (local) | 120-132s | 4.3-7.2s | | L10n build, slow account path queries | 94 | 0 | | L10n build, TestPosAR setup | 145s | 15s | | L10n build, TestCIIFR setup | 109s | 8s | Forward-Port-Of: odoo/odoo#289782
The Vietnamese chart of accounts now includes dedicated accounts for revenue and costs from selling or liquidating investment property. This supports compliance with Circular 99/2025/TT-BTC and helps report related gains or losses separately in Profit & Loss statements.
Original PR description
Circular 99/2025/TT-BTC adds a dedicated Profit & Loss line for gains/losses on the sale and liquidation of investment property, computed from dedicated sub-accounts rather than the main revenue and cost-of-goods-sold accounts. Add the two accounts to the chart of accounts: - 5117 Revenue from sale and liquidation of investment property - 6327 Cost of sale and liquidation of investment property Task-6518304 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#290129 Forward-Port-Of: odoo/odoo#287572
Payroll dashboard loading and batch payslip generation have been optimized, especially for runs with many payslips and varied configurations. This should reduce waiting time for payroll teams when preparing and reviewing payroll runs.
Original PR description
Loading payslip run dashboard Before <img width="1915" height="740" alt="image" src="https://github.com/user-attachments/assets/738f75c0-1b49-4869-8375-aba6fe447588" /> After <img width="1906" height="737" alt="image" src="https://github.com/user-attachments/assets/57584776-362f-428f-b930-12581f1d12a1" /> Generating 200 payslips with various configurations Forward-Port-Of: odoo/enterprise#130000
Bank statement auto-reconciliation now matches payment references with invoice names even when uppercase and lowercase letters differ. This reduces missed automatic matches and saves accounting teams manual reconciliation work.
Original PR description
Currently, when a payment reference isn't in the same letter case as the invoice name, it wouldn't automatically reconcile. task-6562440 Forward-Port-Of: odoo/enterprise#131418
Large reports that reuse the same barcode or QR code now avoid regenerating the same image over and over. This can dramatically reduce report generation time for long documents such as labels, invoices, or traceability reports with repeated codes.
Original PR description
### Description of the issue/feature this PR addresses: Reports with hundreds of pages and repeated barcodes or QR codes (same document number, same lot code on every page) re-run reportlab's…
### Description of the issue/feature this PR addresses: Reports with hundreds of pages and repeated barcodes or QR codes (same document number, same lot code on every page) re-run reportlab's createBarcodeDrawing and PNG encoding on every occurrence, even when type, value and options are identical. ### Current behavior before PR: Every call to ir.actions.report.barcode() re-renders the barcode/QR from scratch, regardless of whether an identical (type, value, options) combination was already rendered earlier in the same report. On a 900-page report with 4 barcodes/page, this dominates generation time. Benchmark, 900 pages x 4 barcodes/page, 4 distinct combos, 3600 calls: 19.131s. ### Desired behavior after PR is merged: The pure rendering step (createBarcodeDrawing + mask + PNG encoding) is extracted to a module-level function keyed on (barcode_type, value, options, mask) and wrapped with functools.lru_cache. Repeated barcodes reuse the cached PNG instead of re-rendering. Same benchmark after the fix: 0.020s (946.7x speedup, cache_info hits=3596 misses=4). --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#289742 Forward-Port-Of: odoo/odoo#289135
Resolved issues and error corrections
This fixes an issue where employees could receive repeated chatter messages when they were automatically enrolled in an eLearning course more than once. The enrollment notification now behaves consistently and avoids cluttering employee records with duplicate messages.
Original PR description
Steps to reproduce: - connect with a user with an employee record - recreate a new eLearning course - enable debug mode - add "Role / User" to "Auto Enroll Groups" fields => message is duplicated in the employee's chatter `_action_add_members` in `website_slides` is designed to be idempotent (calling it twice is a no-op if the partner has already joined) but the override in `hr_skills_slides` is not. We have many many duplicated messages on odoo.com (see task) task-6508615 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#290032 Forward-Port-Of: odoo/odoo#284483
The website setup flow now keeps upcoming fields unavailable until users reach the right step. This prevents hidden options from reacting to mouse or keyboard input and improves accessibility for people using screen readers or keyboard navigation.
Original PR description
Steps to reproduce: - Start the website configurator and open the description step. - Before choosing a website type, move the pointer over the area where the industry field will appear. - Navigate with Tab before completing the industry selection. => Hidden fields still react to input and can receive keyboard focus. Before this commit, the industry input and positioning dropdown were transparent until the previous step was complete. They could still react to pointer input and be announced by screen readers. After this commit, these fields stay hidden and unavailable until their respective steps are reached, while keeping their layout space.
Point of Sale now correctly loads product option values when a product is updated using an existing attribute after the browser has cached session data. This prevents errors when opening products with variants and avoids forcing users to clear cached data or recreate sessions.
Original PR description
Steps to reproduce: - Open a PoS session so the browser caches the data (IndexedDB) - Add an attribute line on a product available in PoS, using an attribute that already existed and was not modified…
Steps to reproduce: - Open a PoS session so the browser caches the data (IndexedDB) - Add an attribute line on a product available in PoS, using an attribute that already existed and was not modified since - Reopen the PoS and click the product Issue: TypeError: undefined is not an object (evaluating 'values[0].is_custom') in openConfigurator. The attribute line is loaded but none of its values are; the variants have no attribute values on the frontend. Closing the session or reloading the browser does not help since the cache is kept. Cause: On an incremental load (pos_last_server_date in context), every model only returns the records written after the last sync. The domain of product.template.attribute.value restricts attribute_id to the ids in data['product.attribute'], which in that case only holds the attributes modified since the last sync. Values created on an older attribute are therefore excluded, and the frontend drops the ids it cannot resolve. Fix: Filter attribute_id with the domain of product.attribute itself instead of the ids of the records returned in the current load. A full load returns the same records as before. opw-6568808 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#288065
Users should no longer receive both a browser alert and an in-app alert for the same mention when Odoo is open but not active. This reduces notification noise and helps users trust that each alert represents a separate event.
Original PR description
Steps to reproduce: - Grant browser notification permission (subscribes to web push). - Set user A's `notification_type` to `inbox` in preferences and reload. - Keep A's tab open but unfocused. - As user B, post an @-mention on a non-channel chatter record targeting A. A receives two alerts: the JS one from the `mail.message/inbox` bus event and the native notification from web push. `OutOfFocusService.notify()` gated JS skip on `message.thread.model`, but the server never sends `mail.thread`, so chatter fell through. Replace it with a `message_type` + `!isSelfAuthored` filter mirroring the server side `_notify_get_recipients_for_extra_notifications`. Also swap the `isInbox` SW handshake workaround added by [1] when the user is busy. [1]: https://github.com/odoo/odoo/pull/232431 Forward-Port-Of: odoo/odoo#290052 Forward-Port-Of: odoo/odoo#282981
Removing or reducing column layouts now drops columns that contain no real content, preventing extra blank paragraphs from appearing. Content-filled columns are preserved, and the editor still keeps one empty paragraph when needed so users can continue editing.
Original PR description
#### Description of the issue this PR addresses: - When reducing the number of columns or removing a column layout, empty columns were previously unwrapped like any other column. - As a result, columns containing only placeholder paragraphs contributed empty paragraphs to the resulting content, even though they did not contain any meaningful user content. #### Desired behavior after PR is merged: - Fully empty columns are discarded when they are removed. - Non-empty columns continue to be merged as-is, preserving their content. - A single empty paragraph is still kept when all columns are empty to ensure the editor remains editable. - Rename `Remove columns` to `Remove column layout` and update its description to `Convert columns to regular content` to better reflect the operation. task-6296536 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286125 Forward-Port-Of: odoo/odoo#270220
Products with multiple variants are no longer shown as sold out just because the first variant has no stock. The shop page now keeps Add to Cart available when at least one variant can still be purchased, preventing lost sales and customer confusion.
Original PR description
Steps to reproduce: =================== 1. Create a storable product with two variants and untick "Continue selling when out-of-stock". 2. Leave the first variant at 0 in stock, put some stock on the…
Steps to reproduce: =================== 1. Create a storable product with two variants and untick "Continue selling when out-of-stock". 2. Leave the first variant at 0 in stock, put some stock on the second one. 3. try the "Add to Cart" option of the shop page. => The product has no add to cart button, as if it were sold out, while its second variant can be bought. Reordering the variants so that the one in stock comes first brings the button back. Root cause: =========== `product.template._is_sold_out()` delegates to `product_variant_id`, which is `product_variant_ids[:1]`, so a whole template is declared sold out on the sole basis of its first variant. `_website_show_quick_add()` then hides the button for the template, whatever the stock of the other variants. The helper has looked at that single variant since it was written: - [1] added it to hide the button of sold out products. Fix: ==== Consider the template sold out only when all of its variants are. The check stops at the first variant in stock, so a product that can be bought still costs a single stock lookup. [1]: https://github.com/odoo/odoo/commit/4ae197cc770cec2058e1f113209c4f93a4a0f4b2 opw-6520052 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#289022 Forward-Port-Of: odoo/odoo#286781
Combo products can no longer be added to the cart unless every required choice has been selected. This prevents customers from checking out with incomplete combo orders, improving order accuracy and avoiding fulfillment issues.
Original PR description
Steps to reproduce: =================== 1. Publish a combo product whose choices each offer more than one item, so the combo configurator is shown. 2. On the shop, open the product and click "Add to…
Steps to reproduce: =================== 1. Publish a combo product whose choices each offer more than one item, so the combo configurator is shown. 2. On the shop, open the product and click "Add to Cart". 3. Select an item for one combo choice only. The dialog's "Add to cart" button stays disabled. 4. Remove the `disabled` attribute from that button with the browser developer tools and click it. => The combo is added to the cart with one of its choices unanswered, and the order can be paid in that state. Root cause: =========== The incomplete selection is only prevented on the client side, by disabling the button until every choice has been answered. Server side, `/website_sale/combo_configurator/update_cart` only rejects a completely empty selection, never that the selected items cover every choice. Fix: ==== Reject the request unless the selected combo items cover exactly the combo choices of the product. Comparing the set of choices rather than counting the items also rejects a selection answering the same choice twice while leaving another one unanswered. opw-6478837 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#289005 Forward-Port-Of: odoo/odoo#283465
Website cards configured as clickable links remain clickable after a background image is added. This prevents broken navigation on published pages and helps editors safely combine visual card designs with link behavior.
Original PR description
**Problem** Clickable cards do not work if they have an image as background. **How to reproduce** Drop a `s_cards_grid` snippet, select a card, enable "Make it clickable", set a URL, then set a background image on the card. Save. The card is no longer clickable. **Why does it happen** When a background image is set, the card gets the `oe_img_bg` class. This class, through `%o-we-background-layer-parent`, sets `position: relative` on every one of its direct children. This is done to keep the background image behind the snippet's own content, but it interferes with the `a.stretched-link` element, making the card not clickable. **Fix** Exclude `.stretched-link` from the children targeted by `%o-we-background-layer-parent`. task-6462339
Delivery slip quantities are now rounded correctly when a receipt or delivery is split across multiple stock move lines. This prevents confusing decimal artifacts from appearing on printed documents, improving clarity for warehouse teams and customers.
Original PR description
**Issue** When a move has several move lines, the printed quantities are not rounded, causing floating-point arithmetic artifacts to appear on the report. **Steps to reproduce** - Create and confirm…
**Issue** When a move has several move lines, the printed quantities are not rounded, causing floating-point arithmetic artifacts to appear on the report. **Steps to reproduce** - Create and confirm a PO for 15.6 units of a product. - On the receipt, change the quantity to 17.8 (this splits it into two stock move lines). - Validate the receipt and print the delivery slip. -> The ordered quantity on the generated PDF is not rounded (floating-point arithmetic issue). **Cause** While printing the delivery slip, quantities are aggregated by product: https://github.com/odoo/odoo/blob/189df3538aa07d22f683008522b9143421f411a2/addons/stock/report/report_deliveryslip.xml#L174 No rounding is performed while subtracting quantities from the ordered quantity (resulting in 13.399..): https://github.com/odoo/odoo/blob/189df3538aa07d22f683008522b9143421f411a2/addons/stock/models/stock_move_line.py#L927 Nor while adding the second move line's quantity back to the ordered quantity (resulting in 15.599..): https://github.com/odoo/odoo/blob/189df3538aa07d22f683008522b9143421f411a2/addons/stock/models/stock_move_line.py#L938 **Additional note** Same issues arise for `packaging_qty_ordered`, `quantity` and `packaging_quantity`. opw-6469422 Forward-Port-Of: odoo/odoo#286846 Forward-Port-Of: odoo/odoo#283792
Duplicating a tax now keeps its domestic status correctly instead of silently marking it as non-domestic. This helps prevent incorrect tax behavior and fiscal position handling when businesses copy existing tax records.
Original PR description
_compute_is_domestic checks whether the company's domestic_fiscal_position_id is part of the tax's fiscal_position_ids. Because this field is both stored and precompute=True, duplicating a tax recomputes it on a virtual new() record before insert. On that virtual record, fiscal_position_ids is resolved through NewId pseudo-ids while company_id (a plain Many2one) keeps its real id, so the containment check always failed, silently turning is_domestic into False on any duplicate. Use fiscal_position_ids._origin to compare against the real underlying records instead. opw-6575113 Forward-Port-Of: odoo/odoo#289896 Forward-Port-Of: odoo/odoo#289246
Unposted accounting entries dated in a locked period are now deleted instead of being reversed. This prevents cancellation flows, such as payroll cancellations, from creating accounting amounts that were never actually posted.
Original PR description
_unlink_or_reverse relies on _can_be_unlinked, which only looks at the hash and the lock dates. A draft entry dated in a locked period thus lands in the reversal branch: it is copied, the copy is posted after the lock date and the draft stays behind. The reversal books amounts that were never posted in the first place. Lock dates protect what has been booked. An entry that was never posted can be deleted whatever its date. Seen with hr_payroll_account when a payslip is cancelled after the accountant locked the period without posting the salary entry. Any caller of _unlink_or_reverse (assets, loans, deferred entries) behaves the same. Forward-Port-Of: odoo/odoo#289980
Shipment cancellations now send each delivery carrier only the shipment information that belongs to it. This prevents duplicate cancellation attempts and avoids incorrect or failed cancellations when multiple carriers are involved.
Original PR description
When cancelling shipments for multiple pickings with different delivery carriers, passing the entire recordset (self) instead of the individual picking to the carrier's cancel_shipment method causes each carrier to receive tracking references that do not belong to it. This results in API errors or silent wrong cancellations at the carrier level, and each shipment being cancelled N times instead of once, where N is the total number of pickings being processed. Forward-Port-Of: odoo/odoo#289809
Fixes an issue where changing the gradient angle in the website editor color picker could appear to save but revert afterward. Users can now type a new angle and have it applied immediately, making gradient styling more reliable when editing website building blocks.
Original PR description
Problem: When changing the gradient angle input in the color picker and saving the record, the updated angle is not saved and reverts to the previous value. Cause: `setOnCloseCallback` runs before `onAngleChange` because the angle input fires the `change` event on blur/close. As a result, `onColorGradientChange` is called with the previous angle value, and `onColorGradientPreview` runs afterwards with the new value, causing the applied preview to be reverted later. Solution: Call `onAngleChange` on the `input` event instead of `change` so that state and preview update immediately each time the user types. Steps to reproduce: - Add a gradient to a building block. - Define a value of 180 degrees. - Save - If you open the color picker the value is still 135. task-6522533 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286778 Forward-Port-Of: odoo/odoo#285967
Fixes an issue where creating a new website with eCommerce and design themes could fail at the final setup step due to missing theme content. The setup now checks all website themes for the needed snippets, making multi-website and eCommerce configuration more reliable.
Original PR description
# How to reproduce - Start a new db with the design-themes addons - Install the eCommerce module - Skip the first website configurator - Go to Website > Configuration > Settings > New website - Go…
# How to reproduce - Start a new db with the design-themes addons - Install the eCommerce module - Skip the first website configurator - Go to Website > Configuration > Settings > New website - Go through all the steps as usual # The issue At the last step, you will get, depending on the version either : - A traceback, pointing out the fact that a template is missing - A popup signaling that another module operation is being processed, even though there are none The configurator won't go through in both cases # Cause When calling `configurator_apply`, we will try to get the content of every snippet specific to theme installed with the website : https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/website.py#L910 But, when trying to find the 'website_sale.configurator_homepage_s_dynamic_snippet_category_list' view, we find nothing and an error is thrown. This view should be created by the `_generate_primary_snippet_templates` function, which is called when loading any theme module or `website` or `website_sale`: https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/ir_module_module.py#L594 The issue is that, to know which snippets to install, that function relies on `get_current_website()`: https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/ir_module_module.py#L694 Which relies on multiple things to find the current website, notably the current request's session : https://github.com/odoo/odoo/blob/ef6765208d400aa1252cd4dff5bc074ea551bbc2/addons/website/models/website.py#L1374-L1381 Since [this commit], the request is not kept when creating a new registry, which is done when loading a module. `get_current_website()` fallbacks on the first website's theme, which is the default theme (since we skipped the first configurator). That theme does not have the `website_sale` snippet in its manifest, contrary to others. # Proposed solution Since a new registry is created, we cannot use the context, nor the current request. We also cannot add a parameter to the `_generate_primary_snippet_templates` function since we are in stable (It would not help much anyway because this function is called when loading a module, so giving the current website is not possible). This means that we have to stop relying on `get_current_website()` to differentiate between the two use case : - Installing a theme module - Installing a module with specific snippets We can determine if the module being installed is a theme by looking at its category. If it is not, we assume its the other use case as there should not be any other. In that case, instead of installing the snippets of the theme of the current website, we check the theme of all websites. Later in the code, we take only distincts snippets and check that a corresponding view do not already exist, so there should be no risk of duplicate : https://github.com/odoo/odoo/blob/1f2ea7be8377c906ded75da893d739109db93c4d/addons/website/models/ir_module_module.py#L692 https://github.com/odoo/odoo/blob/1f2ea7be8377c906ded75da893d739109db93c4d/addons/website/models/ir_module_module.py#L712 https://github.com/odoo/odoo/blob/1f2ea7be8377c906ded75da893d739109db93c4d/addons/website/models/ir_module_module.py#L608 Furthermore, I believe it makes more sense to check every website anyway. If the `website_sale` module is installed, and there already exists multiple website with different themes, these themes might require different `website_sale` specific snippets. opw-6517556 [this commit]: https://github.com/odoo/odoo/commit/be8c85b4ccb10d6f16ddea3ab493e4c9aac749e1 Forward-Port-Of: odoo/odoo#285935
The website project form processing has been cleaned up and reorganized to make it more reliable and easier to update in the future. This helps reduce the risk of errors when visitors submit project-related forms through the website.
Original PR description
Clean up and move some of the form processing logic for future updates opw-6560036 Forward-Port-Of: odoo/odoo#287697
Chatter filters now correctly include field tracking updates in the Tracked Changes view and keep them out of regular conversations. This helps users find audit-related updates in the right place and reduces confusion when reviewing record history.
Original PR description
Previously, the chatter filters only considered notification messages. Since field tracking messages are now stored as tracking messages, they no longer appeared in the 'Tracked Changes' filter and were incorrectly included in the 'Conversations' filter. This PR updates the chatter filters to correctly handle tracking messages, ensuring that each filter displays the appropriate messages. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280981
Project task totals now exclude subtasks that belong to task templates, aligning the counts with what users see as regular tasks in the interface. This prevents template-related items from inflating open, closed, and total task counts, making project reporting clearer and more reliable.
Original PR description
Before this commit, the task_count and closed_task_count takes into account the subtask inside task template, however, those subtasks are not considered as a basic task in the UI, there are also considered as a template.
This commit adds `('has_template_ancestor', '=', False)` condition to make sure those subtasks are no longer taken into account. The condition inside the compute of `open_task_count` has also been reviewed to have the same conditions in the 3 fields.
task-6483156
Forward-Port-Of: odoo/odoo#286910This fixes an issue that could prevent users from signing in to Odoo through the Gmail plugin. The plugin now uses the current configuration method, avoiding an error during authentication and restoring a smoother login experience.
Original PR description
In version 19.1+, the ir.config_parameter model no longer has the function get_param. Instead you use get_int, get_str, etc. This was causing a traceback in auth_access_token when a user tries to authenticate with Odoo via the Odoo gmail plugin. Forward-Port-Of: odoo/odoo#289957
Live Chat now checks URL matching rules when they are created or updated, so invalid patterns are rejected before they can break the website experience. This helps administrators catch configuration mistakes early and prevents visitors from encountering chat initialization errors.
Original PR description
Currently, an error occurs when a user opens the website and Live Chat is initialized with an invalid URL regex. Steps to Reproduce: - Install `website_livechat` without demo data. - Open `Live Chat`…
Currently, an error occurs when a user opens the website and Live Chat is initialized with an invalid URL regex. Steps to Reproduce: - Install `website_livechat` without demo data. - Open `Live Chat` and, in the `YourWebsite.com` channel, click the `three-dot` menu and select `Configure Channel`. - Go to the `Rules` tab. - Add a rule, select `Welcome Bot` in `Chatbot`, and set an invalid regex such as `*` in `URL Regex` and save. - Click `Join Channel`. - Open the `website`. `re.PatternError: nothing to repeat at position 0 when serializing dict item 'result' ` - when `URL Regex is '['` `re.error: unterminated character set at position 0 when serializing dict item 'result'` When a user opens the website, Live Chat is initialized by checking operator availability and finding the matching country/URL rule [1]. The rule matching checks whether `regex_url` matches the current page URL. It uses `re.search()` [2], which compiles the regex pattern before searching. If `regex_url` contains an invalid pattern such as `*`, `re.search()` raises `nothing to repeat at position 0` because `*` must follow a preceding regex element. Similarly, `[` starts a character set (`[...]`) but has no closing `]`, causing `unterminated character set at position 0`. This commit adds a constraint to validate `regex_url` and reject invalid regex patterns when creating or updating Live Chat rules. [1]- https://github.com/odoo/odoo/blob/f962f1eccecfdbc95375f795174c8f687459b521/addons/im_livechat/controllers/main.py#L87 [2]- https://github.com/odoo/odoo/blob/f962f1eccecfdbc95375f795174c8f687459b521/addons/im_livechat/models/im_livechat_channel.py#L387 sentry-7679767396 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283545
This fixes an error that could occur when opening image attachments linked to products, improving reliability in the Odoo web interface. It also improves internal test handling for attachments that reference other attachments, helping prevent similar issues in future releases.
Original PR description
### Steps to Reproduce: 1. Go to Sales > Products and select a product 2. Upload an image for the product 3. Go to Settings > Technical > Attachments 4. Click on one of the image attachments 5.…
### Steps to Reproduce: 1. Go to Sales > Products and select a product 2. Upload an image for the product 3. Go to Settings > Technical > Attachments 4. Click on one of the image attachments 5. Observe the Client Error: "Uncaught Promise > Invalid props for component 'Many2One': 'domain' is not a function" ### Issue: In `attachment_patch.js`, the domain for the `Many2OneReferenceField` is overridden when an attachment is linked to another attachment. The patch calculates the domain and incorrectly assigns a static Array (using `.toList()`) to `props.domain`. In Odoo 19, the `Many2One` component enforces strict prop validation (caught when Owl's debug mode is enabled during testing), causing a crash because it expects the domain prop to be a function. ### Solution: We can just wrap the resulting domain Array in an arrow function so that it evaluates dynamically when the `Many2One` component mounts. Also added a unit test to explicitly cover this patched scenario. The test manually applies a local patch to `Many2OneReferenceField.prototype` to accurately replicate the environment and verify the fix. ### For saas-19.4+: ### Issue: In the MockServer, when a `many2one_reference` field points to its own model (for example, an `ir.attachment` linked to another `ir.attachment`), the record is incorrectly erased and replaced by only its ID. This causes testing environments to lose record data when evaluating self-referencing relations, preventing accurate unit testing. ### Solution: We can update the mock model mapping to properly retain the full record data when the comodel is the same as the main model. opw-6563954 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287951
Fixed an issue where partially processing a subcontracted purchase receipt in the Barcode app could block validation with an error. Businesses can now complete these partial receipts and backorders reliably, avoiding delays in receiving subcontracted products.
Original PR description
eproduction steps: - MRP subcontracting + purchase - Subcontracted product with BOM - Create PO with a qty > 1 for this product - Go into barcode - Scan > 0 and < all of the expected quantity - Back…
eproduction steps: - MRP subcontracting + purchase - Subcontracted product with BOM - Create PO with a qty > 1 for this product - Go into barcode - Scan > 0 and < all of the expected quantity - Back out of barcode - Click validate -> Error **OR** Go back into barcode, attempt to validate -> Error Added the StockMove and StockPicking classes to stock_barcode_mrp_subcontracting to extend functions `_subcontracted_produce`, `_clean_merged`, and `split_uncompleted_moves` to use a new context flag, `keep_subcontract_production`. When this flag is set, the move Barcode creates from the split keeps the MO of the move it was split from rather than splitting the MO in two, gives that link up before it is merged away so its cancellation does not cancel the MO, and the MO quantity is resynced with the merged move afterwards. Error thrown here: https://github.com/odoo/odoo/blob/f84eeb3ed1421e0cc07c906ff6444734dc01d35f/addons/mrp_subcontracting/models/stock_move.py#L248 opw-6428928 Forward-Port-Of: odoo/enterprise#131459
Colombian electronic invoices sent to DIAN no longer include the journal's technical control key in the note field. This keeps the XML note content limited to the invoice Terms and Conditions, avoiding unintended internal information in official documents.
Original PR description
**Steps to reproduce:** - Install the `l10n_co_dian` module and switch to the CO Company. - Disable `Test environment` and enable `DIAN Demo Mode` in the invoicing settings. - Set a `Technical…
**Steps to reproduce:** - Install the `l10n_co_dian` module and switch to the CO Company. - Disable `Test environment` and enable `DIAN Demo Mode` in the invoicing settings. - Set a `Technical control key` on the `Customer Invoices` journal. - Create and confirm an invoice with Terms and Conditions. - Send the invoice to `DIAN`. - Open the generated XML file and observe the `cbc:Note` tag. **Observation:** The `Note` tag contains the `technical control key`. **Expected behavior:** The `Note` tag should only contain the Terms and Conditions value from the invoice. (Confirm with PO [1]) **Root Cause:** At [2] and [3], the code includes the `technical key` in the `Note` tag. [1]: https://www.odoo.com/mail/message/1164671931 [2]: https://github.com/odoo/enterprise/blob/a5f4bde1aa33ea7b796d7ee3f9d713a6c8ab8348/l10n_co_dian/models/account_edi_xml_ubl_dian.py#L664 [3]: https://github.com/odoo/enterprise/blob/a5f4bde1aa33ea7b796d7ee3f9d713a6c8ab8348/l10n_co_dian/models/account_edi_xml_ubl_dian.py#L1600-L1603 opw-6513843 Forward-Port-Of: odoo/enterprise#132606 Forward-Port-Of: odoo/enterprise#131495
The Luxembourg reporting module now uses a working download link for the FAIA XSD file. This prevents failed or empty downloads, helping users access the required reporting schema reliably.
Original PR description
The old link points to a file with zero bytes. opw-6344914 Forward-Port-Of: odoo/enterprise#132582
The scheduled payroll data update has been optimized to avoid memory errors during processing. This helps payroll operations complete smoothly and reduces the risk of interrupted background updates.
Original PR description
From memory error to smooth execution Forward-Port-Of: odoo/enterprise#129959
Australian payroll onboarding now correctly recognizes bank details when a BSB and bank name are provided without a BIC. This prevents users from seeing an incorrect missing bank details warning after completing setup.
Original PR description
Bug reproduction: 1 - Install l10n_au_hr_payroll_api, settings→payroll→AU localization 2 - Start payroll onboarding `2.1 - Next step→fill in BSB and keep BIC empty→Set Bank details` 3 - No BSB or bank set warning continues to appear Bug cause: 1 - For that warning, there is check that checks bic (to not keep empty) Bug solution: 1 - bic check is removed and bank name check is added task-6540863 Forward-Port-Of: odoo/enterprise#130792
This fix hides document actions when no file has been uploaded, preventing users from seeing unusable buttons. It also keeps budget and report amounts properly formatted when a user opens an amount for editing and clicks away without making changes.
Original PR description
Hide Open Document on working files when no attachment Problem: In working files checks with attachment, Open Document button and trash icon are visible even if there is no attachment uploaded yet.…
Hide Open Document on working files when no attachment Problem: In working files checks with attachment, Open Document button and trash icon are visible even if there is no attachment uploaded yet. Cause: We check for existence of attachment by `t-if="record.attachment_ids"`, but `record.attachment_ids`, `record.attachment_ids.value` and `record.attachment_ids.raw_value` are all truthy even if there is no attachment uploaded yet. Fix: Use `invisible="not attachment_ids"` just like before. *** Fix formatting when clicked outside without any change Steps to reproduce:- - Create a budget with a P&L report. - Set a value in a cell. - Then press the edit amount, click somewhere else without changing the amount. - The amount is no longer formatted properly. Cause: When user clicks edit, we populate formatted value for locale format type but when user clicks outside without change, in function `onBlur` we compare populated amount(which is formatted) with cell's no format value. So it mismatches and `focused` is never set `false`. Fix: Make getter `editableValue`, which returns `editableNumericValue` for locale format type and no format value otherwise. Now use this `editableValue` in `onFocus`, `onBlur`(this solves the bug) and `inputValue` to make it more clear. *** [task-6413368](https://www.odoo.com/odoo/project/967/tasks/6413368) Forward-Port-Of: odoo/enterprise#131989
Resetting an expense report to draft now also clears any Studio approval already granted for posting journal entries. This prevents old approvals from being reused after changes, ensuring the approval process is repeated when needed.
Original PR description
Currently, when resetting to draft an expense sheet, approval steps added with studio are not reset, silently granting the change Steps to reproduce: - In Studio, add an approval rule on the "Post Journal Entries" button on expense sheet - Create an expense report, submit it and approve it - Click "Post Journal Entries" and approve approve it - Open the journal entry and reset it to draft - Back to the expense sheet, reset it to draft too Issue: The "Post Journal Entries" button is still approved, showing the previous approver with the same approval date. Analysis: Approval entries are dropped on state change by a base automation that Studio builds when the rule is created. However it is only done for sale order, account move and purchase order. opw-6530689 Forward-Port-Of: odoo/enterprise#130692
Updates Vietnam Profit and Loss reporting to match Circular 99/2025/TT-BTC, including the new mandatory investment property gain/loss line and revised line numbering. Balance Sheet and Profit and Loss comparisons remain available while avoiding an unsupported growth percentage column layout.
Original PR description
Circular 99/2025/TT-BTC (effective FY2026) replaces Circular 200/2014/TT-BTC. The Profit & Loss statement (Mẫu B02-DN) gains a new mandatory line, "Lãi/lỗ của hoạt động bán, thanh lý bất động sản đầu…
Circular 99/2025/TT-BTC (effective FY2026) replaces Circular 200/2014/TT-BTC. The Profit & Loss statement (Mẫu B02-DN) gains a new mandatory line, "Lãi/lỗ của hoạt động bán, thanh lý bất động sản đầu tư" (mã số 21), computed from the new 5117/6327 sub-accounts (see the paired l10n_vn commit). Financial income, financial expenses and the interest memo line shift to mã số 22/23/24 accordingly, the "Net profit from operating activities" formula is updated to include the new line, and all following line numbers/labels are renumbered to match the printed form. The interest memo line itself is renamed from "Chi phí lãi vay" to "Chi phí đi vay" (Borrowing costs), per the gazette text. Also, per revised guidance: - Revenue and cost of goods sold now exclude the new investment property sub-accounts (5117/6327), so those amounts aren't double-counted between the main lines and the new gain/loss line. - Other income is now computed from the "income_other" account type instead of a hardcoded account code, matching how the chart of accounts already classifies it. Both the Balance Sheet and Profit and Loss VN reports also declare an extra "Code" string column (for the printed-form mã số) alongside "Balance". Core's growth-comparison heuristic only turns on the auto growth-% column when there are exactly 2 total columns; with Code+Balance that becomes 4 once a comparison period is added, so the % never renders even though the underlying side-by-side comparison values are fine. account.report exposes filter_growth_comparison as a toggle independent from filter_period_comparison for exactly this case (see Trial Balance and the Deferred reports), so both reports set it to False: period comparison stays available, only the auto growth-% column is suppressed. Task-6518304 Forward-Port-Of: odoo/enterprise#132715 Forward-Port-Of: odoo/enterprise#131085
Invoiced point-of-sale orders are now correctly sent to preparation displays when payment is validated. This prevents kitchen or preparation teams from missing paid orders that include an invoice, helping avoid fulfillment delays.
Original PR description
Steps to reproduce ------------------ 1. Open a PoS that has a preparation display. 2. Create an order and go to the payment screen without sending it to the kitchen. 3. Enable Invoice, set a…
Steps to reproduce ------------------ 1. Open a PoS that has a preparation display. 2. Create an order and go to the payment screen without sending it to the kitchen. 3. Enable Invoice, set a customer and validate the payment. -> The order does not appear on the preparation display. Without the invoice it does appear. Why it's happening ------------------ `sync_from_ui` in `pos_enterprise` only creates the preparation lines when the order state is 'paid'. Validating an invoiced order pays it and creates the invoice in the same call, and `_generate_pos_order_invoice` sets the state to 'done', so when the state is checked it is not 'paid' anymore and the order is skipped. Until 19.3, these skipped orders were still sent by the sync coming with the `preparation` context, which was creating the lines without looking at the state. In 19.4, 8491f7a74363 changed this sync to only ask the displays to reload their orders, so the lines of the skipped orders are not created anymore. On master, invoicing does not set the state to 'done' since f1f5a0b8f8a3, so the order stays 'paid' and its lines are created. The fix ------- Also create the preparation lines when an order sent as 'paid' by the client ends up 'done', meaning it was paid and invoiced by the current call. opw-6513655
This fix prevents Odoo Studio from crashing when a user creates a new app and opens the Map view option. It improves reliability for users configuring views in Studio, with no expected workflow changes beyond the crash being resolved.
Original PR description
In a new app in studio tab "views" ; click on map view. Before this commit there was a crash because of some props not correctly defined After this commit there is no crash
This fix ensures field service planning tests use a consistent employee time zone instead of inheriting the demo user's location. It prevents false test failures caused by daylight or regional time differences, helping keep release validation reliable.
Original PR description
Employees created in TestPlanningFieldServiceCommon had no explicit tz, so they inherited the acting user's timezone. With --with-demo that user's tz is Europe/Brussels, shifting the resource calendar's work hours by 1h in January and making test_planning_slot_allocated_hours_on_multiple_resources compute 7 allocated hours instead of the expected 8. runbot-946587 Forward-Port-Of: odoo/enterprise#131570
Belgian payroll now calculates guaranteed average pay more accurately when an employee's salary and work schedule change within the same month. This helps ensure payslips reflect the right balance of worked time and theoretical hours, reducing payroll discrepancies.
Original PR description
Handle salary and schedule changes occurring in the same month by splitting the month into salary periods, using the longest period as the anchor, valuing the shorter periods normally, and balancing the longest period with the remaining theoretical hours. Task Id: 6107129 Forward-Port-Of: odoo/enterprise#127104
Ecuadorian point-of-sale orders now keep the user's choice when the Invoice option is unchecked during payment. This prevents unwanted invoices from being created and sent to the tax authority after order validation or resynchronization.
Original PR description
Steps to reproduce: - Ecuadorian company, PoS with a preparation printer (or use the "Back" button on the feedback screen) - Open a session, add a product, go to the payment screen - Select a…
Steps to reproduce: - Ecuadorian company, PoS with a preparation printer (or use the "Back" button on the feedback screen) - Open a session, add a product, go to the payment screen - Select a customer, uncheck "Invoice", pay in cash and validate Issue: An invoice is created and sent to the SRI although "Invoice" was unchecked. Cause: l10n_ec_edi_pos patches PosOrder.setup() to force to_invoice = true. setup() runs not only on creation but every time the record is reloaded from the server, so the sync done at validation flips the flag back to true in the browser. Since the paid order can now be re-synced after validation (kitchen printer, "Back" on the feedback screen), the server processes it in process_saved_payments, writes to_invoice = True and generates the invoice. Fix: Only apply the Ecuadorian default when the loaded values carry no to_invoice, i.e. for a newly created order. Reloaded records keep the value the user chose. opw-6572987 Forward-Port-Of: odoo/enterprise#131612
This fix prevents Odoo from running currency translation adjustment calculations when all consolidated companies use the same currency. It avoids unnecessary processing while keeping reporting behavior unchanged for multi-currency consolidations.
Original PR description
The condition used to be options['currency_table']['type'] != 'cta'. In monocurrency, the 'currency_table' option key contained 'monocurrency', so the CTA computation always returned an empty result. However, now that the option key has been removed and we use self.currency_translation, some additional check needs to be added in order to avoid uselessly computing CTA when not consolidating companies in different currencies.
Users working with branch companies can now find contacts owned by the parent company when setting a partner during bank reconciliation. This fixes a multi-company lookup issue and helps teams reconcile bank transactions without manual workarounds.
Original PR description
When setting a partner from the bank reconciliation control panel, the partner lookup domain only considered global contacts and contacts directly linked to the selected company IDs. This caused a multi-company issue for branch companies, where users could not find contacts owned by their parent company. The domain is now updated to include: Global contacts (company_id = false) Contacts whose company is a parent of the selected companies (company_id parent_of companyIds) Forward-Port-Of: odoo/enterprise#122935 Forward-Port-Of: odoo/enterprise#118719
This fix prevents an error when users open the rental schedule from a product that no longer has any variants. The system now skips a missing default product reference, allowing the schedule view to open normally and reducing disruption for rental teams.
Original PR description
Currently, an error occurs when opening the Rental Order Lines Schedule view. **Steps to Reproduce:** - Install the `sale_stock_renting` module. - Go to `Settings` and enable `Variants`. - Go to…
Currently, an error occurs when opening the Rental Order Lines Schedule view. **Steps to Reproduce:** - Install the `sale_stock_renting` module. - Go to `Settings` and enable `Variants`. - Go to `Rental` > `Products` and create a product. - In the `Attributes & Variants` tab, add an attribute with two values and save. - Delete all variants using the `Variants` smart button or from `Inventory` > `Products` > `Product Variants`. - Return to the `Product` and click the `In Renting smart button`. `IndexError: list index out of range` When a product template is created, a product variant is automatically generated if the product has no attributes. Deleting this variant also deletes the product template [1]. By explicitly creating attribute values, a dynamic product variant is generated. Deleting this variant does not delete the product template because of the dynamic attribute [2]. When the In Renting button is clicked, it it going to open the rental order lines Schedule view and adds default_product_id to the action context. Since no product variants exist anymore, accessing the default product variant raises an error [3]. This commit ensures that default_product_id is not added to the context when the product template has no product variants [1]: https://github.com/odoo/odoo/blob/1f70b81eeebcc62b64c18772915e2f4696e189e7/addons/product/models/product_product.py#L486 [2]: https://github.com/odoo/odoo/blob/1f70b81eeebcc62b64c18772915e2f4696e189e7/addons/product/models/product_product.py#L478-L480 [3]- https://github.com/odoo/enterprise/blob/98ec35f3f17a2b706ad4fcc4d6161d12d05eadfd/sale_renting/models/product_product.py#L69 [4]: https://github.com/odoo/odoo/blob/942d3892c33e7ccdcfec260c3da37095069b699e/addons/stock/models/product.py#L635-L643 [5]: https://github.com/odoo/odoo/blob/942d3892c33e7ccdcfec260c3da37095069b699e/addons/stock/models/product.py#L601-L612 sentry-7637715724 Forward-Port-Of: odoo/enterprise#132401 Forward-Port-Of: odoo/enterprise#126356
Selecting a city in an employee's private address now immediately fills the related address details in the form. This avoids confusion by showing the correct address information before saving, matching the data that would already be recorded after save.
Original PR description
Issue: When you select a city in the employee's private address field, the other address fields should be populated accordingly. Even though once you save, the changes are recorded, they don't appear in real-time in the form. This is because the onchange related to the address city field was only in `hr.version`, but not `hr.employee`, so the automatic population upon the change of the city wasn't visible in the employee form view. Fix: Add _onchange_private_city_id() in hr.employee under hr_address_extended module which calls the same-name onchange in the linked version. task-6584206 Forward-Port-Of: odoo/odoo#289837
Link previews in the HTML editor now ignore metadata that contains only spaces. This prevents users from seeing an empty clickable preview area and correctly falls back to showing the link URL when no real title is available.
Original PR description
Problem: When a link's metadata contains whitespace-only strings for `og_title`, `og_description`, or `og_image` (e.g. `og_title: " "`), the link popover displays an empty clickable preview area.…
Problem: When a link's metadata contains whitespace-only strings for `og_title`, `og_description`, or `og_image` (e.g. `og_title: " "`), the link popover displays an empty clickable preview area. Cause: `LinkPopover` assigned raw metadata values directly. Non-empty whitespace strings evaluate to truthy values in JavaScript (`" "` is truthy), preventing fallback to the default URL or empty string. Solution: Trim the metadata values (`og_title`, `og_description`, `og_image`) when populating state so that whitespace-only values evaluate to empty strings and trigger appropriate fallbacks. Steps to reproduce: - Open HTML editor. - Add a link with URL `https://netorg4182089.sharepoint.com/:v:/s/projects/IQD72ajP3WOBT4jcAu_1qLfIAamL9lvrq4ls1Bs9XCyXJvw?e=vQ9wH3`. - Open the link popover. => Observe that the popover title falls back to the URL instead of showing an empty clickable space. opw-6564118 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#288188
A timing issue in the rental planning test was corrected so it handles Brussels timezone dates consistently. This prevents false test failures during early-morning runs and helps keep release validation stable without changing customer-facing behavior.
Original PR description
**Issue:** `test_payment_renting_product_available` test is failing when executed between 0:00 AM and 2:00 AM in Brussels timezone (UTC+2): ``` AssertionError: datetime.datetime(2026, 6, 22, 16, 0) != FakeDatetime(2026, 6, 21, 16, 0) : The planning slot should begin at the same time as the picking time. ``` In the database the datetime is stored in UTC, which is the previous day for the example above. In `test_payment_renting_product_available` test, the datetime is passed to `datetime.combine()` function that naively uses the date part, which leads to a one-day delta. The datetime should be converted to the working timezone before being passed to `datetime.combine()`. runbot-940435 Forward-Port-Of: odoo/enterprise#131944
This fixes a display issue in the HTML editor where bullet points could shift too far left when turning large header text into a list. Users creating formatted content will see cleaner, correctly aligned lists without needing manual adjustments.
Original PR description
Problem: Creating a list on a header block with large font size content causes the list marker/bullet to overflow to the left. Cause: `ListPlugin.blockToList()` wrapped block elements into a list without invoking `this.adjustListPadding(list)`, leaving the list padding unadjusted for larger font sizes. Solution: Call `this.adjustListPadding(list)` in `blockToList` so that proper inline padding is set based on the list item content font size. Steps to reproduce: - Create a header block (e.g. Header 4). - Change the font size of the header content to be bigger. - Apply a list on the content. => Observe that the list marker overflows to the left. opw-6542903 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286975
This fix prevents Kenyan eTIMS invoice numbering from moving backwards after certain failed invoice submissions. It helps avoid duplicate invoice numbers and incorrect receipt details being copied between documents, improving reliability for compliance reporting.
Original PR description
Give an eTIMS invoice number back to the sequence only when the failing call is the one that took it, and only when it is still the last one handed out. When sending a customer invoice fails with anything other than a timeout, the number is given back so that it is not consumed for nothing. current issue: - send an invoice, let it time out, so it keeps number N - send other invoices, so the sequence moves past N - send the first one again and let it fail with a non-timeout error - the sequence drops by one and the next invoice sent reuses a number Both documents then sit under the same number. On its next attempt the one that was never accepted finds the other one's filing through selectInvoiceDetails and copies its receipt details. opw-6502563 Forward-Port-Of: odoo/enterprise#131790 Forward-Port-Of: odoo/enterprise#129994
Fixes several issues that could make accounting report snapshots use the wrong company context, miss branch companies, or delete snapshots unnecessarily. This improves the reliability of historical financial reporting, especially for multi-company setups and temporary lock date exceptions.
Original PR description
[FIX] account_reports: don't rely on self.env.company in _is_available_for This caused issues in snapshot generations, where the function is called without forcing the active company in the context.…
[FIX] account_reports: don't rely on self.env.company in _is_available_for This caused issues in snapshot generations, where the function is called without forcing the active company in the context. Relying on companies[0] should do the same in other contexts it is called in, and is more resilient. =============================================== [FIX] account_reports: snapshots: properly sandbox move lines per company when deducing the first date for snapshots =============================================== [FIX] account_reports: snapshots properly access subranches when checking snapshots to delete _accessible_branches only returns the companies that are in self.env.companies. If we want all the sub-branches (and the company itself), we should search with a child_of. This was found while debugging a test, because the accessible branches returned for a company where an empty recordset instead of the company itself, just because it wasn't in self.env.companies. =============================================== [FIX] account_reports: snapshots: don't delete snapshots uselessly when there is a temporary lock date exception Before this commit, when checking which snapshot needed to be deleted, we always removed the ones created after a temporary lock date exception, without considering the end time of this exception. Also, we were only deleting the snapshots with a higher or equal create_date, which was wrong: snapshot created before the exception should also be deleted. Forward-Port-Of: odoo/enterprise#132557