Daily updates from Odoo
Friday, June 19, 2026
71 changes
11 changes
Resolved issues and error corrections
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 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 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 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 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 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 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 resolves an issue where users with restricted accounting rights incorrectly marked invoices as fully paid, leading to inaccurate financial reporting. The fix ensures automatic bank reconciliation works correctly for these users by safely bypassing a group check during the reconciliation process, maintaining data auditability.
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()` This function creates a balancing line and triggers `move._compute_checked()` to update dependencies However, `move.checked` requires `_is_user_able_to_review()` to be True A user with "Invoicing & Banks" rights lacks the `account.group_account_user` group, meaning the move is not marked as checked, 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#120217 Forward-Port-Of: odoo/enterprise#118023
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 reconciliation by correctly handling user permissions and preventing the creation of unwanted Account Receivable lines, maintaining data integrity.
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/odoo#270080
7 changes
Resolved issues and error corrections
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 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 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#11916113 changes
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, 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 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 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 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 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
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
4 changes
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 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
This update fixes an issue where payments to Mexican CFDI invoices could be sent multiple times, leading to inaccurate reporting. The change ensures the 'Update Payments' button only appears after the invoice is fully reconciled, preventing over-reporting of payment amounts. This improves financial accuracy and compliance.
Original PR description
Issue: Sending payments to CFDI before its full amount is reconciled allow sending the same invoice payment several times to CFDI. So some invoices are declared as paid several times and the total…
Issue: Sending payments to CFDI before its full amount is reconciled allow sending the same invoice payment several times to CFDI. So some invoices are declared as paid several times and the total amount of the payment is seen as exceeding the real total. This fix is a back port of odoo/enterprise#108355 and aim to prevent some things the backend allow, but the front end prevents. Following steps could be used to reproduce from 18.3. Steps to reproduce: - In a Mexican company - Create an invoice A of $40 to Inmobiliaria CVA - Confirm and send to CFDI - Go to bank, create a new Bank transaction of $80 - reconcile with Invoice A - Go to invoice A => click on button "Update payments" (it doesn't appear before version 18.3) - Then sheet CFDI and Download There is the first XML sent to CFDI with payment for invoice A - Create an invoice B of $40 to Inmobiliaria CVA - Confirm and send to CFDI - reconcile the transaction with Invoice B - Go to invoice B - Click on button "Update payments" - Then sheet CFDI and Download There is the second XML sent to CFDI with payment for invoices A and B Invoice A payment was sent twice to CFDI Expected behavior: - The "Update payment" button should appear only once the invoice payment is fully reconciled. Current behavior: - The update payment button appear once the invoice is reconciled with a payment. opw-5432421 Forward-Port-Of: odoo/enterprise#119357
22 changes
New functionality added to Odoo
This update adds the ability to track equipment used during service shifts, including serial numbers and a history of interventions. This improves accountability and traceability for service technicians and allows for better management of valuable assets. The changes integrate equipment data with shift scheduling and lot tracking within the Odoo stock module.
Original PR description
- Create a `Equipment` option that enables lots & serial numbers - Create an `Equipment` page in the shift form view - Create a `Shifts` stat button in the lots form view - Activate lots from stock settings when setting equipments - Prefill the `lot_ids` with the customer and its descendant lots when picking a customer for the shift --- task-5184419
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 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 pull request simplifies the process of configuring car options for employees within the salary offer and employee view. The changes focus on improving the user experience, making it easier for HR to manage car-related benefits and for employees to select their preferred vehicle options. This enhances efficiency and clarity in the contract creation process.
Original PR description
-Introducing some UX changes in salary configurator and employee's offer view to simplify car options.
This update allows users to specify quantities and units for sections within subscription orders. Changes to these values are now accurately reflected in both the generated PDF reports and the customer portal, providing more precise order information. This enhancement improves transparency and accuracy for subscription management.
Original PR description
In the community PR, users can set the quantity and unit on sections and subsections. When a user changes the quantity or unit, these values are displayed on the generated PDF and in the portal. To support this, we changed the table architecture to keep it consistent in sale_subscription. PR: https://github.com/odoo/odoo/pull/267933 Upgrade: https://github.com/odoo/upgrade/pull/10417 task-6075605
This update enhances the user experience for managing shifts within Odoo, particularly in the planning and portal sections. Key changes include direct sign-in and completion buttons on the planning kanban view, a simplified portal interface, and improved navigation for mobile users. These improvements streamline shift management workflows and boost operational efficiency.
Original PR description
_*= planning_field_service,project_forecast,planning_field_service_sale_timesheet,
sale_planning
- Add a Sign Report button to allow users to sign reports directly from planning shifts
- Improve the planning kanban UI by adding Sign In and Complete buttons,
allowing users to directly sign in and complete shifts from the kanban view
- Enhance the portal view by hiding breadcrumbs, banners, and print button in sign mode
- Add a back-to-shift navigation button in the portal
- Improve the mobile view of the shift form view
task-6218179This change addresses a requirement from Avalara, who need the LC16 code to be dotted for their city web services. Previously, Odoo automatically removed these dots. Now, the LC16 code is sent with the dots, allowing Avalara's tool to correctly sanitize the data.
Original PR description
Purpose: Avalara requires the LC116 code to be dotted for certain city webservices. Their tool will automatically sanitize the dots for cities that don't support it. Current Behavior: Odoo sanitizes the LC116 code before sending the JSON payload. Expected Behavior: The LC116 code is sent in the JSON payload with the dots. task-6304351 Forward-Port-Of: odoo/enterprise#120648
This update allows users to connect their personal LinkedIn accounts and schedule posts to Facebook and Instagram Stories. It also includes several UX improvements to make the social posting experience easier and more intuitive.
Original PR description
Purpose ======= This PR addresses many improvements in the social app, the two main ones being allowing to link your personal LinkedIn account and to post Facebook and Instagram "Stories". We also…
Purpose ======= This PR addresses many improvements in the social app, the two main ones being allowing to link your personal LinkedIn account and to post Facebook and Instagram "Stories". We also made a lot of UX tweaks along the way to make the social app easier to use, with a bit of much needed polish as the app has not been worked on a lot since it was introduced (about 7 years before this PR). Specifications =========== Allow the users to add their personal LinkedIn account in Social (and not only the page for which they are admin), and to post on them. During the authentication process of Social, if something went wrong we need to manually go back or enter the Odoo database URL to retry. It can be very frustrating, and so now we open the authentication URL in a new window, and when the process is done we close the window and refresh the view. Show a loading page while doing the token exchange process (which can take some time depending on the number of pages). Allow removing the accounts from the "Connect Account" modal. Show the icons of the selected medias in the calendar view. Improve the way we schedule posts. Allow the users to comment their own posts right after posting. Allow posting stories on Facebook and Instagram. Allow sorting the image when posting on a social media. Use AI to write social post. Show the chars count for all medias while typing the message. Improve the computation of the tweet length (URL must count for 23 chars and emoji for 2). In the post form view, show the media icon instead of the media name when selecting the accounts. Do some refactoring to unify the name for the social post message field. Schedule the social post with a modal, like it's done in mass-mailing. (see each individual commits for more details) Task-5491124
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 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 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
A recent issue causing the Documents view to crash when navigating from an activity has been resolved. This was due to a timing problem with how different parts of the system handled data updates. This fix ensures the Documents view functions reliably for all users.
Original PR description
### Description When navigating to Documents via an activity, the list view crashes with a TypeError on setting 'COMPANY'. ### Root Cause An asynchronous race condition occurs between parent and child `onWillStart` hooks. The child finishes an await before the parent's hook runs `expandDefaultValue()`. Thus, `this.state.expanded[sectionId]` is undefined when the child tries to write to its nested keys. ### Solution Await `sectionsPromise` first in the child hook. opw-6276003 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/enterprise#120713 Forward-Port-Of: odoo/enterprise#119634
This update fixes a critical crash related to AvaTax connections and enhances the user experience. It now clearly indicates when a connection isn't set up correctly, preventing confusion and ensuring accurate tax calculations. Additionally, the connection test results are now more organized and version-aware.
Original PR description
Several related fixes around the AvaTax connection settings: - Surface an unconnected "Avalara Included" setup instead of silently using Direct credentials. Filling Direct credentials, switching to…
Several related fixes around the AvaTax connection settings: - Surface an unconnected "Avalara Included" setup instead of silently using Direct credentials. Filling Direct credentials, switching to Included, then not completing the connection (link/migrate/create) left the company looking configured through those leftover credentials: the user believed they were on Included while a request either silently used Direct or crashed on the unset IAP proxy user (ensure_one). Direct credentials now only count in Direct mode, so the not-connected state raises the usual RedirectWarning pointing to the configuration. - Group nexus locations by country in the connection test result. The list dumped every nexus row flat, so countries appeared alongside their own regions and each jurisdiction repeated once per tax type (e.g. "California" dozens of times). Group by country, drop the country-wide rows, dedupe and sort, with a short summary line. - Make the "Help me choose" documentation link version-aware via the documentation_link widget instead of the /latest/ alias, which redirects to the latest major release (19.0) where the AvaTax docs don't exist. task-6295272 Forward-Port-Of: odoo/enterprise#120761
This update resolves an issue where the EC List XML export was incorrectly identifying invoices with the same VAT number as separate entities, leading to rejection by tax authorities. The fix ensures that invoices with identical VAT numbers are treated as a single partner, complying with Belgian tax regulations. This prevents errors and ensures accurate EC List reporting.
Original PR description
With l10n_be: - Create two contacts with the same VAT - Create an invoice for each that is EC List compatible - Generate the return and export the EC List XML In the generated xml the two partners with the same vat are treated as different partners, which causes a rejection by the tax agency. opw-6109585 Forward-Port-Of: odoo/enterprise#120305 Forward-Port-Of: odoo/enterprise#117702
This update resolves an issue where invoices for Persona Natura customers in Colombia were incorrectly formatted for export to the DIAN tax authority. The change ensures the correct XML structure is generated, addressing a mismatch in account identification. This prevents export errors and ensures compliance with Colombian tax regulations.
Original PR description
Issue: Colombian partner being Persona Natura are misinterpreted as Person Juridica. It raises issue while exporting XMLs for dian. Steps to reproduce: - In a Colombian company - Create a Customer with NIT and "Obligaciones y Responsabilidades" to "R-99-PN" - Create an invoice - Send the invoice Current behavior: - node <cbc:AdditionalAccountID> is set to 1 and node PartyIdentification is missing Expected behavior: - node <cbc:AdditionalAccountID> is set to 2 and there is a PartyIdentification node Cause: Colombian partners having a NIT have is_company to True. However, Persona Natura have NIT but aren't companies. opw-6206308 Forward-Port-Of: odoo/enterprise#118193
This update resolves an error that prevented users from opening the Gantt view for work orders. The issue stemmed from how the system handled resources without calendars, leading to a system crash. The fix ensures the system gracefully handles these resources, preventing the error and allowing users to access the Gantt view.
Original PR description
Currently, an error occurs when opening the gantt view of work orders. **Steps to Reproduce:** - Install `mrp_workorder` with demo data. - Go to `Settings` > `Technical` > `Resource` > `Resources`. -…
Currently, an error occurs when opening the gantt view of work orders. **Steps to Reproduce:** - Install `mrp_workorder` with demo data. - Go to `Settings` > `Technical` > `Resource` > `Resources`. - Open the `Assembly 1` resource and remove its `working time`. - Go to `Manufacturing` > `Operations` > `Work Orders`. - Switch to the `Gantt view`. `KeyError: 22` when the user opens the Gantt view, the system checks the unavailability of work centers and employees based on their resource calendars. While computing unavailable intervals for resources [1], resources without a calendar are flexible resources. If no leave interval exists within the specified start and end range that matches the domain, the resource is not included in the result [2]. when updating the unavailable intervals dictionary [3], the resource is missing. As a result, when it later tries to access the unavailable intervals for that resource, it raises an error [4]. This commit prevents the error by safely handling resources that are not present in the unavailable intervals dictionary by using an empty list instead. [1]: https://github.com/odoo/odoo/blob/1f666de440479dd3d30b7b6cf7c42862c5fcb37a/addons/resource/models/resource_resource.py#L186 [2]: https://github.com/odoo/odoo/blob/1f666de440479dd3d30b7b6cf7c42862c5fcb37a/addons/resource/models/resource_calendar.py#L532-L536 [3]:https://github.com/odoo/odoo/blob/1f666de440479dd3d30b7b6cf7c42862c5fcb37a/addons/resource/models/resource_resource.py#L187 [4] https://github.com/odoo/enterprise/blob/47f2e9e88fa0bbb8852fe7734c6bece4dca8b9d0/mrp_workorder/models/mrp_workorder.py#L692-L694 sentry-7525704808 Forward-Port-Of: odoo/enterprise#119385
This update resolves an issue where the barcode inventory count feature would fail when using archived units of measure. The fix ensures that archived UOMs are correctly included in the inventory count cache, allowing accurate counts to be performed. This improves the reliability of physical inventory processes.
Original PR description
### Steps to reproduce: - In the settings enable: "Units of Measure & Packagings", "Storage Locations" - Create a product in units and register 1 unit in stock - Inventory > Operations > Adjustments…
### Steps to reproduce: - In the settings enable: "Units of Measure & Packagings", "Storage Locations" - Create a product in units and register 1 unit in stock - Inventory > Operations > Adjustments > Physical Inventory - Select your line and request a count > Set Current Value - Inventory > Configurations > units of measures > UOM categories - Select unit and archive it - Go to the barcode app > Click Count inventory ### > Owl error: Uncaught promise ### Cause of the issue: Since the uom used on the quant is archived, it is not found by the search used to fill the barcodeCache: https://github.com/odoo/enterprise/blob/26546bcd3beebc7f65ce08385441b6284b46598e/stock_barcode/static/src/components/main.js#L209-L213 https://github.com/odoo/enterprise/blob/26546bcd3beebc7f65ce08385441b6284b46598e/stock_barcode/models/stock_quant.py#L104-L106 https://github.com/odoo/enterprise/blob/26546bcd3beebc7f65ce08385441b6284b46598e/stock_barcode/static/src/components/main.js#L229 https://github.com/odoo/enterprise/blob/26546bcd3beebc7f65ce08385441b6284b46598e/stock_barcode/static/src/models/barcode_model.js#L37-L39 However, if the uom is not present in the barcode cache the `BarcodeQautnModel` will fail to createLinesState whihc raises a missing error: https://github.com/odoo/enterprise/blob/26546bcd3beebc7f65ce08385441b6284b46598e/stock_barcode/static/src/models/barcode_quant_model.js#L712 https://github.com/odoo/enterprise/blob/26546bcd3beebc7f65ce08385441b6284b46598e/stock_barcode/static/src/lazy_barcode_cache.js#L107-L110 opw-6250090 Forward-Port-Of: odoo/enterprise#120683 Forward-Port-Of: odoo/enterprise#118813
A recent issue was causing the Enterprise application to crash when opening articles with embedded account reports. This fix prevents a critical error related to modifying component properties during setup, ensuring stability and proper functionality of account reporting within the system. This resolves a technical problem that could impact users accessing financial reports.
Original PR description
When opening an article containing an embedded account report component, the application crashes because the `name` prop is mutated during the component `setup`, which is not allowed.
Steps to reproduce:
1. Create a new audit report
2. Open the "Journal Audit" article containing an embedded account report
=> The following exception is raised:
```
Uncaught (in promise) TypeError: setting getter-only property "name"
setup account_report.js:15
```
To fix the issue, the translation of the `name` prop is moved to `getProps`, which prepares component props before mounting. This ensures the value is already translated at instantiation time, avoids any mutation during setup, and preserves prop immutability throughout the component lifecycle.
Ref: odoo/enterprise#109962
Task-6292898
Forward-Port-Of: odoo/enterprise#120077This update fixes an issue where a specific invoice origin code was incorrectly triggering a cancellation request to Mexican tax authorities (CFDI). The change ensures that only invoices with '04' origin codes are used for down payment cancellations, aligning with Mexican regulations. This prevents unintended cancellations of down payments and improves the accuracy of CFDI processing.
Original PR description
**Steps to reproduce:** - Install Sales, Accounting and l10n_mx_edi - Switch to a Mexican company (e.g. ZAPATERIA URTADO ÑERI) - Create a SO: * Customer: [a Mexican customer] (e.g. INMOBILIARIA CVA) * Payment Way: Efectivo * Payment Policy: PUE * Product: [any product with a UNSPSC Category] * Taxes: [any] (e.g. 16%) - Confirm the SO - Create a down payment (e.g. 60%) - Confirm the down payment - Send it to CFDI - Copy the fiscal folio - Go back to SO - Create the final invoice - Set the copied fiscal folio prepend with "07|" as CFDI Origin - Confirm the invoice - Send to CFDI **Issue:** A cancellation request is sent to CFDI for the down payment. "07" origin code is used to link the invoice to a down payment. It should not cancel the down payment. It should only be done with "04" origin code used for substitution of a previous invoice. opw-6266678 Forward-Port-Of: odoo/enterprise#120757
This update resolves a requirement from Luxembourg auditors regarding the classification of partners in our SAFT reports. Specifically, it ensures that less than 30% of transactions with payable or receivable accounts have missing supplier or customer IDs, respectively. The changes update the XML reports to align with these specific reporting guidelines.
Original PR description
This PR is one of many triggered by responses from Luxembourg auditors. See PR #113316 for a full list of these PRs. As described in PR #117799, the \CustomerID and \SupplierID elements on…
This PR is one of many triggered by responses from Luxembourg auditors. See PR #113316 for a full list of these PRs. As described in PR #117799, the \CustomerID and \SupplierID elements on \Transaction\Line elements is determined by a partner's `customer_rank` and `supplier_rank`. This is a binary designation, one or the other. The Luxembourg FAIA report requires that less than 30% of \Transaction\Line elements with payable accounts (class 6) can not have \SupplierID. The same applies for \Transaction\Line elements with receivable accounts (class 7) and the \CustomerID element. TSB clarified that any partner on an receivable or payable line should be added to the Customer list or Supplier list respectively https://github.com/odoo/enterprise/pull/100749#issuecomment-3655127511. In addition, I verified that Luxembourg's analysis of four separate FAIA files (from ticket 5427296) aligns with this expectation. <img width="1322" height="690" alt="image" src="https://github.com/user-attachments/assets/1a82f99e-5b32-4dbb-96e1-1b25bab2629b" /> This commit adds partners to the \Supplier and \Customer lists if they have any payable or receivable lines, respectively. It also picks between the \CustomerID and \SupplierID based on a line's `account_type`. This logic is applied to `account_saft` and updates the other, country-specific SAFT reports where appropriate. It also retains the previous `customer_rank` and `supplier_rank` logic as a fallback for older XML reports and for accounts other than `asset_receivable` or `liability_payable`. opw-6118024 Forward-Port-Of: odoo/enterprise#120790 Forward-Port-Of: odoo/enterprise#118714
4 changes
Enhancements to existing features
This update enhances the system's ability to process payments through Powens and Saltedge by adding debtor and creditor information to the data sent to our payment processor. This change ensures accurate payment initiation and improved integration with payment gateways.
Original PR description
In order to be able to initiate payments using Powens and Saltedge, we need to include debtor information in the payload sent to Odoofin. This commit adds the necessary fields and updates the tests accordingly. Task ID: 5977148, 6095729 Forward-Port-Of: odoo/enterprise#119843
Resolved issues and error corrections
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
```This update fixes a bug in the Preparation Time report for Point of Sale, ensuring that preparation durations are displayed correctly based on the user's current timezone. Previously, the report always used the timezone of the OdooBot, leading to inaccurate data. This change improves reporting accuracy and user experience.
Original PR description
In POS, the Preparation Time report groups average preparation durations by hour. Those hour buckets were always computed with the timezone of the user who ran the module upgrade (OdooBot /…
In POS, the Preparation Time report groups average preparation durations by hour. Those hour buckets were always computed with the timezone of the user who ran the module upgrade (OdooBot / superuser), not the timezone of the user viewing the report. Changing the user, company, or browser timezone had no effect on the graph until the module was upgraded again. Steps to reproduce: ------------------- * Configure a Preparation Display and create POS orders with measured preparation times. * Open Point of Sale → Reporting → Preparation Time. * Note the hour bucket used for the orders. * Change your user timezone in Preferences and reload the report. > Observation: The hour buckets stay the same. Before the fix, they only changed after upgrading `pos_enterprise`, because the timezone was embedded in the SQL view created during `init()` as superuser. Why the fix: ------------ Replace the static PostgreSQL view with a dynamic `_table_query` so `order_hour` is computed with the current user's timezone on each report read. `init()` now only drops the legacy view instead of recreating it with a frozen timezone. opw-6220248
This update resolves an issue where users couldn't select child contacts when creating bank statement lines. The change aligns the system's contact selection process, allowing users to correctly associate bank statements with child contacts within the bank reconciliation workflow. This improves the usability and accuracy of financial reporting.
Original PR description
When creating a bank statement line, we can not set an individual contact that is a children of a company contact. However, when clicking on the 'Set Partner' button, all contacts are shown in the modal list view. This commit aligns the domain coming from the 'Set Partner' button with the domain from the 'partner_id' field of the auto reconcile wizard Steps: - Have a contact X, with a child contact Y - Create and confirm an invoice for contact Y, amount 1000 - Create a bank statement line for 1000 -> You can not select Y, only X - Click 'Add & Close' - Click on 'Set Partner' button -> Y is displayed opw-6205154
7 changes
Enhancements to existing features
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 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 fixes an issue where the reconciliation wizard incorrectly used foreign currency when a company currency exchange difference remained after the reconciliation plan. The fix ensures the wizard accurately reflects the final difference, preventing inflated write-off amounts and improving financial reporting accuracy. This impacts how receivables are reconciled.
Original PR description
## Problem In `account_reconcile_wizard.py`, the internal helper `get_reco_currency` (inside `_compute_reco_wizard_data`) returned the single foreign currency found among the selected lines without…
## Problem
In `account_reconcile_wizard.py`, the internal helper `get_reco_currency` (inside `_compute_reco_wizard_data`) returned the single foreign currency found among the selected lines without checking whether any residual in that currency actually remained after the reconciliation plan ran.
When reconciling a company-currency line against a foreign-currency line where the plan fully consumed the foreign-currency residual (leaving only a small company-currency exchange-difference balance), the wizard still set `reco_currency_id` to the foreign currency, inflating the write-off amount by the exchange rate.
## Root cause
```python
elif len(foreign_currencies) == 1:
return foreign_currencies # ignores whether the plan left any residual in that currency
```
## Fix
In the `elif len(foreign_currencies) == 1:` branch, iterate over `aml_values_map` (post-plan residuals) and return the foreign currency only if at least one line still has a non-zero residual in it; otherwise fall back to company currency.
## Test
`TestAccountReconcileWizard.test_write_off_receivable_company_currency_vs_foreign_currency`
Reconciles a company-currency debit (111.0) against a foreign-currency credit (−110.0 / −330.0 EUR at rate 3.0). The real remaining difference is 1.0 in company currency. The test asserts that `reco_currency_id` is the company currency and `amount` ≈ 1.0.
Task reported at: https://www.odoo.com/my/tasks/63155812 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