Daily updates from Odoo
Navigate
Branch
Friday, June 19, 2026
147 changes
25 changes
New functionality added to Odoo
This update incorporates support for the states of Tajikistan, Turkmenistan, Kyrgyzstan, Armenia, and Kazakhstan, aligning with international ISO 3166-2 standards. This change improves data accuracy and consistency by displaying state names instead of codes within address fields. This update is considered an improvement for our global user base.
Original PR description
Added the states of Tajikistan, Turkmenistan, Kyrgyzstan, Armenian, and Kazakhstan to align with official ISO 3166-2 standards. In addition change their address format of the country to show state name instead of state code. task-6237707 task-6237595 task-6237473 task-6237440 task-6237284 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269091
Enhancements to existing features
This update changes the unit of measure for the 'Flour' product in the Point of Sale system to kilograms. This adjustment is necessary to properly configure product weighing, particularly for compliance with the l10n_eu_iot_scale certification, ensuring accurate inventory tracking.
Original PR description
This small PR sets the "Flour" product's units of measure to kilograms. This helps to configure the weighing of the product, especially for the l10n_eu_iot_scale cert Forward-Port-Of: odoo/odoo#270508
Resolved issues and error corrections
This update addresses a reported issue where users could join live chat conversations that had already ended. The fix hides the 'join' button when a conversation is marked as finished, improving the user experience and preventing unnecessary attempts to engage. This ensures a cleaner and more accurate representation of available chat channels.
Original PR description
Feedback came from our live chat team. It is possible to join a conversation despite the conversation having ended already. so from now the **join button will be hidden** if the livechat is ended. task-6314128 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This update corrects a bug where the duration field in Odoo was incorrectly returning 0 when using formulas. Now, the field accurately calculates duration and displays an error message if the input is invalid, ensuring accurate time tracking. This improves data reliability and prevents miscalculations.
Original PR description
Before this commit, using a formula in the duration field was returning 0. Now, it resolves the formula. TASK-6150460 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#263301
This update resolves an issue where the Saudi Arabia E-invoicing module installation would fail if certain tax settings were missing. The fix filters out missing taxes during installation, eliminating a previous workaround and ensuring smoother module deployment. This improves the reliability of the module for Saudi businesses.
Original PR description
**Issue:** Installing the l10_sa_edi E-invoicing module causes an error in versions 19.1 and above if any of the taxes in the account.tax-sa.csv are missing. This behavior was previously avoided via the post init function _l10n_sa_edi_post_init(), which no longer works due to the change made to ir_module.py fetching the template data during the module installation (made in commit 64f9dcb). **Reproduction Steps:** Install Accounting Configuration > Settings > Change "Fiscal Localization" to Saudi Arabia Configuration > Taxes > Delete 0% "Not Subject to VAT" tax Try to install l10n_sa_edi Saudi Arabia - E-invoicing **Fix:** Updated '_get_sa_edi_account_tax()to filter out taxes that don't already exist on the database. Removed the_l10n_sa_edi_post_init()` function since it should now be obsolete. Related ticket: opw-6293740 Forward-Port-Of: odoo/odoo#270767 Forward-Port-Of: odoo/odoo#270336
This update fixes an issue where an incorrect journal entry was inflating the reported amount of liabilities on the Balance Sheet in the French financial reporting module. The change ensures that a specific account (45) is not incorrectly categorized as a 'borrowing,' improving the accuracy of financial reporting. This resolves a discrepancy identified in the reporting.
Original PR description
1. Create a journal entry with: -> 455100 Partners/Associates - Current Accounts - Principal → Credit -> 512001 Bank → Debit 2. Navigate to Accounting → Reporting → Balance Sheet. -> Observe that the amount of the journal entry appears twice in the Balance Sheet: 1. Under Borrowings and Similar Liabilities 2. Under Partners' Current Accounts 45 accounts should not be included under borrowings and similar liabilities opw-6271305
This update resolves an issue preventing users with Sales access from inserting data into Quotation templates through the spreadsheet management feature. The change adds a setting to ensure the necessary permissions are recognized, now allowing seamless data entry.
Original PR description
Current behavior before PR: - The `can_insert_in_spreadsheet` session flag was not set by the spreadsheet_sale_management module. - Users with proper Sale access rights still could not insert into Quotation templates. Desired behavior after PR is merged: - Added logic to set `can_insert_in_spreadsheet` when the module is installed and the user has the required access rights. Task: [5960761](https://www.odoo.com/odoo/project/2328/tasks/5960761) Forward-Port-Of: odoo/enterprise#120903 Forward-Port-Of: odoo/enterprise#108674
This update resolves an issue that occurred during website theme upgrades. When a custom theme isn't fully supported, the system incorrectly identifies a missing theme manifest, leading to a critical error. This fix ensures the system gracefully handles missing theme manifests, preventing upgrade failures.
Original PR description
After commit 3cbaad4, the theme manifest is now looked up to find addon snippets for the configurator. However, during an upgrade, if a website is configured with a third-party/custom theme whose…
After commit 3cbaad4,
the theme manifest is now looked up to find addon snippets for the configurator. However, during an upgrade, if a website is configured with a third-party/custom theme whose code is not present in the addons path, `Manifest.for_addon()` returns `None` and `_generate_primary_snippet_templates` raises an `AttributeError`:
```py
2026-06-16 03:27:47,155 24126 INFO db_4367932 odoo.modules.loading: loading website/views/new_page_template_templates.xml
2026-06-16 03:27:47,803 24126 WARNING db_4367932 odoo.modules.module: module theme_prime: manifest not found
2026-06-16 03:27:47,845 24126 WARNING db_4367932 odoo.modules.loading: Transient module states were reset
2026-06-16 03:27:47,846 24126 ERROR db_4367932 odoo.registry: Failed to load registry
2026-06-16 03:27:47,846 24126 CRITICAL db_4367932 odoo.service.server: Failed to initialize database `db_4367932`.
Traceback (most recent call last):
File "/home/odoo/src/odoo/19.0/odoo/tools/convert.py", line 605, in _tag_root
f(rec)
File "/home/odoo/src/odoo/19.0/odoo/tools/convert.py", line 273, in _tag_function
_eval_xml(self, rec, env)
File "/home/odoo/src/odoo/19.0/odoo/tools/convert.py", line 197, in _eval_xml
result = method(*args, **kwargs)
File "/home/odoo/src/odoo/19.0/addons/website/models/ir_module_module.py", line 704, in _generate_primary_snippet_templates
theme_addons = theme_manifest.get('configurator_snippets_addons', {})
AttributeError: 'NoneType' object has no attribute 'get'
```
We also get the missing manifest logs right before the error.
TBG-2781
[`_generate_primary_snippet_templates`]: https://github.com/odoo/odoo/blob/19.0/addons/website/models/ir_module_module.py#L700
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#270217This update improves how our connected scales communicate tare status to the Odoo frontend. Previously, the system wasn't reliably indicating when a scale was in tare mode. Now, the frontend receives this information, ensuring accurate weight readings and a better user experience for weighing operations.
Original PR description
See: https://github.com/odoo/enterprise/pull/119960 Before this commit, there was no way for the frontend to know if the tare function on the connected scale was active, despite the driver keeping track in the `tare_mode` variable. After this commit, we optionally send the `tare_mode` alongside the weight when `read_once` is called. By default it still returns just the weight for backwards compatibility. The `tare_mode` is now also updated by the status command, allowing it to be set as soon as tare is pressed, instead of after weight is applied. task-6273412 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269211
This update adjusts the checksum within the EU IoT Scale Certification module to align with recent changes in the core Odoo system. This ensures compatibility and stability following a related update in Odoo itself, preventing potential issues with data integrity. It's a routine maintenance task.
Original PR description
This commit simply adjusts the checksum to align with the changes in odoo/odoo#269211. task-6273412 Forward-Port-Of: odoo/enterprise#120954
This update fixes an issue preventing users from accessing payslip lists within the employee departure process. The changes include making fields read-only to prevent unintended modifications and relocating currency data for improved performance. This ensures accurate payslip access and data integrity.
Original PR description
Bug 1: In the departure tab of the Employee, you can't open the payslip list Fix: Added a check to get the correct departure id depending on the model we are in Bug 2: You can select payslips for other employees than the departing employee and the payslips list is not affected Fix: made fields `l10n_be_payslip_n_ids` and `l10n_be_payslip_n1_ids` readonly so they can't be modified in the UI without being saved Bug 3: You get an error because you can't read `currency_id` when opening n payslips (happens when the monetary fields are shown in the list) Fix: moved the `currency_id` to be inside the list instead of the parent form task-id: 6265648 Forward-Port-Of: odoo/enterprise#119402
This update resolves an issue where UBL invoices with embedded PDF attachments caused import errors due to unexpected whitespace in the attachment data. The change adjusts how attachments are processed, ensuring compatibility with newer Odoo versions and preventing import failures.
Original PR description
Steps to reproduce: 1. Go to [Accounting -> Vendors - Bills] and click Upload 2. Import a UBL BIS3 XML invoice containing an embedded PDF attachment in `AdditionalDocumentReference/Attachment/EmbeddedDocumentBinaryObject` where the content contains internal line breaks or spaces (file available in ticket attachments). Issue: `Error during import: Only base64 data is allowed` Why this happens: Following changes in v19.3 (7662537c878ffe58ed5c7248e4114062c333312b), the import logic in `_import_attachments` was changed to use the `raw` field instead of the `datas` field when creating the ir.attachment record. The `raw` field takes the raw bytes directly, whereas `datas` previously stored the base64 computed version of `raw`, which handled whitespace characters. Some XML files produce `EmbeddedDocumentBinaryObject` content with internal newlines. These were harmless in older versions but now cause a crash. opw-6271352
This update resolves a problem where order signing with Fiskaly failed after the company's API key was updated. The system now correctly resets and recreates the necessary Fiskaly connection details, ensuring orders can be signed without interruption. This prevents disruptions to sales processes for businesses using the Fiskaly integration.
Original PR description
When the Fiskaly API key/secret is changed, the company is bound to a new Fiskaly organization (owner). The SCU and cash registers stored on the company and POS configs were created under the previous owner and no longer exist for the new one, so signing orders fails with E_CASH_REGISTER_NOT_FOUND. Clear l10n_at_pos_company_scuid and each config's l10n_at_cash_regid together with the access token so they are recreated under the new organization on the next authentication. opw-6297695 Forward-Port-Of: odoo/enterprise#120839
This update corrects a bug in how project task rescheduling respects time buffers, particularly when multiple tasks are dependent. The fix ensures that buffers are applied correctly between tasks, preventing scheduling conflicts and maintaining accurate timelines. This improves the reliability of project scheduling.
Original PR description
Steps to reproduce: ---------------------------------------- - Have the company calendar work from 9 to 17 on weekdays - In project gantt view, create tasks with dependencies like this: ``` - [Task…
Steps to reproduce:
----------------------------------------
- Have the company calendar work from 9 to 17 on weekdays
- In project gantt view, create tasks with dependencies like this:
```
- [Task 1] (June 08, 09:00 AM - 10:00 AM) ──┐
├─> [Task 3] (June 11, 09:00 AM - 10:00 AM)
- [Task 2] (June 10, 09:00 AM - 10:00 AM) ──┘
```
- Make sure the "Auto-Reschedule (Keep Buffer)" is selected
- Then reschedule task 1 to `(June 09, 09:00 AM - 10:00 AM)`
- Task 3 is rescheduled to `(June 15, 09:00 AM - 10:00 AM)`
**Expected behavior:**
To respect the buffers, task 3 should have been rescheduled to `(June 12, 09:00 AM - 10:00 AM)`:
- The buffer from task 1 is 23 working hours after `June 09, 10:00 AM` is `June 12, 09:00 AM`
- The buffer from task 2 doesn't affect the rescheduling.
Cause:
----------------------------------------
From `_web_gantt_update_next_candidates_dates()` we call `_get_new_dates()` with `seconds_between_tasks` being the duration of working hours between the end of task 1 and the start of task 3. We also call it with `first_possible_start_date_per_candidate` being the end of task 3 also depends on task 2.
Then `_get_new_dates()` counts the working hours from `first_possible_start_date_per_candidate` until it reaches `seconds_between_tasks`. Which means the buffer between task 1 and task 3 is actually applied between task 2 and task 3.
Solution:
----------------------------------------
The value contained in `first_possible_start_date_per_candidate` is irrelevant, the correct value to give to `_get_new_dates()` would be `compute_end_date` as we want to keep the buffer to be calculated from this datetime.
So we create a fake dictionary with only the task and `compute_end_date` and give it to `_get_new_dates()`.
The same logic applies when moving tasks backwards.
opw-5973597
Forward-Port-Of: odoo/enterprise#119161This update fixes an issue where stock replenishment wasn't updating existing purchase orders correctly, leading to duplicate order creation. Now, the system intelligently updates quantities in existing purchase orders when replenishing stock automatically, ensuring accurate stock levels and reducing unnecessary order placements. This improves the efficiency of our inventory management.
Original PR description
Replenishing the stock from an orderpoint will look for a purchase order line having the same orderpoint_id in order to update the quantity instead of creating a new one. The issue is manual orderpoint are deleted right after the replenishment. Replenishing two times the same product will always create a new purchase order line. This commit makes the orderpoint_id is pass in the procurement values only in case of `trigger == auto` orderpoint. 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#269937 Forward-Port-Of: odoo/odoo#269725
This update corrects a technical error that prevented the system from correctly calculating virtual leave durations. The fix ensures that the system properly utilizes resource calendars when generating reports, resolving a singleton error and improving the accuracy of leave calculations. This ensures accurate reporting of employee availability for holiday-related tasks.
Original PR description
resource_calendar was not being when calculting virtual leaves, it leads to a singleton error here:…
resource_calendar was not being when calculting virtual leaves, it leads to a singleton error here:
https://github.com/odoo/odoo/blob/11b0195dddad5055fc33fa3e28b2d2ca60f24935/addons/l10n_fr_hr_holidays/models/hr_leave.py#L163
```
File "/home/odoo/src/odoo/saas-19.2/odoo/orm/models.py", line 519, in _table_sql
table_query = self._table_query
^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-19.2/addons/hr_holidays/report/hr_leave_employee_report.py", line 40, in _table_query
self._compute_leave_duration(report_records)
File "/home/odoo/src/odoo/saas-19.2/addons/hr_holidays/report/hr_leave_employee_report.py", line 94, in _compute_leave_duration
leaves_durations = virtual_leaves._get_durations(additional_domain=[('holiday_id', 'not in', leave_ids)])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-19.2/addons/l10n_fr_hr_holidays/models/hr_leave.py", line 160, in _get_durations
while not leave.resource_calendar_id._works_on_date(date_start):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-19.2/addons/resource/models/resource_calendar.py", line 877, in _works_on_date
self.ensure_one()
File "/home/odoo/src/odoo/saas-19.2/odoo/orm/models.py", line 5253, in ensure_one
raise ValueError("Expected singleton: %s" % self)
ValueError: Expected singleton: resource.calendar()
```
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#266084This update resolves a crash that occurred when users were manually correcting bank statement lines within the Odoo Enterprise system. The issue stemmed from a missing context key, preventing the correct journal from being assigned. This fix ensures accurate journal settings, preventing data inconsistencies and crashes during manual line adjustments.
Original PR description
When the manual correction tool was used to fill in the lines, we weren't passing the active context when creating the new records. In the case of bank statements, it could be an issue as the `default_journal_id` key is expected to be present to set the correct journal on the newly created bank statement line. Without this key in the context, it would default to the first journal with a valid type (see function `_search_default_journal`). If the journal found this way didn't match the current journal, a crash would occur when modifying the newly created lines. opw-[6294117](https://www.odoo.com/odoo/unassigned-tasks/6294117) Forward-Port-Of: odoo/enterprise#121032 Forward-Port-Of: odoo/enterprise#120745
This update fixes alignment issues within the Timesheet Assistant, specifically in the 'By Project' and 'Chronological' views. It ensures that long descriptions don't cause text to wrap incorrectly, and adds a consistent margin to the section indicating no billable time was recorded. This improves the overall clarity and usability of the timesheet reporting.
Original PR description
# [FIX] timesheet_grid: alignment issues in assistant This commit resolves the following alignment issues in the Timesheet Assistant: - View "By Project", the time wraps if description too long - View "Chronological", the time wraps if descriptions too long and project / task is not truncated - No timesheet recorded does not have a margin start # [FIX] sale_timesheet_enterprise: alignment issues in assistant This commit adds margin start on the "No (non-)billable time recorded" information. task-6264756 Forward-Port-Of: odoo/enterprise#120608
This update fixes a display issue where overtime details weren't showing correctly in attendance records. The change reverses a condition in the system's settings, ensuring that overtime information is now accurately presented for all employee attendance records, regardless of their overtime ruleset configuration.
Original PR description
Steps: * Create an extra-hours attendance for an employee that has overtime ruleset OR * Create an extra-hours attendance for an employee that has no overtime ruleset Issue: * The overtime details in the attendance view visibility condition was flipped Solution: * Reverse the visibility condition of the XML element Task: 6295710 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#270399 Forward-Port-Of: odoo/odoo#269744
This update fixes an error in the Luxembourg localization settings, ensuring the correct stock valuation account (301 Inventories of raw materials) is used instead of an expense account (60761 Merchandise). This ensures accurate financial reporting for Luxembourg businesses using Odoo.
Original PR description
**Problem:** Valuation account for luxembourg is currently 60761 Merchandise which is incorrect because it's an expense account. We should rather use a current asset account like 301 Inventories of raw materials **Steps to reproduce on a fresh db:** - create a new db with modules stock_account and accountant (without demo data) - On the 'fiscal localization setting' set the package as 'Luxembourg' and save - ativate the automatic accounting setting **Current Behavior:** The 'stock valuation account' appearing below the automatic accounting setting is : 60761 Merchandise **Expected behaviour:** It should be 301 Inventories of raw materials Forward-Port-Of: odoo/odoo#270048 Forward-Port-Of: odoo/odoo#269469
This update fixes an issue where imported Peppol/UBL vendor bills with 100% discounts were incorrectly processed. The fix ensures that a line with a LineExtensionAmount of 0 is correctly interpreted as a fully discounted line, preventing incorrect quantity and discount calculations. This improves the accuracy of imported financial data.
Original PR description
When importing a Peppol/UBL vendor bill containing a line with a 100% discount, the line was created in Odoo with quantity=1 and discount of 100*original_qty, instead of the expected…
When importing a Peppol/UBL vendor bill containing a line with a 100% discount, the line was created in Odoo with quantity=1 and discount of 100*original_qty, instead of the expected quantity=qty_original and discount=100%. This happened because the line-level branching in `_import_ubl_invoice_line_add_price_unit_quantity_discount` relied on the truthiness of `line_extension_amount` to detect whether the `LineExtensionAmount` node was present in the XML. a line with a genuine `<cbc:LineExtensionAmount>0</...>` was indistinguishable from a line where the node was missing, and fell through to the fallback branch intended for incomplete XML. That fallback reconstructs the quantity from `<cbc:BaseQuantity>`, ignoring `<cbc:InvoicedQuantity>`, and then computes the discount percentage against the wrong denominator. a `LineExtensionAmount` of 0 is the only legal way to express a fully discounted line, so this case must be distinguished from the node being absent. the fix is simply checking if the line exist not if its True opw-6176349 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269322 Forward-Port-Of: odoo/odoo#265261
This update ensures that combo pricing displayed in the configurator dialog accurately reflects the order's currency. Previously, extra prices were not converted, leading to mismatched totals. Now, prices are automatically converted to the order's currency, guaranteeing accurate pricing and order totals.
Original PR description
Description of the issue/feature this PR addresses: In the combo configurator dialog, a combo item's extra_price and the price_extra of no_variant attributes are stored in the company/product…
Description of the issue/feature this PR addresses: In the combo configurator dialog, a combo item's extra_price and the price_extra of no_variant attributes are stored in the company/product currency but were sent to the front-end without conversion. When the order uses a pricelist in a different currency, the popup shows these extras at face value (e.g. an extra of USD 1700 appears as ARS 1700 instead of being converted). The sale order line itself already converts these extras, so the popup price and the actual line price didn't match. Current behavior before PR: _get_combo_item_data and _get_selected_ptavs_data return extra_price / price_extra raw, in the company currency. With a foreign-currency pricelist the combo configurator popup adds them 1-to-1 to the already-converted base price, displaying an incorrect total that doesn't match the resulting sale order line. Desired behavior after PR is merged: The controller converts extra_price and price_extra to the configurator's currency (via currency._convert()) before serializing them, so the popup shows the correct amounts in the pricelist currency and matches the price computed on the sale order line. A test (test_sale_combo_multicurrency.py) covers combo extra-price conversion with a foreign-currency pricelist. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269905 Forward-Port-Of: odoo/odoo#269386
This update resolves an issue where sending NFC-e invoices would halt the POS synchronization process when IAP credits were exhausted. The fix prevents a system error and ensures that POS transactions continue to sync smoothly, even when IAP credit limits are reached. This improves the reliability of the POS system for businesses using the l10n_br_edi_pos module.
Original PR description
When sending an NFC-e, tax calculation is done by calling Avatax through IAP. If the IAP account has no credits left, iap_jsonrpc() raises an InsufficientCreditError. opw-6290857 Forward-Port-Of: odoo/enterprise#120700
This update resolves an issue where timesheet settings (specifically, whether a project is billable) would reset after the timesheet systray was closed and reopened. Now, changes made to this setting are correctly saved and retained, ensuring accurate timesheet tracking. This improves data consistency and reporting.
Original PR description
## Behavior before PR 1. Open the timesheet systray. 2. Select a billable project. 3. Toggle the is_billable field. 4. Close and reopen the systray. 5. The is_billable value resets to its default instead of keeping the updated value. ## Expected Behavior After this PR The systray now correctly retains the is_billable value after being closed and reopened. ### Technical Notes The issue occurred because the systray view loads a sudo record that triggers compute methods, which overwrite the stored is_billable value. The fix ensures that after compute methods run, the saved is_billable value is preserved. Forward-Port-Of: odoo/enterprise#119681
This update corrects a previous issue where customers who had fully paid their outstanding balances no longer saw the Customer Statement button appear. The fix now checks for any past pay-later payment lines, ensuring that customers with a history of using pay-later options can still generate their statements. This improves the user experience for settled customers.
Original PR description
The override of _compute_has_moves was checking `total_due != 0` to set `has_moves` on for PoS pay_later customers. Once the customer is fully settled however, `total_due` is 0 and the check does not pass anymore, so `has_moves` goes back to `False` and the Customer Statement button hides for them, even though they had past pay_later payment lines. The fix is to check directly for any past pay_later `pos.payment` instead, which covers the cases where partner had used pay_later payment methods before, regardless if they have settled their total due or not. opw-6173760 Forward-Port-Of: odoo/enterprise#120911 Forward-Port-Of: odoo/enterprise#116536
15 changes
New functionality added to Odoo
This update incorporates support for the states of Tajikistan, Turkmenistan, Kyrgyzstan, Armenia, and Kazakhstan, aligning with international ISO 3166-2 standards. This change improves data accuracy and consistency by displaying state names instead of codes within address fields. This update is considered an improvement for our global user base.
Original PR description
Added the states of Tajikistan, Turkmenistan, Kyrgyzstan, Armenian, and Kazakhstan to align with official ISO 3166-2 standards. In addition change their address format of the country to show state name instead of state code. task-6237707 task-6237595 task-6237473 task-6237440 task-6237284 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269091
Resolved issues and error corrections
This update fixes an error in the Luxembourg localization settings, ensuring the correct stock valuation account (301 Inventories of raw materials) is used instead of an expense account (60761 Merchandise). This ensures accurate financial reporting for Luxembourg businesses using Odoo.
Original PR description
**Problem:** Valuation account for luxembourg is currently 60761 Merchandise which is incorrect because it's an expense account. We should rather use a current asset account like 301 Inventories of raw materials **Steps to reproduce on a fresh db:** - create a new db with modules stock_account and accountant (without demo data) - On the 'fiscal localization setting' set the package as 'Luxembourg' and save - ativate the automatic accounting setting **Current Behavior:** The 'stock valuation account' appearing below the automatic accounting setting is : 60761 Merchandise **Expected behaviour:** It should be 301 Inventories of raw materials Forward-Port-Of: odoo/odoo#270048 Forward-Port-Of: odoo/odoo#269469
This update fixes a bug that prevented customers from removing free shipping rewards once they were applied to their cart. The issue stemmed from a technical limitation in how the system handled different reward types. Now, customers can successfully remove free shipping and free product rewards from their carts, improving the user experience.
Original PR description
Steps to produce: --- - Install `website_sale_loyalty`. - Go to `Website > ecommerece > Loyalty > DIscount & Loyalty`. - Create a new discount & loyalty program > set program type as `promotions`. -…
Steps to produce: --- - Install `website_sale_loyalty`. - Go to `Website > ecommerece > Loyalty > DIscount & Loyalty`. - Create a new discount & loyalty program > set program type as `promotions`. - Under Rewards, select `Free Shipping` as the reward type. - Create a product with a price of 1000 and publish it. - Add the product to the cart from the website. - Observe that free shipping is automatically applied on cart. - Attempt to remove the free shipping reward from the cart. Issue: --- - Free shipping (and similarly, free product rewards) cannot be removed from the cart once applied. Root cause: --- - At [1], the `website_sale_loyalty_delete` context is only passed when the reward type is `discount`. As a result, for free shipping and free product rewards, the context is not set. At [2], the order line is removed, but the reward is not added to `disabled_auto_rewards`. The `_auto_apply_rewards` method runs immediately afterward, detects the missing reward, and re-applies it automatically. Fix: --- - Since there are three reward types (discount, free shipping, and free product), the condition restricting the context to only discount rewards should be removed. [1]https://github.com/odoo/odoo/blob/5e90858fa91348f6aa33b4f8a246e77fbb8ea63f/addons/website_sale_loyalty/models/sale_order.py#L179 [2]https://github.com/odoo/odoo/blob/5e90858fa91348f6aa33b4f8a246e77fbb8ea63f/addons/website_sale_loyalty/models/sale_order_line.py#L15-L23 opw-6159288 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#261732
This update resolves an issue that prevented website upgrades when using custom themes. Specifically, the system failed to find a necessary theme file, causing an error during the upgrade process. This fix ensures that website upgrades work correctly with custom themes.
Original PR description
After commit 3cbaad4, the theme manifest is now looked up to find addon snippets for the configurator. However, during an upgrade, if a website is configured with a third-party/custom theme whose…
After commit 3cbaad4,
the theme manifest is now looked up to find addon snippets for the configurator. However, during an upgrade, if a website is configured with a third-party/custom theme whose code is not present in the addons path, `Manifest.for_addon()` returns `None` and `_generate_primary_snippet_templates` raises an `AttributeError`:
```py
2026-06-16 03:27:47,155 24126 INFO db_4367932 odoo.modules.loading: loading website/views/new_page_template_templates.xml
2026-06-16 03:27:47,803 24126 WARNING db_4367932 odoo.modules.module: module theme_prime: manifest not found
2026-06-16 03:27:47,845 24126 WARNING db_4367932 odoo.modules.loading: Transient module states were reset
2026-06-16 03:27:47,846 24126 ERROR db_4367932 odoo.registry: Failed to load registry
2026-06-16 03:27:47,846 24126 CRITICAL db_4367932 odoo.service.server: Failed to initialize database `db_4367932`.
Traceback (most recent call last):
File "/home/odoo/src/odoo/19.0/odoo/tools/convert.py", line 605, in _tag_root
f(rec)
File "/home/odoo/src/odoo/19.0/odoo/tools/convert.py", line 273, in _tag_function
_eval_xml(self, rec, env)
File "/home/odoo/src/odoo/19.0/odoo/tools/convert.py", line 197, in _eval_xml
result = method(*args, **kwargs)
File "/home/odoo/src/odoo/19.0/addons/website/models/ir_module_module.py", line 704, in _generate_primary_snippet_templates
theme_addons = theme_manifest.get('configurator_snippets_addons', {})
AttributeError: 'NoneType' object has no attribute 'get'
```
We also get the missing manifest logs right before the error.
TBG-2781
[`_generate_primary_snippet_templates`]: https://github.com/odoo/odoo/blob/19.0/addons/website/models/ir_module_module.py#L700
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#270217This change reverses a recent update that prevented the blog footer from being editable within the Odoo website builder. The previous change was intended to improve builder stability, but it inadvertently restricted access to a key element. This reversion restores the original functionality, allowing users to modify the blog footer as intended.
Original PR description
This reverts commit[1] which introduced not_activable_element_selectors resource in html_builder and used it to make the blog footer not selectable in the builder. [1]:https://github.com/odoo/odoo/commit/87d0c49a6 Forward-Port-Of: odoo/odoo#268141 Forward-Port-Of: odoo/odoo#268024
This update fixes alignment issues within the Timesheet Assistant, specifically in the 'By Project' and 'Chronological' views. The changes ensure that time entries and project descriptions display correctly, even with lengthy descriptions, and add a margin to the 'No time recorded' section for better visual clarity.
Original PR description
# [FIX] timesheet_grid: alignment issues in assistant This commit resolves the following alignment issues in the Timesheet Assistant: - View "By Project", the time wraps if description too long - View "Chronological", the time wraps if descriptions too long and project / task is not truncated - No timesheet recorded does not have a margin start # [FIX] sale_timesheet_enterprise: alignment issues in assistant This commit adds margin start on the "No (non-)billable time recorded" information. task-6264756 Forward-Port-Of: odoo/enterprise#120608
This update ensures that combo prices shown in the configurator dialog accurately reflect the order's currency. Previously, extra prices were displayed incorrectly due to a lack of currency conversion. Now, prices are automatically converted, guaranteeing accurate totals and matching sale order line prices.
Original PR description
Description of the issue/feature this PR addresses: In the combo configurator dialog, a combo item's extra_price and the price_extra of no_variant attributes are stored in the company/product…
Description of the issue/feature this PR addresses: In the combo configurator dialog, a combo item's extra_price and the price_extra of no_variant attributes are stored in the company/product currency but were sent to the front-end without conversion. When the order uses a pricelist in a different currency, the popup shows these extras at face value (e.g. an extra of USD 1700 appears as ARS 1700 instead of being converted). The sale order line itself already converts these extras, so the popup price and the actual line price didn't match. Current behavior before PR: _get_combo_item_data and _get_selected_ptavs_data return extra_price / price_extra raw, in the company currency. With a foreign-currency pricelist the combo configurator popup adds them 1-to-1 to the already-converted base price, displaying an incorrect total that doesn't match the resulting sale order line. Desired behavior after PR is merged: The controller converts extra_price and price_extra to the configurator's currency (via currency._convert()) before serializing them, so the popup shows the correct amounts in the pricelist currency and matches the price computed on the sale order line. A test (test_sale_combo_multicurrency.py) covers combo extra-price conversion with a foreign-currency pricelist. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269905 Forward-Port-Of: odoo/odoo#269386
This update resolves an issue where focusing on the end date within a daterange widget incorrectly modified the start date. The fix ensures the correct date field is updated by accurately identifying the focused input element, improving data consistency and accuracy.
Original PR description
When a daterange widget is used (e.g., `deferred_start_date` coupled with `deferred_end_date`), focusing on the end date input was incorrectly modifying the start date field. This occurred because the `focusin` event was resolving the field name from the parent widget rather than the specific input focused. This commit updates `onFocusFieldWidget` and `getFullFieldName` to accept and evaluate the specific `event.target`. For `o_field_daterange` widgets, it now extracts the correct field name from the target's `data-field` attribute, ensuring the correct date field is updated. opw-6250048 Forward-Port-Of: odoo/enterprise#121033 Forward-Port-Of: odoo/enterprise#120684
This update fixes an error in the executive summary report that was incorrectly calculating the period length. Previously, it was measuring the gap between dates instead of the total number of days, leading to inaccurate metrics like Average Debtor Days. This change ensures the report accurately reflects the period length, improving the reliability of key business insights.
Original PR description
`_report_custom_engine_executive_summary_ndays` returned `date_to - date_from`, which is the gap between the two dates, not the count of days they span. For example April 2026-04-01 to 2026-04-30 will returned 29 instead of 30, making Average Debtor Days incorrect. Add +1 so the day count is inclusive of both endpoints, matching the rest of the report's date handling. opw-6215362 Forward-Port-Of: odoo/enterprise#118953
This update resolves an issue where POS orders with tracked products and GS1 barcodes would incorrectly report duplicate lot numbers. The fix ensures that the system correctly identifies existing lots based on their full GS1 name, even when the order uses a GS1 nomenclature. This prevents order validation failures and improves the reliability of the POS system.
Original PR description
When validating a POS order containing a product tracked by lots, an error about duplicate lot numbers is raised if the lot name can be read as a GS1 barcode (e.g. "10156": "10" is the GS1…
When validating a POS order containing a product tracked by lots, an error about duplicate lot numbers is raised if the lot name can be read as a GS1 barcode (e.g. "10156": "10" is the GS1 Application Identifier for Batch/Lot) while the company uses a GS1 nomenclature and the Barcode app is installed.
Steps to reproduce:
-------------------
* Install Barcode, POS and Inventory, enable lots & serial numbers
* Set the barcode nomenclature to "Default GS1 Nomenclature"
* Create a product tracked by lots and a lot named "10156" (any name starting with "10"), set an on-hand quantity for it with this lot
* On the "PoS Orders" operation type, enable both "Create New" and "Use Existing ones" for lots/serial numbers
* In POS, sell the product with lot "10156" and validate the order
> Observation:
The order fails to validate with a duplicate lot number error: the search for existing lots does not find lot "10156", so the POS tries to create it again and hits the unique constraint on stock.lot.
Why the fix:
------------
With stock_barcode installed, `stock.lot._search` preprocesses any domain on `name` with `_preprocess_gs1_search_args` so that scanned GS1 barcodes can match lot records. The lot names sent at order validation by `_create_production_lots_for_pos_order` are real lot names coming from the order lines, not scanned barcodes, but "10156" is decomposable as a valid GS1 lot ("10" + "156"), so the search domain became `('name', '=', '156')` and missed the existing lot. Skip the GS1 preprocessing in that search with the existing `skip_preprocess_gs1` context key, as already done in `product` and `stock`.
opw-6274744
Forward-Port-Of: odoo/odoo#269787This update resolves an issue where reducing the PO quantity after a partial receipt in a multi-step warehouse setup incorrectly calculated the remaining backorder demand. The fix ensures the system accurately adjusts quantities based on the actual receipt progress, preventing overestimation of required units.
Original PR description
**Issue** Reducing the PO quantity after performing a partial receipt, in multi-step receipts warehouse can incorrectly update the remaining receipt quantity. **Steps to reproduce** - Setup 2-route…
**Issue** Reducing the PO quantity after performing a partial receipt, in multi-step receipts warehouse can incorrectly update the remaining receipt quantity. **Steps to reproduce** - Setup 2-route receipt warehouse (Inventory > Configuration > Warehouse Management > Warehouses) - Create a PO for 35 units and confirm it - Click on receive products, set received quantity to 10 and create a backorder - Validate the next transfer - Go back to the PO and change the quantity to 20 - Check the receipt demand -> The backorder picking demand become 35 instead of 10 **Cause** Updating the quantity of a purchase order line, also updates the related picking: https://github.com/odoo/odoo/blob/5fc1e34d174f7f61d692d086d0ff65fbfc72b013/addons/purchase_stock/models/purchase_order_line.py#L120 It updates the picking associated to the backorder since the other one is done: https://github.com/odoo/odoo/blob/5fc1e34d174f7f61d692d086d0ff65fbfc72b013/addons/purchase_stock/models/purchase_order_line.py#L185-L187 https://github.com/odoo/odoo/blob/5fc1e34d174f7f61d692d086d0ff65fbfc72b013/addons/purchase_stock/models/purchase_order_line.py#L197 This ultimately calls: https://github.com/odoo/odoo/blob/5fc1e34d174f7f61d692d086d0ff65fbfc72b013/addons/purchase_stock/models/purchase_order_line.py#L228 To compute the new demand for the picking, it retrieves the `move_dest`: https://github.com/odoo/odoo/blob/5fc1e34d174f7f61d692d086d0ff65fbfc72b013/addons/purchase_stock/models/purchase_order_line.py#L240 To compute `qty_to_push`: https://github.com/odoo/odoo/blob/5fc1e34d174f7f61d692d086d0ff65fbfc72b013/addons/purchase_stock/models/purchase_order_line.py#L247-L249 However, since we are in a 2-route receipt setup, `move_dest` is the move from Input to stock for the done picking. Thus, `qty_to_push` is `20 - 10 = 10` instead of `20 - 35 = -15` **Solution** The previous logic assumes a pull flow, where downstream (move_dest_ids) quantities are always up-to-date and can be used as the source of truth to recompute demand. In push flows (e.g., multi-step receipts), this assumption does not hold. To fix this, we instead base the computation on the quantity of the current moves (qty) if nothing has to be attached. **Additional information** Known limitation: this does not address inconsistencies in return flows. When there're returns, units define in the pol and the one define in the sum of the picking can diverge, thus this pr won't fix that. opw-5512172 Forward-Port-Of: odoo/odoo#265583 Forward-Port-Of: odoo/odoo#248626
This update resolves a problem where order signing with Fiskaly failed after the company's API key was updated. The system now correctly resets the associated SCU and cash registers, ensuring seamless integration with the Fiskaly service. This prevents order processing disruptions for users.
Original PR description
When the Fiskaly API key/secret is changed, the company is bound to a new Fiskaly organization (owner). The SCU and cash registers stored on the company and POS configs were created under the previous owner and no longer exist for the new one, so signing orders fails with E_CASH_REGISTER_NOT_FOUND. Clear l10n_at_pos_company_scuid and each config's l10n_at_cash_regid together with the access token so they are recreated under the new organization on the next authentication. opw-6297695 Forward-Port-Of: odoo/enterprise#120839
This update significantly speeds up partner searches within the Point of Sale (POS) system. Previously, searching through a large number of partners was slow due to rendering all filtered results. Now, the system limits the displayed results to 200 and adjusts the search input's delay to reduce unnecessary calls, resulting in a smoother and faster user experience.
Original PR description
Before this commit, when high number of partners were loaded in the POS, searching for a partner was slow. The main issue was that all of the filtered partners based on the search query were being rendered, while in reality, if a query returns lots of results, the search query is not refined enough and the user is likely to type more characters to narrow down the search. So in this commit, we limit the number of rendered partners to 200, which is a reasonable number of results to display and does not cause performance issues. Moreover, the debounce time of the search input has been increased from 100ms to 500ms to further reduce the number of times the search function is called while the user is typing. opw-6215958 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#268658 Forward-Port-Of: odoo/odoo#264300
This update corrects a bug in the project scheduling system that incorrectly applied task buffers when rescheduling tasks with dependencies. The fix ensures that buffers are calculated and respected accurately, preventing tasks from being incorrectly shifted in the Gantt view. This improves the reliability of project timelines.
Original PR description
Steps to reproduce: ---------------------------------------- - Have the company calendar work from 9 to 17 on weekdays - In project gantt view, create tasks with dependencies like this: ``` - [Task…
Steps to reproduce:
----------------------------------------
- Have the company calendar work from 9 to 17 on weekdays
- In project gantt view, create tasks with dependencies like this:
```
- [Task 1] (June 08, 09:00 AM - 10:00 AM) ──┐
├─> [Task 3] (June 11, 09:00 AM - 10:00 AM)
- [Task 2] (June 10, 09:00 AM - 10:00 AM) ──┘
```
- Make sure the "Auto-Reschedule (Keep Buffer)" is selected
- Then reschedule task 1 to `(June 09, 09:00 AM - 10:00 AM)`
- Task 3 is rescheduled to `(June 15, 09:00 AM - 10:00 AM)`
**Expected behavior:**
To respect the buffers, task 3 should have been rescheduled to `(June 12, 09:00 AM - 10:00 AM)`:
- The buffer from task 1 is 23 working hours after `June 09, 10:00 AM` is `June 12, 09:00 AM`
- The buffer from task 2 doesn't affect the rescheduling.
Cause:
----------------------------------------
From `_web_gantt_update_next_candidates_dates()` we call `_get_new_dates()` with `seconds_between_tasks` being the duration of working hours between the end of task 1 and the start of task 3. We also call it with `first_possible_start_date_per_candidate` being the end of task 3 also depends on task 2.
Then `_get_new_dates()` counts the working hours from `first_possible_start_date_per_candidate` until it reaches `seconds_between_tasks`. Which means the buffer between task 1 and task 3 is actually applied between task 2 and task 3.
Solution:
----------------------------------------
The value contained in `first_possible_start_date_per_candidate` is irrelevant, the correct value to give to `_get_new_dates()` would be `compute_end_date` as we want to keep the buffer to be calculated from this datetime.
So we create a fake dictionary with only the task and `compute_end_date` and give it to `_get_new_dates()`.
The same logic applies when moving tasks backwards.
opw-5973597
Forward-Port-Of: odoo/enterprise#119161This update resolves an issue where changes to a company's country setting caused errors in Time Off management. The system now checks if there are existing Time Off records linked to a Time Off Type before allowing the company country to be modified, ensuring smoother operation.
Original PR description
When a Time Off Type is created, it inherits the country of the current company. If there are leaves or allocations created from this Time Off Type and the company's country is then changed, various…
When a Time Off Type is created, it inherits the country of the current company. If there are leaves or allocations created from this Time Off Type and the company's country is then changed, various parts of Time Off will throw access errors as the leaves and allocations are still tied to the former country. The goal of this PR is to constrain the company country from being changed unless there are no such leaves or allocations. **Steps to Reproduce on Runbot:** 1. Ensure the current company has a `country` set, e.g. "My Company (San Fransisco)" has country set to "United States". 2. Access Time Off as Mitchell Admin. 3. Create a new Time Off Type, for simplicity's sake without a need for allocation or approval, ex: "Gone Fishing". Note this Time Off Type will have the `country` set to the company country by default. 4. Take "Gone Fishing" time off. 5. Change or set blank the company's `country` value. 6. Ensure the record rules cache is flushed. 7. Try to access Time Off. opw-6206359, opw-6140496 closes #263950 Forward-Port-Of: odoo/odoo#263950
23 changes
Enhancements to existing features
This update incorporates support for the states of Tajikistan, Turkmenistan, Kyrgyzstan, Armenia, and Kazakhstan, aligning with international ISO 3166-2 standards. This change improves data accuracy and consistency by displaying state names instead of codes within address fields. This update is considered an improvement to the base system.
Original PR description
Added the states of Tajikistan, Turkmenistan, Kyrgyzstan, Armenian, and Kazakhstan to align with official ISO 3166-2 standards. In addition change their address format of the country to show state name instead of state code. task-6237707 task-6237595 task-6237473 task-6237440 task-6237284 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269091
Resolved issues and error corrections
This update fixes an error in the Luxembourg localization settings, ensuring the correct valuation account (301 Inventories of raw materials) is used instead of an expense account (60761 Merchandise). This ensures accurate financial reporting for Luxembourg businesses using Odoo.
Original PR description
**Problem:** Valuation account for luxembourg is currently 60761 Merchandise which is incorrect because it's an expense account. We should rather use a current asset account like 301 Inventories of raw materials **Steps to reproduce on a fresh db:** - create a new db with modules stock_account and accountant (without demo data) - On the 'fiscal localization setting' set the package as 'Luxembourg' and save - ativate the automatic accounting setting **Current Behavior:** The 'stock valuation account' appearing below the automatic accounting setting is : 60761 Merchandise **Expected behaviour:** It should be 301 Inventories of raw materials Forward-Port-Of: odoo/odoo#270048 Forward-Port-Of: odoo/odoo#269469
This update fixes a bug that prevented customers from removing automatically applied free shipping rewards from their online shopping carts. Previously, once a free shipping reward was added, it remained in the cart regardless of the customer's attempts to remove it. The fix ensures that free shipping rewards can be correctly removed, improving the customer experience.
Original PR description
Steps to produce: --- - Install `website_sale_loyalty`. - Go to `Website > ecommerece > Loyalty > DIscount & Loyalty`. - Create a new discount & loyalty program > set program type as `promotions`. -…
Steps to produce: --- - Install `website_sale_loyalty`. - Go to `Website > ecommerece > Loyalty > DIscount & Loyalty`. - Create a new discount & loyalty program > set program type as `promotions`. - Under Rewards, select `Free Shipping` as the reward type. - Create a product with a price of 1000 and publish it. - Add the product to the cart from the website. - Observe that free shipping is automatically applied on cart. - Attempt to remove the free shipping reward from the cart. Issue: --- - Free shipping (and similarly, free product rewards) cannot be removed from the cart once applied. Root cause: --- - At [1], the `website_sale_loyalty_delete` context is only passed when the reward type is `discount`. As a result, for free shipping and free product rewards, the context is not set. At [2], the order line is removed, but the reward is not added to `disabled_auto_rewards`. The `_auto_apply_rewards` method runs immediately afterward, detects the missing reward, and re-applies it automatically. Fix: --- - Since there are three reward types (discount, free shipping, and free product), the condition restricting the context to only discount rewards should be removed. [1]https://github.com/odoo/odoo/blob/5e90858fa91348f6aa33b4f8a246e77fbb8ea63f/addons/website_sale_loyalty/models/sale_order.py#L179 [2]https://github.com/odoo/odoo/blob/5e90858fa91348f6aa33b4f8a246e77fbb8ea63f/addons/website_sale_loyalty/models/sale_order_line.py#L15-L23 opw-6159288 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#261732
This update resolves an issue preventing users with Sales access from inserting data into Quotation templates through the spreadsheet management feature. The change adds a setting to ensure the necessary permissions are recognized, now enabling seamless data entry.
Original PR description
Current behavior before PR: - The `can_insert_in_spreadsheet` session flag was not set by the spreadsheet_sale_management module. - Users with proper Sale access rights still could not insert into Quotation templates. Desired behavior after PR is merged: - Added logic to set `can_insert_in_spreadsheet` when the module is installed and the user has the required access rights. Task: [5960761](https://www.odoo.com/odoo/project/2328/tasks/5960761) Forward-Port-Of: odoo/enterprise#120903 Forward-Port-Of: odoo/enterprise#108674
This update enhances the visual clarity of the account reconciliation search dialog. The changes remove text truncation and reposition date and balance fields, making it easier for users to quickly review key financial information. This improves the user experience for managing bank reconciliations.
Original PR description
This commit will remove the text-truncate from the reference so that we have it full. Also removing the align item so that the date and balance are on top. no task id Forward-Port-Of: odoo/enterprise#120963
This update resolves a problem that occurred during website upgrades when using custom themes. Specifically, if a theme's code wasn't found, the system would fail to load necessary snippets, causing an upgrade error. This fix ensures the system gracefully handles missing theme manifests, preventing upgrade failures.
Original PR description
After commit 3cbaad4, the theme manifest is now looked up to find addon snippets for the configurator. However, during an upgrade, if a website is configured with a third-party/custom theme whose…
After commit 3cbaad4,
the theme manifest is now looked up to find addon snippets for the configurator. However, during an upgrade, if a website is configured with a third-party/custom theme whose code is not present in the addons path, `Manifest.for_addon()` returns `None` and `_generate_primary_snippet_templates` raises an `AttributeError`:
```py
2026-06-16 03:27:47,155 24126 INFO db_4367932 odoo.modules.loading: loading website/views/new_page_template_templates.xml
2026-06-16 03:27:47,803 24126 WARNING db_4367932 odoo.modules.module: module theme_prime: manifest not found
2026-06-16 03:27:47,845 24126 WARNING db_4367932 odoo.modules.loading: Transient module states were reset
2026-06-16 03:27:47,846 24126 ERROR db_4367932 odoo.registry: Failed to load registry
2026-06-16 03:27:47,846 24126 CRITICAL db_4367932 odoo.service.server: Failed to initialize database `db_4367932`.
Traceback (most recent call last):
File "/home/odoo/src/odoo/19.0/odoo/tools/convert.py", line 605, in _tag_root
f(rec)
File "/home/odoo/src/odoo/19.0/odoo/tools/convert.py", line 273, in _tag_function
_eval_xml(self, rec, env)
File "/home/odoo/src/odoo/19.0/odoo/tools/convert.py", line 197, in _eval_xml
result = method(*args, **kwargs)
File "/home/odoo/src/odoo/19.0/addons/website/models/ir_module_module.py", line 704, in _generate_primary_snippet_templates
theme_addons = theme_manifest.get('configurator_snippets_addons', {})
AttributeError: 'NoneType' object has no attribute 'get'
```
We also get the missing manifest logs right before the error.
TBG-2781
[`_generate_primary_snippet_templates`]: https://github.com/odoo/odoo/blob/19.0/addons/website/models/ir_module_module.py#L700
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#270217This update fixes a visual issue where the shadow around buttons in the spreadsheet dashboard was obscured when they were focused. The change ensures that buttons have a clear, consistent appearance, improving the user experience and overall visual quality of the dashboard. This resolves a minor aesthetic problem.
Original PR description
the searchbar container cropped the shadow of its button when they were focused. Task-6303342 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
This update restores the display of the 'amount_total' field in the Orders to Invoice view. Previously, this field was hidden as it was a technical field used only for credit limit warnings. This change ensures accurate invoice totals are visible to users, improving financial reporting and reconciliation.
Original PR description
This is a backport of 953e92b8bd79a39042c78ca7183ae73f90906c1e. `amount_to_invoice` is a technical field only used to trigger the credit limit warning on invoices/SOs and is not supposed to be shown in views. 6feba7b018e15738be145ce48be9761481e9e11a opw-6271335
This update ensures that combo prices shown in the configurator dialog accurately reflect the order's currency. Previously, extra prices were displayed incorrectly when orders used pricelists in different currencies, leading to mismatched totals. This fix converts extra prices to the correct currency before displaying them, improving accuracy and the user experience.
Original PR description
Description of the issue/feature this PR addresses: In the combo configurator dialog, a combo item's extra_price and the price_extra of no_variant attributes are stored in the company/product…
Description of the issue/feature this PR addresses: In the combo configurator dialog, a combo item's extra_price and the price_extra of no_variant attributes are stored in the company/product currency but were sent to the front-end without conversion. When the order uses a pricelist in a different currency, the popup shows these extras at face value (e.g. an extra of USD 1700 appears as ARS 1700 instead of being converted). The sale order line itself already converts these extras, so the popup price and the actual line price didn't match. Current behavior before PR: _get_combo_item_data and _get_selected_ptavs_data return extra_price / price_extra raw, in the company currency. With a foreign-currency pricelist the combo configurator popup adds them 1-to-1 to the already-converted base price, displaying an incorrect total that doesn't match the resulting sale order line. Desired behavior after PR is merged: The controller converts extra_price and price_extra to the configurator's currency (via currency._convert()) before serializing them, so the popup shows the correct amounts in the pricelist currency and matches the price computed on the sale order line. A test (test_sale_combo_multicurrency.py) covers combo extra-price conversion with a foreign-currency pricelist. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269905 Forward-Port-Of: odoo/odoo#269386
This update ensures that preparation prints accurately reflect all items associated with a merged order. Previously, when moving an order to a table with existing items, the preparation print only showed the items on the new table. Now, it correctly displays all items from both orders, improving kitchen efficiency and order accuracy.
Original PR description
When moving an order (Order A) to a table that already has an order (Order B), the merged order only reprints Order B's products. The products from Order A are missing from the preparation reprint.…
When moving an order (Order A) to a table that already has an order (Order B), the merged order only reprints Order B's products. The products from Order A are missing from the preparation reprint. Steps to reproduce: ------------------- * Open a POS session on a Restaurant POS * Create an order (Order A) for Table 1 * Create a second order (Order B) for Table 2 * Transfer/Merge Order A to Table 2 * Reprint the preparation order > Observation: Only the products that were already on Table 2 (Order B) appear on the reprint. Products from Order A are missing. Why the fix: ------------ mergeOrders correctly transfers kitchen history (last_order_preparation_change.lines) via handlePreparationHistory, but does not update uiState.lastPrints on the destination order. The reprint button uses lastPrints.at(-1) when there are no pending changes, so it only shows the destination order's last print batch — ignoring the merged lines entirely. Implementation: After the merge loop, build a consolidated lastPrints entry from the destination order's last_order_preparation_change.lines (which now contains lines from both orders) and push it onto destOrder.uiState.lastPrints so that reprint reflects the full merged state. opw-6060684 Forward-Port-Of: odoo/odoo#256309
This update resolves an issue where POS orders with tracked products and GS1 barcodes would incorrectly report duplicate lot numbers. The fix ensures that the system properly recognizes and utilizes lot names formatted as GS1 barcodes, preventing validation errors and ensuring accurate inventory tracking when the Barcode app is enabled.
Original PR description
When validating a POS order containing a product tracked by lots, an error about duplicate lot numbers is raised if the lot name can be read as a GS1 barcode (e.g. "10156": "10" is the GS1…
When validating a POS order containing a product tracked by lots, an error about duplicate lot numbers is raised if the lot name can be read as a GS1 barcode (e.g. "10156": "10" is the GS1 Application Identifier for Batch/Lot) while the company uses a GS1 nomenclature and the Barcode app is installed.
Steps to reproduce:
-------------------
* Install Barcode, POS and Inventory, enable lots & serial numbers
* Set the barcode nomenclature to "Default GS1 Nomenclature"
* Create a product tracked by lots and a lot named "10156" (any name starting with "10"), set an on-hand quantity for it with this lot
* On the "PoS Orders" operation type, enable both "Create New" and "Use Existing ones" for lots/serial numbers
* In POS, sell the product with lot "10156" and validate the order
> Observation:
The order fails to validate with a duplicate lot number error: the search for existing lots does not find lot "10156", so the POS tries to create it again and hits the unique constraint on stock.lot.
Why the fix:
------------
With stock_barcode installed, `stock.lot._search` preprocesses any domain on `name` with `_preprocess_gs1_search_args` so that scanned GS1 barcodes can match lot records. The lot names sent at order validation by `_create_production_lots_for_pos_order` are real lot names coming from the order lines, not scanned barcodes, but "10156" is decomposable as a valid GS1 lot ("10" + "156"), so the search domain became `('name', '=', '156')` and missed the existing lot. Skip the GS1 preprocessing in that search with the existing `skip_preprocess_gs1` context key, as already done in `product` and `stock`.
opw-6274744
Forward-Port-Of: odoo/odoo#269787This update resolves a problem where order signing with Fiskaly failed after the company's API key was updated. The system now correctly resets and recreates essential data (SCU and cash registers) to ensure compatibility with the new Fiskaly organization, preventing order errors.
Original PR description
When the Fiskaly API key/secret is changed, the company is bound to a new Fiskaly organization (owner). The SCU and cash registers stored on the company and POS configs were created under the previous owner and no longer exist for the new one, so signing orders fails with E_CASH_REGISTER_NOT_FOUND. Clear l10n_at_pos_company_scuid and each config's l10n_at_cash_regid together with the access token so they are recreated under the new organization on the next authentication. opw-6297695 Forward-Port-Of: odoo/enterprise#120839
This update significantly speeds up partner searches within the Point of Sale module. Previously, searching through a large number of partners was slow due to rendering all results. Now, the system limits the displayed results to 200 and adjusts the search input's delay to reduce unnecessary calls, resulting in a smoother and faster user experience.
Original PR description
Before this commit, when high number of partners were loaded in the POS, searching for a partner was slow. The main issue was that all of the filtered partners based on the search query were being rendered, while in reality, if a query returns lots of results, the search query is not refined enough and the user is likely to type more characters to narrow down the search. So in this commit, we limit the number of rendered partners to 200, which is a reasonable number of results to display and does not cause performance issues. Moreover, the debounce time of the search input has been increased from 100ms to 500ms to further reduce the number of times the search function is called while the user is typing. opw-6215958 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#268658 Forward-Port-Of: odoo/odoo#264300
This update corrects a bug in how project task rescheduling respects time buffers. Previously, buffers were incorrectly applied between tasks, leading to inaccurate rescheduling times. This fix ensures that buffers are calculated and applied correctly, maintaining accurate project timelines.
Original PR description
Steps to reproduce: ---------------------------------------- - Have the company calendar work from 9 to 17 on weekdays - In project gantt view, create tasks with dependencies like this: ``` - [Task…
Steps to reproduce:
----------------------------------------
- Have the company calendar work from 9 to 17 on weekdays
- In project gantt view, create tasks with dependencies like this:
```
- [Task 1] (June 08, 09:00 AM - 10:00 AM) ──┐
├─> [Task 3] (June 11, 09:00 AM - 10:00 AM)
- [Task 2] (June 10, 09:00 AM - 10:00 AM) ──┘
```
- Make sure the "Auto-Reschedule (Keep Buffer)" is selected
- Then reschedule task 1 to `(June 09, 09:00 AM - 10:00 AM)`
- Task 3 is rescheduled to `(June 15, 09:00 AM - 10:00 AM)`
**Expected behavior:**
To respect the buffers, task 3 should have been rescheduled to `(June 12, 09:00 AM - 10:00 AM)`:
- The buffer from task 1 is 23 working hours after `June 09, 10:00 AM` is `June 12, 09:00 AM`
- The buffer from task 2 doesn't affect the rescheduling.
Cause:
----------------------------------------
From `_web_gantt_update_next_candidates_dates()` we call `_get_new_dates()` with `seconds_between_tasks` being the duration of working hours between the end of task 1 and the start of task 3. We also call it with `first_possible_start_date_per_candidate` being the end of task 3 also depends on task 2.
Then `_get_new_dates()` counts the working hours from `first_possible_start_date_per_candidate` until it reaches `seconds_between_tasks`. Which means the buffer between task 1 and task 3 is actually applied between task 2 and task 3.
Solution:
----------------------------------------
The value contained in `first_possible_start_date_per_candidate` is irrelevant, the correct value to give to `_get_new_dates()` would be `compute_end_date` as we want to keep the buffer to be calculated from this datetime.
So we create a fake dictionary with only the task and `compute_end_date` and give it to `_get_new_dates()`.
The same logic applies when moving tasks backwards.
opw-5973597
Forward-Port-Of: odoo/enterprise#119161This update resolves a bug that prevented users from successfully uploading cover images for Knowledge articles. The issue stemmed from a missing configuration within the article creation process, causing uploads to fail. This fix ensures a smooth and reliable cover image upload experience.
Original PR description
Steps to reproduce: 1. Install Knowledge. 2. Create an article. 3. Open the more actions menu. 4. Click "Add Cover". 5. Upload a cover image. Issue: - The upload crashes with the following traceback:…
Steps to reproduce: 1. Install Knowledge. 2. Create an article. 3. Open the more actions menu. 4. Click "Add Cover". 5. Upload a cover image. Issue: - The upload crashes with the following traceback: `Uncaught Promise > this.props.setAbortUploadsCallback is not a function` Cause: - `KnowledgeCoverSelector` extends the html_editor `ImageSelector`, whose upload flow registers an abort callback through setAbortUploadsCallback. The generic MediaDialog provides this callback, but KnowledgeCoverDialog renders KnowledgeCoverSelector directly and did not pass it. As a result, the inherited upload flow called a missing prop. Solution: - Pass setAbortUploadsCallback from KnowledgeCoverDialog to KnowledgeCoverSelector and abort pending uploads when the cover dialog is discarded. Alternative approach: - Make ImageSelector tolerate callers that do not provide setAbortUploadsCallback by calling it with optional chaining. opw-6176716 Forward-Port-Of: odoo/enterprise#120134 Forward-Port-Of: odoo/enterprise#116906
This update resolves an issue where users with limited accounting permissions incorrectly marked invoices as 'Fully Paid' after reconciling bank statements. The fix ensures accurate payment status tracking by correctly handling reconciliation processes and preventing the creation of unnecessary Account Receivable lines.
Original PR description
### Issue When you have an invoice partially paid via a method using an Outstanding Account, the payment can be kept open, leaving the invoice considered as partially paid If a user with only…
### Issue When you have an invoice partially paid via a method using an Outstanding Account, the payment can be kept open, leaving the invoice considered as partially paid If a user with only "Invoicing & Banks" rights tries to reconcile a Bank Statement with the same partner, amount, and the invoice name as the memo, the automatic reconciliation fails to properly match the payment Instead, the invoice is incorrectly considered as Fully Paid with an unwanted extra Account Receivable line added ### Cause When a new Bank Statement is created, `_try_auto_reconcile_statement_lines()` is called and matches the outstanding credit, which invokes `set_line_bank_statement_line()` In 19.0, this function creates a balancing line and triggers `move._compute_checked()` to update dependencies Especially `_compute_is_reconciled` But checked as been replaced by `review_state` This FW port will use an update on the `review_state` instead of the `checked` A user with "Invoicing & Banks" rights lacks the `account.group_account_user` group, meaning the move is not marked as `reviewed`, preventing dependencies from computing correctly Consequently, the statement line's `amount_residual` is not cleared and the line is not removed from `remaining_st_line_ids` Later in the process, `_try_auto_reconcile_statement_lines()` is called again with `with_user(SUPERUSER_ID)` Because the payment matching was never finalized in the previous step, the engine fallback matches against the full invoice, adding an incorrect Account Receivable line to close it ### Steps to reproduce - Install `accountant` - Go to Accounting / Configuration / Accounting / Journals - Open the Bank, under Incoming Payments tab, set the Manual Payment method's Outstanding Receipts account to 101403 Outstanding Receipts - Update the Demo user's accounting rights to Invoicing & Banks - Log in with the Demo user - Create and confirm an invoice for Acme Corporation (Amount: $1100) - Register a payment on the invoice (Amount: $500, Keep open) - Copy the invoice name - Open the Bank Reconciliation widget from the Accounting Dashboard - Create and add a new Bank Statement Line (Label: Invoice name, Partner: Acme Corporation, Amount: $500) Before the fix, an unexpected Account Receivable line is created and the invoice is marked as Fully Paid ### Notes Instead of processing the entire block under SUPERUSER_ID, which would hide the creator identity in logs and chatter, the context key `skip_account_review_check=True` is injected during the automated statement line reconciliation This safely bypasses the group check inside `_is_user_able_to_review` for this specific automated flow A fallback using `.with_user(SUPERUSER_ID)` is already implemented twice within the same `_try_auto_reconcile_statement_lines` method for this specific use case, but avoiding it here preserves data auditability opw-6077137
This update resolves an issue where users with limited accounting permissions incorrectly marked invoices as 'Fully Paid' after reconciling bank statements. The fix ensures accurate payment matching and prevents the creation of unwanted Account Receivable lines, maintaining proper invoice status.
Original PR description
### Issue When you have an invoice partially paid via a method using an Outstanding Account, the payment can be kept open, leaving the invoice considered as partially paid If a user with only…
### Issue When you have an invoice partially paid via a method using an Outstanding Account, the payment can be kept open, leaving the invoice considered as partially paid If a user with only "Invoicing & Banks" rights tries to reconcile a Bank Statement with the same partner, amount, and the invoice name as the memo, the automatic reconciliation fails to properly match the payment Instead, the invoice is incorrectly considered as Fully Paid with an unwanted extra Account Receivable line added ### Cause When a new Bank Statement is created, `_try_auto_reconcile_statement_lines()` is called and matches the outstanding credit, which invokes `set_line_bank_statement_line()` In 19.0, this function creates a balancing line and triggers `move._compute_checked()` to update dependencies Especially `_compute_is_reconciled` But checked as been replaced by `review_state` This FW port will use an update on the `review_state` instead of the `checked` A user with "Invoicing & Banks" rights lacks the `account.group_account_user` group, meaning the move is not marked as `reviewed`, preventing dependencies from computing correctly Consequently, the statement line's `amount_residual` is not cleared and the line is not removed from `remaining_st_line_ids` Later in the process, `_try_auto_reconcile_statement_lines()` is called again with `with_user(SUPERUSER_ID)` Because the payment matching was never finalized in the previous step, the engine fallback matches against the full invoice, adding an incorrect Account Receivable line to close it ### Steps to reproduce - Install `accountant` - Go to Accounting / Configuration / Accounting / Journals - Open the Bank, under Incoming Payments tab, set the Manual Payment method's Outstanding Receipts account to 101403 Outstanding Receipts - Update the Demo user's accounting rights to Invoicing & Banks - Log in with the Demo user - Create and confirm an invoice for Acme Corporation (Amount: $1100) - Register a payment on the invoice (Amount: $500, Keep open) - Copy the invoice name - Open the Bank Reconciliation widget from the Accounting Dashboard - Create and add a new Bank Statement Line (Label: Invoice name, Partner: Acme Corporation, Amount: $500) Before the fix, an unexpected Account Receivable line is created and the invoice is marked as Fully Paid ### Notes Instead of processing the entire block under SUPERUSER_ID, which would hide the creator identity in logs and chatter, the context key `skip_account_review_check=True` is injected during the automated statement line reconciliation This safely bypasses the group check inside `_is_user_able_to_review` for this specific automated flow A fallback using `.with_user(SUPERUSER_ID)` is already implemented twice within the same `_try_auto_reconcile_statement_lines` method for this specific use case, but avoiding it here preserves data auditability opw-6077137 Forward-Port-Of: odoo/enterprise#120125 Forward-Port-Of: odoo/enterprise#118023
This update corrects a previous issue where fully settled customers with past pay-later payments were incorrectly prevented from seeing their Customer Statements. The fix now checks for any existing pay-later payment lines, ensuring that customers can still access their statements regardless of their overall balance. This improves the user experience for all customers.
Original PR description
The override of _compute_has_moves was checking `total_due != 0` to set `has_moves` on for PoS pay_later customers. Once the customer is fully settled however, `total_due` is 0 and the check does not pass anymore, so `has_moves` goes back to `False` and the Customer Statement button hides for them, even though they had past pay_later payment lines. The fix is to check directly for any past pay_later `pos.payment` instead, which covers the cases where partner had used pay_later payment methods before, regardless if they have settled their total due or not. opw-6173760 Forward-Port-Of: odoo/enterprise#120911 Forward-Port-Of: odoo/enterprise#116536
This update resolves an issue where new tasks created through Timesheets Assistant Rules were incorrectly set as private. The fix ensures that tasks are always associated with a project, preventing this unintended behavior and improving task management clarity. This change enhances the user experience and data accuracy within the Timesheets module.
Original PR description
Steps to reproduce: - Install Timesheets and enable Timesheets Assistant. - Go to Timesheets -> Configuration -> Assistant Rules. - Open an existing rule or create a new one. - Select a Project and enter a new Task name. Issue 1: - Click Create. - The task is created as a private task, and the project is cleared. Issue 2: - Click Create and Edit. - Remove the project and save. - The task is saved as a private task. Cause: - When using Create, the `default_project_id` from the context is not applied, so the task is created as a private task. - When using Create and Edit, users can remove the prefilled project before saving, which also results in a private task. Fix: - Pass `default_project_id` and `form_view_ref='project.view_task_form_res_partner'` in the context. This prefills the project and makes it required when creating a task. task-6293306
This update corrects a visual issue where product images in the grid layout didn't always display in the correct left-to-right order. The change ensures that product images in the product image viewer now match the visual order as seen by the user, improving the overall product presentation.
Original PR description
This commit ensures product images follow their visual order in the product image viewer when using the grid layout. Steps to reproduce: - Open a product page with multiple images (or add Extra Media…
This commit ensures product images follow their visual order in the product image viewer when using the grid layout. Steps to reproduce: - Open a product page with multiple images (or add Extra Media to the product) - Change layout mode to "Grid" and click save - Click any image to open the product image viewer - Navigate between images Images do not follow the visual left-to-right order. This regression was introduced by [commit], which replaced the row-based grid with a column-first layout. As a result, `querySelectorAll` returns images in DOM order, which no longer matches the visual order. To fix this, images are now reordered based on their visual placement in the grid so navigation matches the order seen by the user. Images are traversed in visual left-to-right order while also accounting for varying image heights and multi-column alignment. [commit]: https://github.com/odoo/odoo/commit/9a3628b9735550bf8ecc2252ea1b7338f68ab966 task-[4364143](https://www.odoo.com/odoo/project/974/tasks/4364143) Forward-Port-Of: odoo/odoo#270513 Forward-Port-Of: odoo/odoo#254077
This update fixes an issue where delivery note costs weren't accurately calculated for products tracked across multiple lots. The fix ensures that the total sale price of all lots is used in the DDT cost calculation, preventing undercharging on multi-lot deliveries. This improves the accuracy of financial reporting.
Original PR description
Steps to reproduce: 1. Install Italian localization and l10n_it_stock_ddt 2. Create a product tracked by lots with a price of 100 3. Create two lots for that product, each with 5 in stock 4. Create a sale order for a quantity of 8 5. Confirm the sale order and validate the delivery 6. Print the delivery note Issue: Only the first lot's sale price is used in the DDT cost calculation (price = 500 instead of 800) Why this happens: The QWeb template used `move.move_line_ids[0].sale_price`, which only reads the sale_price of the first move line. When a delivery is split across multiple lots, each lot produces its own move line, so only the first is considered in the price calculation. opw-6244076 Forward-Port-Of: odoo/odoo#267757
This update fixes an issue where refund discounts were incorrectly calculated in the sales details report. When a refund line with a negative quantity was processed, the discount was inflated instead of canceled, leading to inaccurate discount reporting. This change ensures refunds are properly accounted for, providing more accurate sales data.
Original PR description
The sales details report computes a line discount as `original_price - price_subtotal_incl`. On a refund line the quantity is negative, so `original_price` is negative, while `price_subtotal_incl` is stored positive. Subtracting the two then inflates the discount instead of cancelling it, understating "discount_amount" by `2 * price_subtotal_incl` for every refunded discounted line. opw-6281752 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270592
This update resolves an issue where sending NFC-e invoices would halt the synchronization of point-of-sale (POS) data when IAP credits were exhausted. The change prevents a system block, ensuring POS data continues to update smoothly even when IAP credit limits are reached. This improves the reliability of the POS system.
Original PR description
When sending an NFC-e, tax calculation is done by calling Avatax through IAP. If the IAP account has no credits left, iap_jsonrpc() raises an InsufficientCreditError. opw-6290857 Forward-Port-Of: odoo/enterprise#120700
1 change
Resolved issues and error corrections
This update resolves an issue preventing users with Sales access from inserting data into Quotation templates through the spreadsheet management feature. The change adds a setting to ensure the necessary permissions are recognized, now enabling this functionality for authorized users. This improves usability for sales teams.
Original PR description
Current behavior before PR: - The `can_insert_in_spreadsheet` session flag was not set by the spreadsheet_sale_management module. - Users with proper Sale access rights still could not insert into Quotation templates. Desired behavior after PR is merged: - Added logic to set `can_insert_in_spreadsheet` when the module is installed and the user has the required access rights. Task: [5960761](https://www.odoo.com/odoo/project/2328/tasks/5960761) Forward-Port-Of: odoo/enterprise#120903 Forward-Port-Of: odoo/enterprise#108674
7 changes
New functionality added to Odoo
This update enhances how Odoo tracks document movements and trash actions. When documents are moved or sent to the trash, automated notifications are now logged, providing better visibility into document activity. This improves auditability and helps ensure documents are managed effectively.
Resolved issues and error corrections
This update fixes a bug that prevented customers from removing automatically applied free shipping rewards from their online shopping carts. Previously, the system would automatically re-apply these rewards, leading to unexpected charges. This change ensures customers have full control over their cart contents and accurately reflect the applied discounts.
Original PR description
Steps to produce: --- - Install `website_sale_loyalty`. - Go to `Website > ecommerece > Loyalty > DIscount & Loyalty`. - Create a new discount & loyalty program > set program type as `promotions`. -…
Steps to produce: --- - Install `website_sale_loyalty`. - Go to `Website > ecommerece > Loyalty > DIscount & Loyalty`. - Create a new discount & loyalty program > set program type as `promotions`. - Under Rewards, select `Free Shipping` as the reward type. - Create a product with a price of 1000 and publish it. - Add the product to the cart from the website. - Observe that free shipping is automatically applied on cart. - Attempt to remove the free shipping reward from the cart. Issue: --- - Free shipping (and similarly, free product rewards) cannot be removed from the cart once applied. Root cause: --- - At [1], the `website_sale_loyalty_delete` context is only passed when the reward type is `discount`. As a result, for free shipping and free product rewards, the context is not set. At [2], the order line is removed, but the reward is not added to `disabled_auto_rewards`. The `_auto_apply_rewards` method runs immediately afterward, detects the missing reward, and re-applies it automatically. Fix: --- - Since there are three reward types (discount, free shipping, and free product), the condition restricting the context to only discount rewards should be removed. [1]https://github.com/odoo/odoo/blob/5e90858fa91348f6aa33b4f8a246e77fbb8ea63f/addons/website_sale_loyalty/models/sale_order.py#L179 [2]https://github.com/odoo/odoo/blob/5e90858fa91348f6aa33b4f8a246e77fbb8ea63f/addons/website_sale_loyalty/models/sale_order_line.py#L15-L23 opw-6159288 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#261732
This update fixes an issue where the leave duration calculation incorrectly excluded public holidays when the 'Include Public Holidays as Working Day' option was selected. Now, the system accurately accounts for public holidays when determining the sandwich duration of leave requests, ensuring accurate leave calculations for employees.
Original PR description
Problem: When a time off type is configured with "Include Public Holidays as Working Day", the sandwich rule was still treating public holidays as non-working days. This caused the sandwiched weekend days to not be included in the leave duration. Example: Employee applies leave from May 15 (Friday, Public Holiday) to May 18 (Monday). Expected duration is 4 days since May 15 is a working day and May 16-17 (weekend) should be sandwiched. Instead, only 1 day was calculated. Fix: Now when "Include Public Holidays as Working Day" is enabled, the correct number of days are calculated in the sandwich rule. Also added a test case to verify that public holidays are correctly treated as working days during sandwich rule evaluation. Task-4570118 Forward-Port-Of: odoo/odoo#266624
This update corrects a technical issue within the Odoo accounting module for Denmark (l10n_dk) where an account was listed twice. Removing this duplication ensures accurate financial reporting and avoids potential errors in accounting processes. This change improves the reliability of the Danish accounting functionality.
Original PR description
In the list 'dk_coa_7630 ', the account has been used in the list twice. Removing the duplication from the list. [Reference](https://github.com/odoo/odoo/blob/17.0/addons/l10n_dk/migrations/1.4/end-migrate.py#L14) 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#270573
This update resolves an issue where the Odoo upgrade process could fail when updating account settings. Specifically, it prevents the system from incorrectly changing the 'reconcilable' flag on accounts with ongoing transactions, ensuring smoother and more reliable upgrades. This change was originally introduced in 18.0 and is now addressed.
Original PR description
### Context: Clients can have some existing accounts with `reconcilable` flag set as True. Some of these accounts also have partially reconcilated transactions. In Odoo 19.0, it is not authorized to…
### Context: Clients can have some existing accounts with `reconcilable` flag set as True. Some of these accounts also have partially reconcilated transactions. In Odoo 19.0, it is not authorized to toggle the `reconcilable` flag from True to False on accounts that contain partially reconcilated transactions. When the migration script `l10n_pl/migrations/2.1/end-migrate.py` is executed and try to update the CoA by adding/updating accounts, using the accounts in the file `l10n_pl/data/template/account.account-pl.csv`. This CSV file contains a reconcilable flag per account. ### Problem: Before this modification, the upgrade script was trying to update the CoA using `_load_data`, which try to overwrite the reconcilation flag of accounts in the client DB. A traceback occurs during the upgrade if an account's `reconcilable` flag is toggled to False while it still contains partially reconciled transactions. ### Solution: I have sanitized the dict `data` using the _pre_reload_data method. ### Notes: `_pre_reload_data` method sanitizes the dict `data` by avoiding the creation of duplicated accounts, the creation of duplicated fields for a given record, the toggling of the `reconcilable` flag, etcs. Back port of: https://github.com/odoo/odoo/commit/23ed5448a13255e68ff4d3ca87884c2ca2f97ba3 Reason: the issue was originally introduced in `18.0` (see https://github.com/odoo/odoo/commit/d5109a45d610916530b4b74cf3e2fa440ee7ed63) Related to: https://github.com/odoo/upgrade/pull/10266#discussion_r3402549250 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269794
This update resolves an issue where rapidly changing product quantities in the product catalog could lead to incorrect final quantities on Sale Order Lines. The fix ensures that quantity updates are processed sequentially, preventing data inconsistencies and guaranteeing accurate order totals. This improves the reliability of our sales process.
Original PR description
Fix a concurrency race condition in the product catalog where rapid quantity updates could result in incorrect final quantities on Sale Order Lines (SOL). Steps to produce: --- - We need a DB with…
Fix a concurrency race condition in the product catalog where rapid quantity updates could result in incorrect final quantities on Sale Order Lines (SOL). Steps to produce: --- - We need a DB with too many products. Also it might not be easy to reproduce the issue locally. Try runbot. - Open a Sale Order (SO) and open the Product Catalog. - Rapidly change or paste quantities (e.g., changing from 1 to 100) across multiple records very fast. - Return to the SO. Some lines intermittently retain an intermediate quantity (e.g., qty = 1) instead of the final entered value. Cause: --- - This is a concurrency issue. In the faulty cases, the `update_order_line_info` setting quantity to 1 takes a few seconds to resolve, while the update setting quantity to 100 resolves faster (around 200ms). This cause the SOL final quantity set to 1. Fix: --- - We can chain RPC calls to ensure that each request is completed before starting the next one. Backport of ef9554ad95d5e39ab7b550db0d39454373f99aed opw-6282877 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270844 Forward-Port-Of: odoo/odoo#269750
This update fixes an error in the delivery note (DDT) pricing calculation for products tracked by multiple lots. Previously, the system only used the price of the first lot, leading to inaccurate costs. This change ensures that the correct total price is reflected on the delivery note when a sale order is split across multiple lots.
Original PR description
Steps to reproduce: 1. Install Italian localization and l10n_it_stock_ddt 2. Create a product tracked by lots with a price of 100 3. Create two lots for that product, each with 5 in stock 4. Create a sale order for a quantity of 8 5. Confirm the sale order and validate the delivery 6. Print the delivery note Issue: Only the first lot's sale price is used in the DDT cost calculation (price = 500 instead of 800) Why this happens: The QWeb template used `move.move_line_ids[0].sale_price`, which only reads the sale_price of the first move line. When a delivery is split across multiple lots, each lot produces its own move line, so only the first is considered in the price calculation. opw-6244076 Forward-Port-Of: odoo/odoo#267757
16 changes
Enhancements to existing features
This update significantly improves the user experience of the sign request control panel by redesigning the interface and modernizing the underlying technology. The changes include a cleaner, more intuitive design, improved responsiveness, and a more efficient architecture for managing sign requests, resulting in a better user experience and streamlined workflow.
Original PR description
### Context The previous signer status header in the sign app relied on static QWeb injected from the backend and legacy DOM manipulation (jQuery/Vanilla JS) to handle state changes like "Resend"…
### Context The previous signer status header in the sign app relied on static QWeb injected from the backend and legacy DOM manipulation (jQuery/Vanilla JS) to handle state changes like "Resend" buttons. Additionally, the horizontal layout consumed too much screen space for documents with multiple signers, and users lacked quick visual context of the document's template tags. ### Summary of Changes This PR completely modernizes the Sign Request Control Panel header, moving it to a pure OWL architecture while vastly improving the UI/UX. 1. Architectural Refactoring (Legacy JS to OWL) Extracted the signer status wrapper into a modern, reusable OWL public component (SignerStatusBadge) that works flawlessly in both the backend and the portal. Ripped out fragile querySelector DOM manipulation for the "Resend" buttons. Button state (e.g., swapping a paper-plane icon for a success checkmark upon clicking) is now fully managed via reactive OWL state and clean RPC calls. 2. UI/UX Redesign Redesigned the signer list into a compact, native Odoo <Dropdown> when multiple signers are present, saving valuable horizontal space. Replaced bulky text buttons with clean, semantic Bootstrap/FontAwesome icons (check/clock/cross for status, paper-plane for actions) for a more modern SaaS feel. 3. Added Template Context Reused the existing SignTemplateHeaderTags component to display the original template's tags directly in the request control panel (in read-only mode). It gracefully hides itself if no tags or template exist. Task: 6109365
This update enhances Odoo's payment processing by tailoring it to the specific requirements of our key delivery partners like Shiprocket and Ups. This change allows for more accurate and reliable payments, streamlining the integration with these important services and improving overall transaction management. It’s a crucial step in supporting our growing delivery network.
Original PR description
See also: - https://github.com/odoo/odoo/pull/258854 - https://github.com/odoo/documentation/pull/18527
This update correctly classifies the Company Executive's meal voucher as a benefit, aligning with payroll best practices. Previously, it was treated as a salary input, which has now been corrected. This change ensures accurate reporting and compliance.
Original PR description
The meal voucher for the Company Executive is a salary input and should be a benefit. Task: 6303114
This update allows Field Service technicians to directly include subscription products with 'Accept One-Time' enabled within the Field Service product catalog during on-site visits. Previously, these subscriptions required a separate 'Extra Quotation' process. This streamlines the sales process and improves technician efficiency.
Original PR description
This commit makes subscription products with the "Accept One-Time" option enabled visible in the Field Service product catalog, so technicians can add them as one-time sales during on-site interventions without going through the Extra Quotation flow. task-5401135
This update enhances the task planning process within the Gantt view by adding a helper that prevents empty dialogs when creating new tasks. This improves the user experience and reduces potential confusion for users adding tasks from the Gantt chart.
Original PR description
This commit adds a custom no-content helper to the dialog opened when planning tasks from the Gantt view. task-3613715
Resolved issues and error corrections
This update resolves an issue where the car simulation information wasn't appearing correctly for Belgian employees with car orders. The fix ensures that car details and the simulation button are displayed properly by addressing a race condition in the salary calculation process. This improves the accuracy of salary configurations for employees with vehicles.
Original PR description
- Step to reproduce: open the salary configurator for a belgian employee with only a car to order linked to its version. Car info and simulation button are not appearing and the page reactivity is…
- Step to reproduce: open the salary configurator for a belgian employee with only a car to order linked to its version. Car info and simulation button are not appearing and the page reactivity is broken
- Cause:
- Broken page reactivity is due to a promise that never resolve in willStart super call because of race condition caused by overlapping calls to a debounced function
- Car model description is computed and displayed only when a new value is passed
- Simulation button is rendered only on select value change
- Solution:
- Execute `updateGross()` and `setUpBenefits()` sequentially in parent willStart to prevent overlapping salary recomputations during startup
- Implementing a condition that handle the case of the new car value being already set in the description computation function
- Triggering the new car change function in willStart so that the simulation button is rendered on page load
Task: 6241194
Forward-Port-Of: odoo/enterprise#120614
Forward-Port-Of: odoo/enterprise#118647This update resolves an issue preventing users with Sales access from inserting data into Quotation templates through the spreadsheet management feature. The change adds a setting to ensure the necessary permissions are granted, now enabling seamless data import.
Original PR description
Current behavior before PR: - The `can_insert_in_spreadsheet` session flag was not set by the spreadsheet_sale_management module. - Users with proper Sale access rights still could not insert into Quotation templates. Desired behavior after PR is merged: - Added logic to set `can_insert_in_spreadsheet` when the module is installed and the user has the required access rights. Task: [5960761](https://www.odoo.com/odoo/project/2328/tasks/5960761) Forward-Port-Of: odoo/enterprise#120903 Forward-Port-Of: odoo/enterprise#108674
This update fixes a visual glitch in the planning module where resource assignments would unexpectedly change when a form was refreshed. The change prevents the system from automatically assigning resources, ensuring users always see and manage their assigned resources as intended. This improves the user experience and data consistency.
Original PR description
Added a guard check in `default_get` to ensure the current user is only assigned when `resource_ids` is explicitly requested in the `fields` parameter. This prevents the web client from receiving unsolicited defaults and visually overwriting the explicitly saved resource when the browser is refreshed. Task: 6313650
This update fixes an issue where the Turkish e-Ledger CSV export was incorrectly using the trade registry number instead of the branch number (SUBENO). The change reads the correct branch number from the 'additional_identifiers' field, ensuring accurate reporting for Turkish businesses. This improves the reliability of financial data exports.
Original PR description
## Description of the issue/feature this PR addresses: The `BranchNumber` column in the Turkish e-Ledger CSV export was reading from `company.company_registry` (trade registry number) instead of the actual branch number. ## Current behavior before PR: `BranchNumber` is populated from `company.company_registry`, which is the trade registry number — not the branch number. ## Desired behavior after PR is merged: `BranchNumber` reads `TR_SUBE` from `additional_identifiers` on the company partner, which holds the branch number (SUBENO) following the migration from the old tag system. Community: https://github.com/odoo/odoo/pull/262960 task-5175991 I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This update adjusts the automated tests within the Odoo Enterprise system to account for a recent change in how purchase order links are presented as warnings during autocomplete. Previously, these warnings were interfering with the test execution process. This change ensures the tests accurately reflect the new functionality and provides a more reliable testing environment.
Original PR description
Introduced some `mute_logger` as feedback of the PO link to a bill is now given from the autocomplete through a warning, which would disrupt the tests. Task-6172792
This update resolves a potential conflict in how product categories are filtered within the Odoo Enterprise system. The change updates an internal XPath expression to ensure smoother operation of the POS category search. This improves the reliability of product filtering for point-of-sale transactions.
Original PR description
In this commit: ================ - Updated the XPath from `pos_categ_ids` to `group_by_pos_categ_ids` to avoid conflicts with the POS category search filter mechanism. Task-6185999 Related community PR: odoo/odoo#263208
This update corrects a bug where the timesheet timer was incorrectly adding extra seconds, leading to inaccurate overtime calculations and marking workdays as exceeding their limits. The fix ensures that the user-entered time is accurately saved, preventing the system from misinterpreting the duration and displaying incorrect overtime status.
Original PR description
Steps to reproduce --- 1. Set an employee to work 8 hours per day. 2. Open the timesheet timer in the systray, type a duration like 8:00 and save. 3. Open the My Timesheets grid for that day. The day…
Steps to reproduce --- 1. Set an employee to work 8 hours per day. 2. Open the timesheet timer in the systray, type a duration like 8:00 and save. 3. Open the My Timesheets grid for that day. The day is marked as overtime (yellow) even though only 8 hours were logged. Issue --- While the entry is open the timer keeps running and, every second, writes the elapsed time into unit_amount down to the second. https://github.com/odoo/enterprise/blob/a3c9295cf28b47f43233ac6a9f4106810842e37a/timesheet_grid/static/src/components/timesheet_timer_inline_form/timesheet_timer_inline_form.js#L89-L102 When the duration is set by hand, the save skips the usual rounding and keeps the value as it is. https://github.com/odoo/enterprise/blob/a3c9295cf28b47f43233ac6a9f4106810842e37a/timesheet_grid/static/src/components/timesheet_timer_inline_form/timesheet_timer_inline_form.js#L120-L132 So the clean 8:00 the user typed gets a few extra seconds from the next timer tick (8h 1s, stored as 8.000277) and is saved with them. The seconds are hidden in the HH:MM display but are enough to push the day above its working hours, so the grid paints it as overtime. This timer form is new in saas-19.2 (c3dac6ccdb5), which is why earlier versions are not affected. The fix ignores timer ticks once the duration has been set by hand, so the typed value is kept. opw-6180676 --- Forward-Port-Of: odoo/enterprise#120931 Forward-Port-Of: odoo/enterprise#120601
This update ensures the checksum for the EU IoT Scale Certification module aligns with recent changes in the core Odoo system. This adjustment is a necessary step to maintain compatibility and stability following an internal Odoo update. It’s a routine maintenance task.
Original PR description
This commit simply adjusts the checksum to align with the changes in odoo/odoo#269211. task-6273412 Forward-Port-Of: odoo/enterprise#120954
This update prevents the Timesheet Assistant from incorrectly matching events to projects or tasks with disabled timesheets. The change updates backend filtering and frontend data handling to ensure only active projects and tasks are considered, improving the accuracy of the assistant's recommendations. This resolves a potential issue where users could create confusing or inaccurate timesheet suggestions.
Original PR description
Currently, the Timesheet Assistant (ActivityWatch) can match events to projects or tasks that have timesheets disabled, either via Custom Rules or Historical Memory.
This commit resolves the issue across the entire pipeline:
- Backend: Updated `resolve_assistant_models_targets` to efficiently filter out records where `allow_timesheets` is False using a search domain.
- Frontend: Updated the `loadData` JS pipeline to intercept and wipe any project/task IDs rejected by the backend, ensuring they cleanly fall back into a single "Unmatched" group.
- Views: Added the `[('allow_timesheets', '=', True)]` domain to `project_id` and `task_id` fields in `aw.rule` views to prevent users from creating invalid rules.
Task: 6267401
Forward-Port-Of: odoo/enterprise#120490
Forward-Port-Of: odoo/enterprise#119403This update ensures the 'add-documents composer' dialog opens correctly within the PIP window when triggered from there. Previously, the dialog opened in the main window, causing a less intuitive user experience. This change improves usability and consistency within the Odoo interface.
Original PR description
**Current behavior before PR:** The add-documents composer action dialog opens in the main window when triggered from the PIP window because the required context is not provided. **Desired behavior after PR is merged:** The add-documents composer action dialog opens in the PIP window when triggered from the PIP window by providing the proper context to the dialog. task-[5149597](https://www.odoo.com/odoo/project/1519/tasks/5149597) Community: https://github.com/odoo/odoo/pull/264014
This update fixes an issue where helpdesk tickets automatically assigned to teams would fail due to a lack of access to employee time-off information. The system now checks employee time-off status using elevated permissions, ensuring accurate auto-assignment decisions. This prevents crashes and improves the reliability of the helpdesk ticketing process.
Original PR description
Picking the next assignee reads hr.leave to skip members who are off. A plain helpdesk user has no access to Time Off, so creating a ticket on an auto-assigned team crashed. Whom to assign is a system decision, so read the leaves in sudo. The bug was highlighted after https://github.com/odoo/odoo/pull/166359 which made the query crash rather than silently skipping unreadable. Fixes https://runbot.odoo.com/odoo/error/940352
2 changes
Resolved issues and error corrections
This update resolves an issue preventing users with Sales access from inserting data into Quotation templates through the spreadsheet management feature. The change adds a setting to ensure the necessary flag is enabled when the module is installed and the user has the correct permissions, improving usability.
Original PR description
Current behavior before PR: - The `can_insert_in_spreadsheet` session flag was not set by the spreadsheet_sale_management module. - Users with proper Sale access rights still could not insert into Quotation templates. Desired behavior after PR is merged: - Added logic to set `can_insert_in_spreadsheet` when the module is installed and the user has the required access rights. Task: [5960761](https://www.odoo.com/odoo/project/2328/tasks/5960761) Forward-Port-Of: odoo/enterprise#120903 Forward-Port-Of: odoo/enterprise#108674
This update significantly speeds up the generation of key financial reports, specifically the Trial Balance and Unallocated Earnings reports. By removing unnecessary checks and adding a new database index, the system processes these reports much faster, reducing processing times by up to 70%.
Original PR description
* By removing the access right check, we often are able to remove a `JOIN` on `account_move`. This `JOIN` was forcing the planner to take a worse plan and do a lot more work. * The new index allows…
* By removing the access right check, we often are able to remove a `JOIN` on `account_move`. This `JOIN` was forcing the planner to take a worse plan and do a lot more work.
* The new index allows many optimizations in the query plan, including removing the need for various `Scan`, `Nested Loop` nodes.
| | Unallocated Earnings | Trial Balance | Code Engine |
|------------|----------------------|---------------|-------------|
| Before | 38s | 22s | 15s |
| W/O access | 11s | 6s | 5s |
| & W Index | 6s | 6s | 7s |
Note that these timings are done on hot queries. The index would help in all the cases because it allows to avoid many Rows Removed by Filter.
Future work will also improve the cases where the domain contains:
```python
[
'|',
('account_id.include_initial_balance', '=', True),
('date', '>=', fiscalyear_start),
]
```
When splitting this in two queries, the new index also helps keeping the number of rows filtered very low.
________________________________________________________________________
# `_get_unallocated_earnings_lines`
## Before
```sql
SELECT account_move_line.company_id,
COALESCE(SUM((account_move_line.balance) * COALESCE(account_currency_table.rate, 1)), 0.0) AS balance,
COALESCE(SUM((account_move_line.debit) * COALESCE(account_currency_table.rate, 1)), 0.0) AS debit,
COALESCE(SUM((account_move_line.credit) * COALESCE(account_currency_table.rate, 1)), 0.0) AS credit
FROM "account_move_line" LEFT JOIN "account_account" AS "account_move_line__account_id" ON ("account_move_line"."account_id" = "account_move_line__account_id"."id") JOIN "account_move" AS "account_move_line__move_id" ON ("account_move_line"."move_id" = "account_move_line__move_id"."id")
JOIN (VALUES (1, CAST(NULL AS VARCHAR), CAST(NULL AS DATE), CAST(NULL AS DATE), 'current', 1)) AS account_currency_table(company_id, period_key, date_from, date_next, rate_type, rate)
ON account_move_line.company_id = account_currency_table.company_id
AND (account_currency_table.period_key = '_trial_balance_middle_periods' OR account_currency_table.period_key IS NULL)
WHERE (("account_move_line"."account_id" IS NOT NULL AND ("account_move_line__account_id"."account_type" IN ('equity_unaffected') OR split_part("account_move_line__account_id"."account_type", '_', 1) IN ('income', 'expense'))) AND "account_move_line"."company_id" IN (1) AND "account_move_line"."date" < '2026-01-01'::date AND "account_move_line"."date" <= '2026-04-30'::date AND "account_move_line"."display_type" NOT IN ('line_section', 'line_subsection', 'line_note') AND "account_move_line"."journal_id" IN (5, 181, 2, 407, 571, 570, 506, 169, 271, 167, 121, 6, 9, 241, 655, 623, 242, 63, 331, 123, 3, 18, 142, 4, 320, 170, 218, 236, 97, 143, 343, 64, 521, 660, 139, 344, 118, 119, 274, 179, 441, 619, 439, 89, 522, 91, 17, 8, 128, 135, 19, 10, 7, 180, 134, 124, 52, 53, 137, 98, 1, 96, 57) AND "account_move_line"."parent_state" IN ('posted')) AND (("account_move_line"."company_id" IN (1) OR "account_move_line"."company_id" IS NULL) OR ("account_move_line__move_id"."invoice_user_id" IN (1078415) OR "account_move_line__move_id"."partner_id" IN (1889993)))
GROUP BY account_move_line.company_id;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Finalize GroupAggregate (cost=1001.44..1409209.41 rows=1 width=100) (actual time=38461.802..38461.890 rows=1 loops=1)
-> Gather (cost=1001.44..1409209.38 rows=1 width=100) (actual time=38461.786..38461.877 rows=2 loops=1)
Workers Planned: 1
Workers Launched: 1
-> Partial GroupAggregate (cost=1.44..1408209.28 rows=1 width=100) (actual time=32388.314..32388.316 rows=1 loops=2)
-> Nested Loop (cost=1.44..1407952.19 rows=17138 width=18) (actual time=0.371..31450.650 rows=2829550 loops=2)
-> Nested Loop (cost=1.00..1371951.59 rows=38651 width=22) (actual time=0.286..20582.850 rows=2829550 loops=2)
-> Parallel Index Scan using account_account_pkey on account_account account_move_line__account_id (cost=0.29..1862.11 rows=66 width=4) (actual time=0.210..5.952 rows=1936 loops=2)
Filter: (((account_type)::text = 'equity_unaffected'::text) OR (split_part((account_type)::text, '_'::text, 1) = ANY ('{income,expense}'::text[])))
Rows Removed by Filter: 2294
-> Index Scan using account_move_line_account_id_date_idx on account_move_line (cost=0.72..20697.02 rows=6191 width=26) (actual time=5.337..10.510 rows=1462 loops=3872)
Index Cond: ((account_id = account_move_line__account_id.id) AND (account_id IS NOT NULL) AND (date < '2026-01-01'::date) AND (date <= '2026-04-30'::date))
Filter: ((company_id = 1) AND ((parent_state)::text = 'posted'::text) AND ((display_type)::text <> ALL ('{line_section,line_subsection,line_note}'::text[])) AND (journal_id = ANY ('{5,181,2,407,571,570,506,169,271,167,121,6,9,241,655,623,242,63,331,123,3,18,142,4,320,170,218,236,97,143,343,64,521,660,139,344,118,119,274,179,441,619,439,89,522,91,17,8,128,135,19,10,7,180,134,124,52,53,137,98,1,96,57}'::integer[])))
Rows Removed by Filter: 1570
-> Index Scan using account_move_pkey on account_move account_move_line__move_id (cost=0.43..0.92 rows=1 width=12) (actual time=0.004..0.004 rows=1 loops=5659099)
Index Cond: (id = account_move_line.move_id)
Filter: ((account_move_line.company_id = 1) OR (account_move_line.company_id IS NULL) OR (invoice_user_id = 1078415) OR (partner_id = 1889993))
Planning Time: 9.433 ms
Execution Time: 38461.932 ms
```
## Without access rights
```sql
SELECT account_move_line.company_id,
COALESCE(SUM((account_move_line.balance) * COALESCE(account_currency_table.rate, 1)), 0.0) AS balance,
COALESCE(SUM((account_move_line.debit) * COALESCE(account_currency_table.rate, 1)), 0.0) AS debit,
COALESCE(SUM((account_move_line.credit) * COALESCE(account_currency_table.rate, 1)), 0.0) AS credit
FROM "account_move_line" LEFT JOIN "account_account" AS "account_move_line__account_id" ON ("account_move_line"."account_id" = "account_move_line__account_id"."id")
JOIN (VALUES (1, CAST(NULL AS VARCHAR), CAST(NULL AS DATE), CAST(NULL AS DATE), 'current', 1)) AS account_currency_table(company_id, period_key, date_from, date_next, rate_type, rate)
ON account_move_line.company_id = account_currency_table.company_id
AND (account_currency_table.period_key = '_trial_balance_middle_periods' OR account_currency_table.period_key IS NULL)
WHERE (("account_move_line"."account_id" IS NOT NULL AND ("account_move_line__account_id"."account_type" IN ('equity_unaffected') OR split_part("account_move_line__account_id"."account_type", '_', 1) IN ('income', 'expense'))) AND "account_move_line"."company_id" IN (1) AND "account_move_line"."date" < '2026-01-01'::date AND "account_move_line"."date" <= '2026-04-30'::date AND "account_move_line"."display_type" NOT IN ('line_section', 'line_subsection', 'line_note') AND "account_move_line"."journal_id" IN (5, 181, 2, 407, 571, 570, 506, 169, 271, 167, 121, 6, 9, 241, 655, 623, 242, 63, 331, 123, 3, 18, 142, 4, 320, 170, 218, 236, 97, 143, 343, 64, 521, 660, 139, 344, 118, 119, 274, 179, 441, 619, 439, 89, 522, 91, 17, 8, 128, 135, 19, 10, 7, 180, 134, 124, 52, 53, 137, 98, 1, 96, 57) AND "account_move_line"."parent_state" IN ('posted'))
GROUP BY account_move_line.company_id;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
GroupAggregate (cost=0.72..2326905.01 rows=1 width=100) (actual time=11392.164..11392.166 rows=1 loops=1)
-> Nested Loop (cost=0.72..2325919.40 rows=65706 width=18) (actual time=0.753..9939.918 rows=5659099 loops=1)
-> Seq Scan on account_account account_move_line__account_id (cost=0.00..919.07 rows=112 width=4) (actual time=0.011..4.111 rows=3872 loops=1)
Filter: (((account_type)::text = 'equity_unaffected'::text) OR (split_part((account_type)::text, '_'::text, 1) = ANY ('{income,expense}'::text[])))
Rows Removed by Filter: 4589
-> Index Scan using account_move_line_account_id_date_idx on account_move_line (cost=0.72..20697.02 rows=6191 width=22) (actual time=1.180..2.469 rows=1462 loops=3872)
Index Cond: ((account_id = account_move_line__account_id.id) AND (account_id IS NOT NULL) AND (date < '2026-01-01'::date) AND (date <= '2026-04-30'::date))
Filter: ((company_id = 1) AND ((parent_state)::text = 'posted'::text) AND ((display_type)::text <> ALL ('{line_section,line_subsection,line_note}'::text[])) AND (journal_id = ANY ('{5,181,2,407,571,570,506,169,271,167,121,6,9,241,655,623,242,63,331,123,3,18,142,4,320,170,218,236,97,143,343,64,521,660,139,344,118,119,274,179,441,619,439,89,522,91,17,8,128,135,19,10,7,180,134,124,52,53,137,98,1,96,57}'::integer[])))
Rows Removed by Filter: 1570
Planning Time: 0.630 ms
Execution Time: 11392.193 ms
```
## With the new index
```sql
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
GroupAggregate (cost=0.72..1224754.13 rows=1 width=100) (actual time=6021.088..6021.090 rows=1 loops=1)
-> Nested Loop (cost=0.72..1223768.52 rows=65706 width=18) (actual time=0.140..4563.265 rows=5659099 loops=1)
-> Seq Scan on account_account account_move_line__account_id (cost=0.00..919.07 rows=112 width=4) (actual time=0.009..3.596 rows=3872 loops=1)
Filter: (((account_type)::text = 'equity_unaffected'::text) OR (split_part((account_type)::text, '_'::text, 1) = ANY ('{income,expense}'::text[])))
Rows Removed by Filter: 4589
-> Index Scan using idx_aml_reporting on account_move_line (cost=0.72..10856.39 rows=6191 width=22) (actual time=0.002..1.080 rows=1462 loops=3872)
Index Cond: ((company_id = 1) AND (account_id = account_move_line__account_id.id) AND (account_id IS NOT NULL) AND (date < '2026-01-01'::date) AND (date <= '2026-04-30'::date) AND ((parent_state)::text = 'posted'::text))
Filter: (journal_id = ANY ('{5,181,2,407,571,570,506,169,271,167,121,6,9,241,655,623,242,63,331,123,3,18,142,4,320,170,218,236,97,143,343,64,521,660,139,344,118,119,274,179,441,619,439,89,522,91,17,8,128,135,19,10,7,180,134,124,52,53,137,98,1,96,57}'::integer[]))
Rows Removed by Filter: 0
Planning Time: 1.258 ms
Execution Time: 6021.114 ms
```
# `_report_custom_engine_trial_balance`
## Before
```sql
SELECT
"account_move_line"."account_id" AS "groupby_key_account_id",
COALESCE(SUM((account_move_line.balance) * COALESCE(account_currency_table.rate, 1)), 0.0) AS balance,
COALESCE(SUM((account_move_line.debit) * COALESCE(account_currency_table.rate, 1)), 0.0) AS debit,
COALESCE(SUM((account_move_line.credit) * COALESCE(account_currency_table.rate, 1)), 0.0) AS credit
FROM "account_move_line" LEFT JOIN "account_account" AS "account_move_line__account_id" ON ("account_move_line"."account_id" = "account_move_line__account_id"."id") JOIN "account_move" AS "account_move_line__move_id" ON ("account_move_line"."move_id" = "account_move_line__move_id"."id")
JOIN (VALUES (1, CAST(NULL AS VARCHAR), CAST(NULL AS DATE), CAST(NULL AS DATE), 'current', 1)) AS account_currency_table(company_id, period_key, date_from, date_next, rate_type, rate)
ON account_move_line.company_id = account_currency_table.company_id
AND (account_currency_table.period_key = '_trial_balance_middle_periods' OR account_currency_table.period_key IS NULL)
WHERE ("account_move_line"."company_id" IN (1) AND "account_move_line"."date" <= '2026-04-30'::date AND "account_move_line"."display_type" NOT IN ('line_section', 'line_subsection', 'line_note') AND "account_move_line"."journal_id" IN (5, 181, 2, 407, 571, 570, 506, 169, 271, 167, 121, 6, 9, 241, 655, 623, 242, 63, 331, 123, 3, 18, 142, 4, 320, 170, 218, 236, 97, 143, 343, 64, 521, 660, 139, 344, 118, 119, 274, 179, 441, 619, 439, 89, 522, 91, 17, 8, 128, 135, 19, 10, 7, 180, 134, 124, 52, 53, 137, 98, 1, 96, 57) AND "account_move_line"."parent_state" IN ('posted') AND (("account_move_line"."account_id" IS NOT NULL AND ("account_move_line__account_id"."account_type" NOT IN ('equity_unaffected') AND (split_part("account_move_line__account_id"."account_type", '_', 1) NOT IN ('income', 'expense') OR split_part("account_move_line__account_id"."account_type", '_', 1) IS NULL))) OR "account_move_line"."date" >= '2026-01-01'::date)) AND (("account_move_line"."company_id" IN (1) OR "account_move_line"."company_id" IS NULL) OR ("account_move_line__move_id"."invoice_user_id" IN (1078415) OR "account_move_line__move_id"."partner_id" IN (1889993)))
GROUP BY "groupby_key_account_id";
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Finalize GroupAggregate (cost=3845774.51..3845891.36 rows=779 width=100) (actual time=22590.401..22953.611 rows=622 loops=1)
Group Key: account_move_line.account_id
-> Gather Merge (cost=3845774.51..3845864.10 rows=779 width=100) (actual time=22590.390..22952.586 rows=1116 loops=1)
Workers Planned: 1
Workers Launched: 1
-> Sort (cost=3844774.50..3844776.45 rows=779 width=100) (actual time=22562.645..22562.672 rows=558 loops=2)
Sort Key: account_move_line.account_id
Sort Method: quicksort Memory: 151kB
Worker 0: Sort Method: quicksort Memory: 148kB
-> Partial HashAggregate (cost=3844723.45..3844737.09 rows=779 width=100) (actual time=22562.148..22562.519 rows=558 loops=2)
Group Key: account_move_line.account_id
Batches: 1 Memory Usage: 1065kB
Worker 0: Batches: 1 Memory Usage: 1065kB
-> Hash Left Join (cost=1139137.12..3819476.57 rows=1442679 width=18) (actual time=10306.112..21012.284 rows=4067550 loops=2)
Hash Cond: (account_move_line.account_id = account_move_line__account_id.id)
Filter: (((account_move_line.account_id IS NOT NULL) AND ((account_move_line__account_id.account_type)::text <> 'equity_unaffected'::text) AND ((split_part((account_move_line__account_id.account_type)::text, '_'::text, 1) <> ALL ('{income,expense}'::text[])) OR (split_part((account_move_line__account_id.account_type)::text, '_'::text, 1) IS NULL))) OR (account_move_line.date >= '2026-01-01'::date))
Rows Removed by Filter: 2829550
-> Parallel Hash Join (cost=1138175.75..3814586.90 rows=1494949 width=22) (actual time=6131.481..19429.577 rows=6897099 loops=2)
Hash Cond: (account_move_line.move_id = account_move_line__move_id.id)
Join Filter: ((account_move_line.company_id = 1) OR (account_move_line.company_id IS NULL) OR (account_move_line__move_id.invoice_user_id = 1078415) OR (account_move_line__move_id.partner_id = 1889993))
-> Parallel Index Scan using idx_wan on account_move_line (cost=0.56..2667561.57 rows=3371483 width=30) (actual time=0.182..10737.129 rows=6897099 loops=2)
Index Cond: ((company_id = 1) AND (journal_id = ANY ('{5,181,2,407,571,570,506,169,271,167,121,6,9,241,655,623,242,63,331,123,3,18,142,4,320,170,218,236,97,143,343,64,521,660,139,344,118,119,274,179,441,619,439,89,522,91,17,8,128,135,19,10,7,180,134,124,52,53,137,98,1,96,57}'::integer[])))
Filter: ((date <= '2026-04-30'::date) AND ((parent_state)::text = 'posted'::text) AND ((display_type)::text <> ALL ('{line_section,line_subsection,line_note}'::text[])))
Rows Removed by Filter: 303744
-> Parallel Hash (cost=1085103.97..1085103.97 rows=4245697 width=12) (actual time=6087.945..6087.946 rows=4795563 loops=2)
Buckets: 16777216 Batches: 1 Memory Usage: 539328kB
-> Parallel Seq Scan on account_move account_move_line__move_id (cost=0.00..1085103.97 rows=4245697 width=12) (actual time=0.132..4719.857 rows=4795563 loops=2)
-> Hash (cost=855.61..855.61 rows=8461 width=16) (actual time=4.600..4.601 rows=8461 loops=2)
Buckets: 16384 Batches: 1 Memory Usage: 535kB
-> Seq Scan on account_account account_move_line__account_id (cost=0.00..855.61 rows=8461 width=16) (actual time=0.013..3.674 rows=8461 loops=2)
Planning Time: 29.548 ms
Execution Time: 22953.702 ms
```
## Without access rights
```sql
SELECT
"account_move_line"."account_id" AS "groupby_key_account_id",
COALESCE(SUM((account_move_line.balance) * COALESCE(account_currency_table.rate, 1)), 0.0) AS balance,
COALESCE(SUM((account_move_line.debit) * COALESCE(account_currency_table.rate, 1)), 0.0) AS debit,
COALESCE(SUM((account_move_line.credit) * COALESCE(account_currency_table.rate, 1)), 0.0) AS credit
FROM "account_move_line" LEFT JOIN "account_account" AS "account_move_line__account_id" ON ("account_move_line"."account_id" = "account_move_line__account_id"."id")
JOIN (VALUES (1, CAST(NULL AS VARCHAR), CAST(NULL AS DATE), CAST(NULL AS DATE), 'current', 1)) AS account_currency_table(company_id, period_key, date_from, date_next, rate_type, rate)
ON account_move_line.company_id = account_currency_table.company_id
AND (account_currency_table.period_key = '_trial_balance_middle_periods' OR account_currency_table.period_key IS NULL)
WHERE ("account_move_line"."company_id" IN (1) AND "account_move_line"."date" <= '2026-04-30'::date AND "account_move_line"."display_type" NOT IN ('line_section', 'line_subsection', 'line_note') AND "account_move_line"."journal_id" IN (5, 181, 2, 407, 571, 570, 506, 169, 271, 167, 121, 6, 9, 241, 655, 623, 242, 63, 331, 123, 3, 18, 142, 4, 320, 170, 218, 236, 97, 143, 343, 64, 521, 660, 139, 344, 118, 119, 274, 179, 441, 619, 439, 89, 522, 91, 17, 8, 128, 135, 19, 10, 7, 180, 134, 124, 52, 53, 137, 98, 1, 96, 57) AND "account_move_line"."parent_state" IN ('posted') AND (("account_move_line"."account_id" IS NOT NULL AND ("account_move_line__account_id"."account_type" NOT IN ('equity_unaffected') AND (split_part("account_move_line__account_id"."account_type", '_', 1) NOT IN ('income', 'expense') OR split_part("account_move_line__account_id"."account_type", '_', 1) IS NULL))) OR "account_move_line"."date" >= '2026-01-01'::date))
GROUP BY "groupby_key_account_id";
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Finalize GroupAggregate (cost=2875894.51..2876117.19 rows=779 width=100) (actual time=5505.661..5806.173 rows=622 loops=1)
Group Key: account_move_line.account_id
-> Gather Merge (cost=2875894.51..2876076.29 rows=1558 width=100) (actual time=5505.647..5804.748 rows=1622 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Sort (cost=2874894.49..2874896.43 rows=779 width=100) (actual time=5491.557..5491.581 rows=541 loops=3)
Sort Key: account_move_line.account_id
Sort Method: quicksort Memory: 150kB
Worker 0: Sort Method: quicksort Memory: 144kB
Worker 1: Sort Method: quicksort Memory: 143kB
-> Partial HashAggregate (cost=2874843.44..2874857.07 rows=779 width=100) (actual time=5491.055..5491.420 rows=541 loops=3)
Group Key: account_move_line.account_id
Batches: 1 Memory Usage: 1065kB
Worker 0: Batches: 1 Memory Usage: 1065kB
Worker 1: Batches: 1 Memory Usage: 1065kB
-> Hash Left Join (cost=962.09..2834512.35 rows=2304634 width=18) (actual time=6.720..4466.785 rows=2711700 loops=3)
Hash Cond: (account_move_line.account_id = account_move_line__account_id.id)
Filter: (((account_move_line.account_id IS NOT NULL) AND ((account_move_line__account_id.account_type)::text <> 'equity_unaffected'::text) AND ((split_part((account_move_line__account_id.account_type)::text, '_'::text, 1) <> ALL ('{income,expense}'::text[])) OR (split_part((account_move_line__account_id.account_type)::text, '_'::text, 1) IS NULL))) OR (account_move_line.date >= '2026-01-01'::date))
Rows Removed by Filter: 1886366
-> Parallel Index Scan using account_move_line__company_id_index on account_move_line (cost=0.72..2827275.65 rows=2388134 width=22) (actual time=0.080..3433.378 rows=4598066 loops=3)
Index Cond: (company_id = 1)
Filter: ((date <= '2026-04-30'::date) AND ((parent_state)::text = 'posted'::text) AND ((display_type)::text <> ALL ('{line_section,line_subsection,line_note}'::text[])) AND (journal_id = ANY ('{5,181,2,407,571,570,506,169,271,167,121,6,9,241,655,623,242,63,331,123,3,18,142,4,320,170,218,236,97,143,343,64,521,660,139,344,118,119,274,179,441,619,439,89,522,91,17,8,128,135,19,10,7,180,134,124,52,53,137,98,1,96,57}'::integer[])))
Rows Removed by Filter: 202669
-> Hash (cost=855.61..855.61 rows=8461 width=16) (actual time=6.531..6.531 rows=8461 loops=3)
Buckets: 16384 Batches: 1 Memory Usage: 535kB
-> Seq Scan on account_account account_move_line__account_id (cost=0.00..855.61 rows=8461 width=16) (actual time=0.022..5.568 rows=8461 loops=3)
Planning Time: 10.803 ms
Execution Time: 5806.252 ms
```
## With the new index
```sql
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Finalize GroupAggregate (cost=3804515.40..3804572.57 rows=200 width=100) (actual time=6225.615..6565.399 rows=622 loops=1)
Group Key: account_move_line.account_id
-> Gather Merge (cost=3804515.40..3804562.07 rows=400 width=100) (actual time=6225.605..6564.588 rows=853 loops=1)
Workers Planned 2
Workers Launched: 2
-> Sort (cost=3803515.37..3803515.87 rows=200 width=100) (actual time=6211.591..6211.605 rows=284 loops=3)
Sort Key: account_move_line.account_id
Sort Method: quicksort Memory: 71kB
Worker 0: Sort Method: quicksort Memory: 68kB
Worker 1: Sort Method: quicksort Memory: 126kB
-> Partial HashAggregate (cost=3803504.23..3803507.73 rows=200 width=100) (actual time=6211.303..6211.503 rows=284 loops=3)
Group Key: account_move_line.account_id
Batches: 1 Memory Usage: 288kB
Worker 0: Batches: 1 Memory Usage: 288kB
Worker 1: Batches: 1 Memory Usage: 569kB
-> Parallel Append (cost=7.68..3778010.68 rows=2549355 width=100) (actual time=472.875..5759.570 rows=2937896 loops=3)
-> Parallel Bitmap Heap Scan on account_move_line (cost=380080.85..946147.41 rows=259602 width=100) (actual time=1418.158..3029.272 rows=1054880 loops=1)
Recheck Cond: ((journal_id = ANY ('{5,181,2,407,571,570,506,169,271,167,121,6,9,241,655,623,242,63,331,123,3,18,142,4,320,170,218,236,97,143,343,64,521,660,139,344,118,119,274,179,441,619,439,89,522,91,17,8,128,135,19,10,7,180,134,124,52,53,137,98,1,96,57}'::integer[])) AND (company_id = 1) AND (date <= '2026-04-30'::date) AND (date >= '2026-01-01'::date) AND ((parent_state)::text = 'posted'::text) AND ((display_type)::text <> ALL ('{line_section,line_subsection,line_note}'::text[])))
-> BitmapAnd (cost=380080.69..380080.69 rows=623044 width=0) (actual time=1321.505..1321.506 rows=0 loops=1)
-> Bitmap Index Scan on account_move_line__journal_id_index (cost=0.00..158146.78 rows=14398682 width=0) (actual time=1100.520..1100.520 rows=14401687 loops=1)
Index Cond: (journal_id = ANY ('{5,181,2,407,571,570,506,169,271,167,121,6,9,241,655,623,242,63,331,123,3,18,142,4,320,170,218,236,97,143,343,64,521,660,139,344,118,119,274,179,441,619,439,89,522,91,17,8,128,135,19,10,7,180,134,124,52,53,137,98,1,96,57}'::integer[]))
-> Bitmap Index Scan on idx_aml_reporting (cost=0.00..221622.14 rows=1389653 width=0) (actual time=161.288..161.288 rows=1054880 loops=1)
Index Cond: ((company_id = 1) AND (date <= '2026-04-30'::date) AND (date >= '2026-01-01'::date) AND ((parent_state)::text = 'posted'::text))
-> Merge Join (cost=7.68..2819116.49 rows=2289753 width=100) (actual time=44.892..4575.995 rows=2586269 loops=3)
Merge Cond: (account_move_line_1.account_id = account_move_line__account_id.id)
-> Parallel Index Scan using idx_aml_reporting on account_move_line account_move_line_1 (cost=0.72..2853823.83 rows=2320470 width=18) (actual time=0.042..3730.932 rows=4598051 loops=3)
Index Cond: ((company_id = 1) AND (account_id IS NOT NULL) AND (date <= '2026-04-30'::date) AND ((parent_state)::text = 'posted'::text))
Filter: (journal_id = ANY ('{5,181,2,407,571,570,506,169,271,167,121,6,9,241,655,623,242,63,331,123,3,18,142,4,320,170,218,236,97,143,343,64,521,660,139,344,118,119,274,179,441,619,439,89,522,91,17,8,128,135,19,10,7,180,134,124,52,53,137,98,1,96,57}'::integer[]))
Rows Removed by Filter: 173
-> Index Scan using account_account_pkey on account_account account_move_line__account_id (cost=0.29..1071.81 rows=8349 width=4) (actual time=0.043..3.777 rows=4474 loops=3)
Filter: (((account_type)::text <> 'equity_unaffected'::text) AND ((split_part((account_type)::text, '_'::text, 1) <> ALL ('{income,expense}'::text[])) OR (split_part((account_type)::text, '_'::text, 1) IS NULL)))
Rows Removed by Filter: 3831
Planning Time: 1.112 ms
Execution Time: 6565.487 ms
```
# `_compute_formula_batch_with_engine_account_codes`
## Before
```sql
SELECT
account_move_line.account_id AS account_id,
SUM((account_move_line.balance) * COALESCE(account_currency_table.rate, 1)) AS sum,
COUNT(account_move_line.id) AS aml_count
FROM "account_move_line" JOIN "account_move" AS "account_move_line__move_id" ON ("account_move_line"."move_id" = "account_move_line__move_id"."id")
JOIN (VALUES (1, CAST(NULL AS VARCHAR), CAST(NULL AS DATE), CAST(NULL AS DATE), 'current', 1)) AS account_currency_table(company_id, period_key, date_from, date_next, rate_type, rate)
ON account_move_line.company_id = account_currency_table.company_id
AND (account_currency_table.period_key = 'None_2026-06-10' OR account_currency_table.period_key IS NULL)
WHERE ("account_move_line"."company_id" IN (1) AND "account_move_line"."date" <= '2026-06-10'::date AND "account_move_line"."display_type" NOT IN ('line_section', 'line_subsection', 'line_note') AND "account_move_line"."journal_id" IN (5, 181, 2, 407, 571, 570, 506, 169, 271, 167, 121, 6, 9, 241, 655, 623, 242, 63, 331, 123, 3, 18, 142, 4, 320, 170, 218, 236, 97, 143, 343, 64, 521, 660, 139, 344, 118, 119, 274, 179, 441, 619, 439, 89, 522, 91, 17, 8, 128, 135, 19, 10, 7, 180, 134, 124, 52, 53, 137, 98, 1, 96, 57) AND "account_move_line"."parent_state" IN ('posted')) AND (("account_move_line"."company_id" IN (1) OR "account_move_line"."company_id" IS NULL) OR ("account_move_line__move_id"."invoice_user_id" IN (1078415) OR "account_move_line__move_id"."partner_id" IN (1889993)))
GROUP BY account_move_line.account_id;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Finalize GroupAggregate (cost=4119792.83..4119999.93 rows=779 width=44) (actual time=15387.279..15724.790 rows=839 loops=1)
Group Key: account_move_line.account_id
-> Gather Merge (cost=4119792.83..4119974.61 rows=1558 width=44) (actual time=15387.268..15723.982 rows=2202 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Sort (cost=4118792.81..4118794.76 rows=779 width=44) (actual time=15360.304..15360.333 rows=734 loops=3)
Sort Key: account_move_line.account_id
Sort Method: quicksort Memory: 102kB
Worker 0: Sort Method: quicksort Memory: 99kB
Worker 1: Sort Method: quicksort Memory: 99kB
-> Partial HashAggregate (cost=4118745.66..4118755.40 rows=779 width=44) (actual time=15359.911..15360.156 rows=734 loops=3)
Group Key: account_move_line.account_id
Batches: 1 Memory Usage: 553kB
Worker 0: Batches: 1 Memory Usage: 553kB
Worker 1: Batches: 1 Memory Usage: 553kB
-> Parallel Hash Join (cost=1138175.34..4107895.66 rows=1085000 width=14) (actual time=4293.304..14318.483 rows=4681102 loops=3)
Hash Cond: (account_move_line.move_id = account_move_line__move_id.id)
Join Filter: ((account_move_line.company_id = 1) OR (account_move_line.company_id IS NULL) OR (account_move_line__move_id.invoice_user_id = 1078415) OR (account_move_line__move_id.partner_id = 1889993))
-> Parallel Seq Scan on account_move_line (cost=0.16..2963297.25 rows=2446945 width=22) (actual time=0.113..8270.809 rows=4681102 loops=3)
Filter: ((date <= '2026-06-10'::date) AND (company_id = 1) AND ((parent_state)::text = 'posted'::text) AND ((display_type)::text <> ALL ('{line_section,line_subsection,line_note}'::text[])) AND (journal_id = ANY ('{5,181,2,407,571,570,506,169,271,167,121,6,9,241,655,623,242,63,331,123,3,18,142,4,320,170,218,236,97,143,343,64,521,660,139,344,118,119,274,179,441,619,439,89,522,91,17,8,128,135,19,10,7,180,134,124,52,53,137,98,1,96,57}'::integer[])))
Rows Removed by Filter: 6023968
-> Parallel Hash (cost=1085103.97..1085103.97 rows=4245697 width=12) (actual time=4266.392..4266.392 rows=3197042 loops=3)
Buckets: 16777216 Batches: 1 Memory Usage: 539360kB
-> Parallel Seq Scan on account_move account_move_line__move_id (cost=0.00..1085103.97 rows=4245697 width=12) (actual time=0.059..3265.827 rows=3197042 loops=3)
Planning Time: 6.028 ms
Execution Time: 15724.870 ms
```
## Without access rights
```sql
SELECT
account_move_line.account_id AS account_id,
SUM((account_move_line.balance) * COALESCE(account_currency_table.rate, 1)) AS sum,
COUNT(account_move_line.id) AS aml_count
FROM "account_move_line"
JOIN (VALUES (1, CAST(NULL AS VARCHAR), CAST(NULL AS DATE), CAST(NULL AS DATE), 'current', 1)) AS account_currency_table(company_id, period_key, date_from, date_next, rate_type, rate)
ON account_move_line.company_id = account_currency_table.company_id
AND (account_currency_table.period_key = 'None_2026-06-10' OR account_currency_table.period_key IS NULL)
WHERE ("account_move_line"."company_id" IN (1) AND "account_move_line"."date" <= '2026-06-10'::date AND "account_move_line"."display_type" NOT IN ('line_section', 'line_subsection', 'line_note') AND "account_move_line"."journal_id" IN (5, 181, 2, 407, 571, 570, 506, 169, 271, 167, 121, 6, 9, 241, 655, 623, 242, 63, 331, 123, 3, 18, 142, 4, 320, 170, 218, 236, 97, 143, 343, 64, 521, 660, 139, 344, 118, 119, 274, 179, 441, 619, 439, 89, 522, 91, 17, 8, 128, 135, 19, 10, 7, 180, 134, 124, 52, 53, 137, 98, 1, 96, 57) AND "account_move_line"."parent_state" IN ('posted'))
GROUP BY account_move_line.account_id;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Finalize GroupAggregate (cost=2852792.27..2852999.37 rows=779 width=44) (actual time=4663.910..4980.515 rows=839 loops=1)
Group Key: account_move_line.account_id
-> Gather Merge (cost=2852792.27..2852974.05 rows=1558 width=44) (actual time=4663.900..4979.680 rows=2214 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Sort (cost=2851792.25..2851794.20 rows=779 width=44) (actual time=4650.874..4650.905 rows=738 loops=3)
Sort Key: account_move_line.account_id
Sort Method: quicksort Memory: 103kB
Worker 0: Sort Method: quicksort Memory: 99kB
Worker 1: Sort Method: quicksort Memory: 100kB
-> Partial HashAggregate (cost=2851745.10..2851754.84 rows=779 width=44) (actual time=4650.446..4650.708 rows=738 loops=3)
Group Key: account_move_line.account_id
Batches: 1 Memory Usage: 553kB
Worker 0: Batches: 1 Memory Usage: 553kB
Worker 1: Batches: 1 Memory Usage: 553kB
-> Parallel Index Scan using account_move_line__company_id_index on account_move_line (cost=0.72..2827275.65 rows=2446945 width=14) (actual time=0.147..3636.824 rows=4681102 loops=3)
Index Cond: (company_id = 1)
Filter: ((date <= '2026-06-10'::date) AND ((parent_state)::text = 'posted'::text) AND ((display_type)::text <> ALL ('{line_section,line_subsection,line_note}'::text[])) AND (journal_id = ANY ('{5,181,2,407,571,570,506,169,271,167,121,6,9,241,655,623,242,63,331,123,3,18,142,4,320,170,218,236,97,143,343,64,521,660,139,344,118,119,274,179,441,619,439,89,522,91,17,8,128,135,19,10,7,180,134,124,52,53,137,98,1,96,57}'::integer[])))
Rows Removed by Filter: 119633
Planning Time: 0.423 ms
Execution Time: 4980.584 ms
```
## With the new index
```sql
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Finalize GroupAggregate (cost=1000.74..2857192.00 rows=779 width=44) (actual time=3339.293..7476.511 rows=839 loops=1)
Group Key: account_move_line.account_id
-> Gather Merge (cost=1000.74..2857166.68 rows=1558 width=44) (actual time=3339.266..7475.827 rows=1136 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial GroupAggregate (cost=0.72..2855986.83 rows=779 width=44) (actual time=0.439..4673.216 rows=379 loops=3)
Group Key: account_move_line.account_id
-> Parallel Index Scan using idx_aml_reporting on account_move_line (cost=0.72..2831507.64 rows=2446945 width=14) (actual time=0.055..3919.782 rows=4681102 loops=3)
Index Cond: ((company_id = 1) AND (date <= '2026-06-10'::date) AND ((parent_state)::text = 'posted'::text))
Filter: (journal_id = ANY ('{5,181,2,407,571,570,506,169,271,167,121,6,9,241,655,623,242,63,331,123,3,18,142,4,320,170,218,236,97,143,343,64,521,660,139,344,118,119,274,179,441,619,439,89,522,91,17,8,128,135,19,10,7,180,134,124,52,53,137,98,1,96,57}'::integer[]))
Rows Removed by Filter: 173
Planning Time: 0.485 ms
Execution Time: 7476.576 ms
```9 changes
Enhancements to existing features
This update simplifies how the account_edi_ubl_cii report templates are identified, reducing potential conflicts with other Odoo modules. Previously, a fragile inheritance method caused issues when updating templates. This change creates a more reliable and stable system for identifying the necessary updates.
Original PR description
**Description of the issue/feature this PR addresses:** As the new `xpath` is expecting a very specific type of `t-if` which is possibly changed in other templates of third parties or even Odoo itself which do not depend on this module, we take a more robust approach to identify the block **Current behavior before PR:** Issues with inherited views outside the dependency tree (because of primary=True) **Desired behavior after PR is merged:** Less friction and smoother identifier of the needed diff Info: @wt-io-it --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This update enhances the error messages related to leave validity, particularly when creating public holidays. The improved message will significantly help customers and our support team quickly identify and resolve leave allocation problems, reducing troubleshooting time. This change improves the user experience and support efficiency.
Original PR description
The current message is pretty useless as of now when a lot of leaves are being written to, notably when creating a public holiday, which sets the state of all the leaves overlapping the public holiday's day to be reevaluated, and if an error occurs, you have to go through every employee's leave allocation and leaves taken to hopefully find one who might have to many days taken/not enough allocated. This extra information will be a huge QOL improvement, for the customer who will be able to troubleshoot his issue himself more easily, but also for our support team as the only way to debug those kind of issues now is to put a breakpoint there and see what employee has an issue. opw-4411999 Forward-Port-Of: odoo/odoo#200376
Resolved issues and error corrections
This update corrects a bug where changing the expected arrival date on a Purchase Order automatically updated the deadline for related Sales Order delivery orders. The fix prevents this cross-document propagation by limiting updates to moves with established procurement links, ensuring accurate delivery scheduling without unintended consequences.
Original PR description
Version: ---------- - 18.0+ Steps to reproduce: ---------------------- 1. Install `sale_management`, `purchase`, and `stock` module. 2. Go to Settings and enable Reception Report. 3. Create a…
Version: ---------- - 18.0+ Steps to reproduce: ---------------------- 1. Install `sale_management`, `purchase`, and `stock` module. 2. Go to Settings and enable Reception Report. 3. Create a storable product with tracking enabled and add supplier information. 4. Create a Sales Order for the product with a quantity and confirm it. 5. Open the generated Delivery Order and note the deadline value. 6. Create a Purchase Order for the same product and quantity using the vendor set on the product and confirm it. 7. Open the generated Receipt and click on the Allocation smart button. 8. Assign the receipt to the delivery order. 9. Go back to the Purchase Order and change the Expected Arrival Date. Issue - Updating the Expected Arrival Date on the Purchase Order also updates the deadline of the related Sales Order delivery. Root cause: ------------ - Clicking Assign triggers `action_assign`, which links incoming and outgoing moves: https://github.com/odoo/odoo/blob/cfc63060926db4cec773c159b8ecf97dc0b36d1a/addons/stock/report/report_stock_reception.py#L271 - Updating the PO Expected Arrival Date triggers purchase.order.line.write(), which calls _update_move_date_deadline(new_date). https://github.com/odoo/odoo/blob/cfc63060926db4cec773c159b8ecf97dc0b36d1a/addons/purchase_stock/models/purchase_order_line.py#L100-L101 - Then this `_update_move_date_deadline` function updates the incoming move `date_deadline` and this triggers stock.move.write(). https://github.com/odoo/odoo/blob/cfc63060926db4cec773c159b8ecf97dc0b36d1a/addons/purchase_stock/models/purchase_order_line.py#L165 - Inside write(), _set_date_deadline() is called. https://github.com/odoo/odoo/blob/cfc63060926db4cec773c159b8ecf97dc0b36d1a/addons/stock/models/stock_move.py#L748-L749 - Inside `_set_date_deadline()`, the method calls` _get_moves_to_propagate_date_deadline()` to collect all related moves that should be updated. - This method includes `move_dest_ids` in the returned moves. Since the incoming move is manually linked to the delivery move, the corresponding outgoing move (from the Sales Order) is part of `move_dest_ids`, and its `date_deadline` is therefore updated as well. https://github.com/odoo/odoo/blob/cfc63060926db4cec773c159b8ecf97dc0b36d1a/addons/stock/models/stock_move.py#L553 https://github.com/odoo/odoo/blob/cfc63060926db4cec773c159b8ecf97dc0b36d1a/addons/stock/models/stock_move.py#L545 Solution: ---------- - Limit propagation to moves that share the same `group_id` or where no formal procurement group/picking link is established. - This prevents unintended date shifts across unrelated documents (manual links) while preserving correct behavior for standard MTO and Manufacturing flows. --- opw-6085890 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This update corrects a technical issue within the Odoo accounting system for Denmark (l10n_dk) where an account was listed twice. Removing this duplication ensures accurate financial reporting and avoids potential errors in transactions. This is a routine maintenance fix to maintain data integrity.
Original PR description
In the list 'dk_coa_7630 ', the account has been used in the list twice. Removing the duplication from the list. [Reference](https://github.com/odoo/odoo/blob/17.0/addons/l10n_dk/migrations/1.4/end-migrate.py#L14) 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#270573
This update significantly speeds up timesheet updates and creation, particularly for projects with many planning slots. Previously, a single timesheet change triggered a slow, unnecessary recomputation across the entire project. This fix optimizes the system to only recalculate when needed, resulting in a much faster and more responsive experience.
Original PR description
The planning.slot model has the field timesheet_ids which is computed and non-stored. It also has a dependency on `project_id.timesheet_ids.unit_amount`. As timesheet_ids is non-stored while it's a…
The planning.slot model has the field timesheet_ids which is computed and non-stored. It also has a dependency on `project_id.timesheet_ids.unit_amount`. As timesheet_ids is non-stored while it's a dependency of the field `effective_hours`, if a single timesheet is modified on a project, a recomputation is required on all planning slots on that same project. Before this commit, the dependency highlighted earlier was resulting in a huge unnecessary computation since otherwise there's no way for the ORM to scope this computation to only affected slots. This commit elimintates this dependency and manually invalidates the timesheet_ids field on only the associated slots which drastically improves performance. A recomputation should only be done in case of write/create since timesheet_ids is computed nonstored. ## Benchmarks: ### The time it takes to update or create a timesheet in a specific project: | No. planning slots in project | Before | After | | ----------------------------- | ------- | ----- | | 699838 | Timeout | <1s | | 107203 | ~130s | <1s | | 7812 | ~9s | <1s | opw-5082577
This update significantly speeds up bank reconciliation by optimizing how early payment discounts are calculated. Previously, checking eligibility for discounts was slow and caused memory issues. Now, the system processes multiple invoices at once, dramatically reducing processing time and improving the overall bank reconciliation experience.
Original PR description
### Issue A user cannot open their bank journal if some rules are too slow to compute or consumes too much memory. ### Solution This commit refactors the `_is_eligible_for_early_payment_discount`…
### Issue A user cannot open their bank journal if some rules are too slow to compute or consumes too much memory. ### Solution This commit refactors the `_is_eligible_for_early_payment_discount` method on `account.move` to improve performance during bank reconciliation. The method was designed to run on a single record and was being called inside a loop when generating reconciliation suggestions. A new method, `_is_eligible_for_early_payment_discount_batched`, has been introduced to handle the eligibility check for a recordset of moves. The original `_is_eligible_for_early_payment_discount` method is preserved as a wrapper around the new batched method to maintain backward compatibility. Enterprise PR: https://github.com/odoo/enterprise/pull/92401 ### Benchmarks Profiling `_get_invoice_matching_amls_result` when using a reconciliation model with a Partner mapping: | # of amls | Before | After | % | |:-------------:|:-----------:|:------:|:----:| | 499 | ~3 min then MemoryError | 230 ms | 99.9 % | ### References opw-4998109 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This update fixes a security vulnerability where users with the 'Recruitment: Interviewer' role could access job positions, even without associated applicants. The change adds access rules to limit this access to only those positions where the user is a reviewer or has an applicant they review, enhancing data security.
Original PR description
Issue: ---------------------------------------- A user with the access right "Recruitment: Interviewer" can actually access the job positions. Steps to reproduce: ---------------------------------------- - Install Recruitment - Have a user with no rights on employee and "Recruitment: Interviewer" - Connect as this user - Open an public employee record - You can navigate to their Job Position even if you don't have any applicant with this job position Cause: ---------------------------------------- The group `hr_recruitment.group_hr_recruitment_interviewer` give read access to the model `hr.job` and there are no access rules on this model. Solution: ---------------------------------------- Create an access rule restricting the access to only the job positions where the user is set as reviewer or having an applicant where the user is reviewer. opw-6264247
This update fixes an error in the delivery note pricing calculation for products tracked by multiple lots. Previously, the price was incorrectly based on only the first lot, leading to inaccurate DDT costs. This change ensures that the total sale price across all lots is correctly reflected on the delivery note.
Original PR description
Steps to reproduce: 1. Install Italian localization and l10n_it_stock_ddt 2. Create a product tracked by lots with a price of 100 3. Create two lots for that product, each with 5 in stock 4. Create a sale order for a quantity of 8 5. Confirm the sale order and validate the delivery 6. Print the delivery note Issue: Only the first lot's sale price is used in the DDT cost calculation (price = 500 instead of 800) Why this happens: The QWeb template used `move.move_line_ids[0].sale_price`, which only reads the sale_price of the first move line. When a delivery is split across multiple lots, each lot produces its own move line, so only the first is considered in the price calculation. opw-6244076 Forward-Port-Of: odoo/odoo#267757
This update resolves a problem where emojis, particularly complex ones like `👨🚒`, were being displayed incorrectly due to how they were encoded. The fix backports a more robust regex pattern from a previous version of Odoo to correctly handle these variations, ensuring emojis are displayed properly.
Original PR description
Bug === Some emoji like `👨🚒` are separated, because they are built using `👨 + Emoji_Modifier + 🚒` (`\uFE0F` can also be used to get the variant of the emoji). Adapt the regex to take into account those Unicode variations. Task-5491124 Forward-Port-Of: odoo/odoo#269719
2 changes
Enhancements to existing features
This update enhances the Speedscope profiling feature by adding a control panel to manage rendering options. Previously, Speedscope visualizations were static and prone to performance issues. Now, users can easily enable or disable options, leading to faster, more reliable results and simplified sharing of profile data.
Original PR description
This pr aims to improve the profiling, and mainly speedscope rendering experience. Right now, visualizing a speedscope result: Gives a static result. The makes the usage of some options like constant time impossible without editing the code. It also generate to much different types of output at the same time making it slow and leads faster to memory error. Needs to enable the profiling on the database to be able to visualize the result. This can be painful mainly when trying to access result after some time, or just after profiling in python. This pr removes the need to enable the profiling to visualize the results. This pr adds a wizard to enable or disable some options. As a bonus it is now possible to combine multiple profile in the same view (experimental) Backport of https://github.com/odoo/odoo/pull/189370 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Resolved issues and error corrections
This update fixes an issue where payslips weren't correctly generated for employees registered within branch companies of a larger organization. The fix adjusted the system's search criteria to include all related companies within the organizational hierarchy, ensuring accurate payslip creation. This improves payroll accuracy and reporting for businesses with multiple branches.
Original PR description
Bug: employees registered on branch companies don't appear in the
employee_id field when creating a payslip from the parent company.
Reason: the domain used ('company_id', '=', company_id) which only
matches the exact company, not its children.
Solution: replaced '=' with 'child_of' to include all descendant
companies in the hierarchy.
task - 6299634