Monday, June 22, 2026
41 changes · saas-19.3
New functionality added to Odoo
This update expands Odoo's capabilities by integrating new food delivery providers – including Talabna, Mandoob, and DiDi Food – for use in various countries. It also backports existing delivery provider integrations, broadening the system's reach and offering more options for restaurants and delivery services.
Original PR description
In this commit: - We are introducing new delivery providers like Talabna, Mandoob, Snoonu, DiDi Food and Zyada for different countries and backporting Radyes, ToYou, The Chefz, InstaShop and Smiles. Task-6263289,6263272,6263203,6263165,6310690 Forward-Port-Of: odoo/enterprise#121051 Forward-Port-Of: odoo/enterprise#119537
Enhancements to existing features
This update strengthens the process of exporting Liasse Fiscale reports by implementing crucial data checks. Specifically, it verifies that key information related to writers, debtors, and referenced partners is complete and accurate before reports are generated and sent. This improves data reliability and reduces potential errors in tax reporting.
Original PR description
while exporting liasse fiscale, no chceks were done before the export and there were no account returns checks before sending it, this commit add account return checks to the liasse fiscale: -writer and debtor have the necessary informations on their record -partners mentionned in the reports have the necessary fields completed on their records -for some line, we have to choose between some values task-6127270
Resolved issues and error corrections
This update resolves an issue where leave schedules incorrectly blocked resource allocation, now only applying to resources with matching calendars. Additionally, tests have been reorganized and corrected to ensure accurate coverage of rental planning features, improving overall system stability and reliability.
Original PR description
## [FIX] sale_renting_planning: check global leaves working schedule Before this commit: any `resource.calendar.leaves` with no `resource_id` created would prevent all resources from being allocated…
This update expands the data sent to payment processing systems (Powens and Saltedge) by including debtor and creditor information. This change is necessary to correctly initiate payments and improve integration with these external 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
This update allows users to group and filter POS orders based on their DIAN transmission status. Previously, the system couldn't easily identify rejected or failed syncs, making it difficult to monitor the health of daily DIAN transmissions. This change provides better visibility and control over POS order synchronization with DIAN authorities.
Original PR description
The DIAN transmission status (l10n_co_edi_pos_dian_state) is a non-stored computed field, so it could not be used to group, filter or sort the POS Orders list: users had no way to isolate rejected or failed orders, nor to get an overview of daily sync health. Provide a compute_sql for the field so the ORM can express it in SQL, making it groupable/searchable/sortable without storing it. This avoids a schema change while keeping the value derived from the most recent DIAN document. task-6273842
## [FIX] sale_renting_planning: check global leaves working schedule
Before this commit: any `resource.calendar.leaves` with no `resource_id` created would prevent all resources from being allocated during the leave date.
After this commit: any `resource.calendar.leaves` with `no resource_id` would be applied only to resources with the same `calendar_id` as the leave.
if the leave has no `calendar_id` then the leave applies to all `resource.calendars`
if a resource has no `calendar_id` then leaves with no `calendar_id` apply to it as well
## [IMP] {website_}sale_renting_planning: move tests from industry and fix existing ones
This commit moves the tests from [odoo/industry#1980](vscode-file://vscode-app/snap/code/237/usr/share/code/resources/app/out/vs/code/electron-browser/workbench/workbench.html) to their respective standard modules.
It also fixes the logic behind some tests as they weren't testing a `planning.role` with `sync_shift_rental` enabled.
task-6179505
Forward-Port-Of: odoo/enterprise#120865
Forward-Port-Of: odoo/enterprise#116430This update enhances the Timesheet Assistant by streamlining suggestions, adding shortcuts, and correcting inaccuracies in hour calculations. Specifically, it removes distracting highlights, excludes leave time from totals, and improves the relevance of suggestions for timesheet creation.
Original PR description
## Expected Behavior After Commit - Remove the green highlight when selecting a suggestion. - Add shortcuts for timesheet creation buttons. - Allow calendar events to be considered side activities - Exclude leave time from total hours, as leave time is already counted in the timesheet. - Do not show to‑do tasks (tasks without a project) in suggestions. - Restore previous suggestions for to‑do tasks when they later become linked to a project. - Add a default name for suggestions that do not have one. - Add hotkeys to Timesheet Assistant task-[6191451](https://www.odoo.com/odoo/project/4105/tasks/6191451) Forward-Port-Of: odoo/enterprise#120057
This update corrects an error that prevented users from sending SMS messages to website visitors. The issue stemmed from a change in how visitor data was accessed. This fix ensures that the correct phone number is used when sending SMS messages, improving the functionality of the website CRM SMS feature.
Original PR description
Currently, an error occurs when a user tries to send an sms to a visitor. **Steps to Reproduce:** - Install `website_crm_sms` without demo data. - Go to `Website` > `Edit`, drag and drop another…
Currently, an error occurs when a user tries to send an sms to a visitor. **Steps to Reproduce:** - Install `website_crm_sms` without demo data. - Go to `Website` > `Edit`, drag and drop another `Form` block. - Configure the form action to `Create an Opportunity` and `save`. - Fill in the required fields, including phone number and `Submit` the form. - Go to `Website` > `Reporting` > `Visitors` and click the `SMS` button on the visitor record. `AttributeError: 'website.visitor' object has no attribute 'phone'` After [this commit], which removed the mobile field from res.partner along with all related views, then it was updated to access the phone number from the website visitor. When a user creates an opportunity through the website and then tries to send an sms from the corresponding visitor record, it raises an error [1] because it attempts to access the phone field on website.visitor. when an anonymous (non-logged-in) user creates an opportunity, clicking the sms button on the corresponding visitor record triggers error here [2]. This commit ensures that the correct mobile field is accessed from the website visitor. [this commit]: https://github.com/odoo/odoo/commit/6b820eb6fc6f782ba6a83d605d87b4a1dd2a87be [1]- https://github.com/odoo/odoo/blob/6a0e6443951053f8361e97f42e5e45c32bb73656/addons/website_crm_sms/models/website_visitor.py#L13 [2]- https://github.com/odoo/odoo/blob/6a0e6443951053f8361e97f42e5e45c32bb73656/addons/website_crm_sms/models/website_visitor.py#L20 sentry-7550340909 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270118
This update resolves an issue impacting payroll calculations in both the UAE (AE) and Saudi Arabia (SA) localizations. The fix ensures accurate net cost calculations by standardizing how salary rules are aggregated, preventing negative values from incorrectly affecting payroll totals.
Original PR description
Steps: - Add a new salary category with the parent_id of company contribution (COMP) in AE - Create a dummy salary rule of that category - Compute a payslip and see the net cost unchanged Or - Create and compute a payslip in SA - Company contributions will be subtracted from each other Issue: - In AE localization, the issue with the rule was dropping salary rules that have a parent of company contribution category - In SA localization, the issue with the NETCOST was the aggregation of individual rules could include negative values which is not the intended flow. Solution: A standardized approach was adopted in both localizations in order to match the calculation of the NETCOST across. This approach will account for the categories with company contribution parent as well as the positive values for the individual salary rules. Forward-Port-Of: odoo/enterprise#115499
This update resolves an issue impacting how sickness pay is calculated for employees transitioning between long and partial periods of absence. The fix ensures accurate DPV (disabled person's verification) computations, particularly when moving from a long sickness to a partial incapacity. This improves the reliability of payroll processing.
Original PR description
Forward-Port-Of: odoo/enterprise#120868
This update fixes a limitation in how subscription products are priced, allowing users to accurately set one-time prices for hybrid subscriptions. Now, the system requires a plan to be selected for subscription products and ensures users can correctly configure pricing for products that allow both recurring and one-time sales. This enhances the flexibility and accuracy of subscription pricing.
Original PR description
Before this commit:
1. Users could save a pricelist rule for a pure subscription product without assigning a plan.
2. Hybrid subscription products (where 'Allow One-Time Sale' is True) were filtered out of the pricelist item form when no plan was selected, preventing users from setting a one-time price.
After this commit:
- The `plan_id` field on the product template form is now mandatory if the product is a subscription and does not allow one-time sales.
- The `product_tmpl_id` domain on the pricelist item form is updated to `['|', ('recurring_invoice', '=', bool(plan_id)), ('allow_one_time_sale', '=', True)]`, allowing users to select hybrid products for one-time pricing.
task: 6164232
Forward-Port-Of: odoo/enterprise#115271This update fixes a bug that prevented customers from removing free shipping rewards once they were applied to their cart. The fix removes a restriction in the system that only handled discount rewards, now allowing free shipping and free product rewards to be removed as intended. This improves the customer experience and ensures accurate order totals.
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 milestone deadline dates disappeared from task views after navigating back or refreshing. The fix ensures that milestone deadlines are consistently displayed in the task Kanban view and task form views, improving project tracking accuracy.
Original PR description
Steps to reproduce: 1. Open the Project application and open any project. 2. Filter the tasks by milestone (milestone deadlines appear as expected in the kanban view). 3. Open any task form view. 4.…
Steps to reproduce: 1. Open the Project application and open any project. 2. Filter the tasks by milestone (milestone deadlines appear as expected in the kanban view). 3. Open any task form view. 4. Click the browser's back button (or simply refresh the page while on the task kanban view). Issue: Milestone deadline dates disappear from the task Kanban cards and headers after navigating back or reloading. Why this happens: When hitting the browser back button or refreshing, the web client's router state recovery workflow executes (`loadRouterState` -> `loadState` -> `doAction` -> `_executeActWindowAction`). During this flow, `_getActionParams` checks if it can reuse the cached `lastAction`. However, due to a safety condition introduced in commit ab26f95893 to prevent embedded action showing across different projects, the router falls back to generating a fresh action request via `state.action`. This forces `_loadAction` to fetch the action definition from the database. Because the original base action window `act_project_project_2_project_task_all` lacks the `display_milestone_deadline` key inside its default context dictionary, the reloaded view is rendered without the flags required by the frontend to display milestone deadlines. opw-6283514 Forward-Port-Of: odoo/odoo#269781
This update fixes a visual issue in the CRM's Kanban view where the progress bar wasn't accurately displaying the number of opportunities in each stage. The team removed a counter that was previously showing the 'Other' opportunity count, and this change restores that functionality, providing a clearer picture of pipeline activity. This ensures sales teams have a more accurate view of their progress.
Original PR description
# How to reproduce - Go to the CRM kanban view - Add an oppurtinity where the salesperson is yourself & add another one where it is not in Stage X - Enable the "My pipeline" filter - Hover the progress bar of Stage X # The problem The green part of the progress bar displays "X Planned" while the grey one displays "No activities scheduled" even if there are # Cause This commit introduced the change from "X Other" to "No activities scheduled" : https://github.com/odoo/odoo/commit/ec52375d3b99f42e712b8a44afee43d82ffdf239 But it removed the counter, which the PO wishes to add back opw-6229549 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#265523
This update corrects a problem in how Odoo generates UBL BIS3 files for Debit Notes. Previously, the system incorrectly used 'LegalMonetaryTotal' instead of the required 'RequestedMonetaryTotal' node. This change ensures compliance with UBL BIS3 standards, improving the accuracy of our financial document exports.
Original PR description
Problem --------- Debit note should have the node `RequestedMonetaryTotal` instead of `LegalMonetaryTotal`. Solution --------- Add a conditional depending on the document type. opw-6295897 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270238
This update fixes an issue where stock valuation calculations were incorrect when a product had no recorded value. The fix ensures all products are considered during valuation replays, resulting in accurate inventory accounting. This improves the reliability of financial reporting.
Original PR description
Usecase to reproduce: - Create two average product A and B - Delele all the product.value for B - Receipt both units at 10$ - Set the price unit of A to 20$ - Receipt both units at 20$ Check the value at date to trigger a replay of valuation Expected behavior: - Product A -> 20 units at 20$ -> 400$ - Product B -> 20 units at 15$ -> 300$ Current behavior: - Correct for A but B is 200$ It happens because when we replay the history, we check for the minimal product.value and we replay valuation from this date (with moves). However in our case, the product B has no product value and thus we replay from A product.value. However it arrives after the first receipt of B and thus we only consider the second receipt for B. This is fixed by ensuring we have a product.value for all products in order to add a date domain on the moves. Forward-Port-Of: odoo/odoo#255909 Forward-Port-Of: odoo/odoo#255787
This update fixes an issue where preparation prints weren't showing all items after transferring a restaurant order to a shared table. The fix ensures that all products from both orders are accurately reflected on the preparation reprint, improving kitchen efficiency and order accuracy. It addresses a technical problem related to how order history is managed during merging.
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#270311 Forward-Port-Of: odoo/odoo#256309
This update fixes inaccurate Cost of Goods Sold (COGS) calculations for kit products in Odoo. The change ensures kits are correctly valued, addressing issues with multiple steps, multiple kits in a BOM, and FIFO inventory accounting. The fix simplifies the calculation and improves test coverage.
Original PR description
There's a few problems with kits and cogs This PR fixes them and unskips most tests of the test class. **Problems:** - Problem 1 multiple steps delivery - steps to reproduce: - activate 3 steps…
There's a few problems with kits and cogs
This PR fixes them and unskips most tests
of the test class.
**Problems:**
- Problem 1 multiple steps delivery
- steps to reproduce:
- activate 3 steps delivery
- create 2 storable products 'comp A' and 'comp B'
with category standard perpetual
- for both : set a cost of 10 and on on hand quantity
- create a storable kit product with category standard perpetual
- create a kit bom for the kit product with 1 comp A and 1 comp B
- confirm a SO for 1 quantity of the kit prod
- validate only first delivery
- confirm invoice
- Current behaviour:
No cogs line
- expected behaviour :
There should be cogs for 20$
- Problem 2 multiple kits in Bom :
- steps to reproduce:
- (multiple steps delivery not needed)
- use same products as for problem 1 but, in the Bom, set
the number of kit products produced to 2
- confirm a SO for 2 quantity of the kit prod
- validate all pickings
- confirm invoice
- Current behaviour:
Cogs have a value of 10$
- expected behaviour :
There should be cogs for 20$
- Problem 3: fifo comp
- steps to reproduce:
- with 1 step delivery
- create a storable product 'comp A' with fifo perpetual
category
- Confirm a PO and validate receipt for 1 comp A at 10
- Confirm a PO and validate receipt for 1 comp A at 20
- create a storable product 'kit' with fifo perpetual categ
- create a kit bom for the kit product with 1 comp A
- confirm SO for 2 kit
- deliver 1 quantity and create backorder
- confirm invoice for 1
- COGS line are created for 10$ (as expected)
- deliver the backorder
- confirm invoice for 1
- Current Behaviour:
Cogs are created for 15$
- Expected Behaviour:
Cogs should be created for 20$
**Cause of the issues:**
To compute the price_unit used for the cogs we call
_get_cogs_value()
https://github.com/odoo/odoo/blob/f8741728294a5147c7d2427d9997738096386262/addons/stock_account/models/account_move.py#L122
What we want is the price unit for 1 unit of the kit product
So we want :
sum(unit price of each comp * quantity of comp in bom)/ quantity of kit in bom
What is done for now :
Inside the sale_mrp override, for each component of
the bom we call _get_price_unit() on its move and
add the value to 'average_price_unit' and then divide
by the quantity of the kit product in the bom
https://github.com/odoo/odoo/blob/f8741728294a5147c7d2427d9997738096386262/addons/sale_mrp/models/account_move.py#L38-L42
Inside the sale_mrp override of _get_price_unit()
we return _get_kit_price_unit() called on the move,
https://github.com/odoo/odoo/blob/f8741728294a5147c7d2427d9997738096386262/addons/sale_mrp/models/stock_move.py#L15
Inside _get_kit_price_unit(), the variable 'component_qty_per_kit',
contains the quantity of each component as recorded in the bom
times the valued quantity (sale order line quantity).
For each comp :
- we store the return value of _get_price_unit
called on its moves in 'price_unit'.
- we add to 'total_price_unit':
price_unit * component_qty_per_kit/ the kit qty in the bom
we then return total_price_unit / valued quantity
So we actually return:
sum(unit price of each comp * quantity of comp in bom*
valued quantity)/ (quantity of kit in bom * valued quantity)
which is equal to:
sum(unit price of each comp *quantity of comp in bom)
/ quantity of kit in bom
https://github.com/odoo/odoo/blob/38c737c2a4cc29b48235a100cfa9d6152af73826/addons/mrp_account/models/stock_move.py#L40-L44
Problem 1 is caused by the fact that _get_price_unit()
will return 0 if there's only internal moves because
they have a value of 0.
(The problem does not happen with a single component
cause then the fallback on the super method is correct, but
with multiple comp the super method also returns 0
because _get_cogs_price_unit returns 0 when more than
one product).
Problem 2 is caused by the fact that we divide by the
quantity of the kit in the bom (kit_bom.product_qty) here
(inside _get_kit_price_unit) and again inside _get_cogs_value
as mentionned before.
Problem 3 happens because there is no mechanism
to account for already posted cogs inside the sale_mrp
override of _get_cogs_value(), as qty_invoiced
is computed but never used
https://github.com/odoo/odoo/blob/38c737c2a4cc29b48235a100cfa9d6152af73826/addons/sale_mrp/models/account_move.py#L31
**Fix**
As regards to the super methods (so non kit scenario),
_get_cogs_value() is used to :
- use original invoice if needed
- use standard price of the product if no moves
- deduct already posted cogs
- calls get _get_cogs_price_unit() to compute price_unit
based on the moves
All of this is also wanted for kits and don't need adaptation,
therefore the override should be on the _get_cogs_price_unit
where we do need a different behaviour when the product is a kit
Doing this we benefit from the 'already posted mechanism'
from _get_cogs_value which solves problem 3
Additionally, instead of calling get_price_unit we can directly
call the super method _get_cogs_price_unit as we have
already computed all the components quantities needed
for our computation and therefore don't need
_get_kit_price_unit to recompute all of this.
Also, _get_cogs_price_unit will fall back on the product
standard price if the move has no value which solves
problem 1.
That will also prevent dividing twice by the quantity
of kit product in the bom (bom.product_qty)
which solves problem2.
**Tests:**
Out of the 9 existing tests of the class (that were skipped
before this PR) and after adapation to v19 valuation :
- 2 succeeded before and after the fix : this PR unskips them
- 5 failed before the fix and now suceed with the fix : this PR
unskips them
- 2 failed before the fix and after the fix, they were let
skipped
In addition, 2 tests were added to cover problem 1 and 3
(problem 2 is covered in test test_sale_mrp_kit_bom_cogs)
Forward-Port-Of: odoo/odoo#270675
Forward-Port-Of: odoo/odoo#270075This update resolves an issue where users couldn't complete delivery preset orders in self-ordering mode if the Google Places API key wasn't set up. Now, the system automatically fills in addresses via the API if the key is present, or accepts manual address entry if it's not. This ensures a smoother ordering experience for all users.
Original PR description
Before this commit: =================== If the Google Places Autocomplete API key was not configured for the company, users could not proceed with delivery preset orders in self-ordering mode because the complete address could not be retrieved from the API. After this commit: ================== - If the API key is configured: The address is fetched using the Google Places Autocomplete API. - If the API key is not configured: The system accepts the address entered manually by the user. task-6213436 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This update fixes an error in the Luxembourg eCDF XML export that was causing incorrect financial year data. Specifically, an issue with account 142 was resolved by removing it from the export mapping, ensuring accurate reporting on the Odoo Profit and Loss visualization. A new test has been added to verify the fix.
Original PR description
Issue: Users reported that the financial year result in the XML export for the Luxembourg eCDF platform is incorrect, despite being correct in the Odoo Profit and Loss visualization. The exported XML populated incorrect amounts in cell 0161 under certain circumstances (namely, in the case of an explicit entry from account 999999 to account 142000). Solution: * Removed account 142 entirely from both the `ACCOUNTS_2019` and `ACCOUNTS_2020` dictionaries so it no longer auto-populates cells 0161/0162 (up to 2019 included) and 2955/2956 (from 2020 onward). * Removed the 2019 threshold condition in the loop bypass for account 142. * Removed the hard-coded manual pop for cell 2955 since it has been removed from the mapping. * Deleted the redundant reassignment of `net142` in the loss calculation block. Ticket [link](https://www.odoo.com/odoo/project.task/6059571) opw-6059571 Forward-Port-Of: odoo/enterprise#121010
This update resolves an issue where the inventory valuation closing entry incorrectly calculated accounting balances for companies with multiple stock locations. The fix ensures the closing entry accurately reflects the stock valuation for each company, preventing discrepancies in accounting balances. This ensures accurate financial reporting across all company setups.
Original PR description
**Steps to reproduce on a new db:** (bug also reproducable on runbot but the impact is less easy to compute because of influence of other existing companies) - create a new company as company 2 and…
**Steps to reproduce on a new db:** (bug also reproducable on runbot but the impact is less easy to compute because of influence of other existing companies) - create a new company as company 2 and use the existing default company as company 1. - create a warehouse for both company - for both comp, in settings for the 'fiscal localization' set Package : Generic Chart of account, if not already set (to have account journals). - for both comp, in settings for inventory valuation set 'periodic' and for periodic valuation set 'daily' From company 1 : - create a storable product with standard price method and set a cost of 30 - set an onhand quantity of 1 if you navigate to 'inventory valuation' you'll see that : - initial balance is 0 - ending stock is 30 - the variation lines have a balance of 30 - all of this is expected From company 2 : - change the cost of the product to 10 - set an onhand quantity of 1 if you navigate to 'inventory valuation' you'll see that : - initial balance is 0 - ending stock is 10 - the variation lines have a balance of 10 - all of this is expected From any company : - navigate to 'scheduled actions' and select the action 'Stock Account: Inventory Valuation Closing' - click on 'Run Manually' - navigate to 'inventory valuation' **Current behavior:** with company 1 selected : - the initial balance is now 30 - ending stock still 30 - no variation lines - the initial balance was correctly increased by the closing entry with company 2 selected: - the initial balance is now 40 - the ending stock is still 10 - the variation lines credit 30 in stock valuation In company 2 the closing entry debitted 40 in stock valuation instead of 10 which increased the initial balance to 40 instead of 10 If you open the journal items you'll find the closing amls have a balance of 40 instead of 10 **Cause of the issue:** The _cron_post_stock_valuation() method calls action_close_stock_valuation() on both companies https://github.com/odoo/odoo/blob/bfa39854e56da4bf23295d62f63d66973ad0d78e/addons/stock_account/models/res_company.py#L143-L144 This methods calls _action_close_stock_valuation with a context modified with only self.env.company.ids in 'allowed_company_ids' https://github.com/odoo/odoo/blob/bfa39854e56da4bf23295d62f63d66973ad0d78e/addons/stock_account/models/res_company.py#L56 This is needed because inside stock_value() we use the total value of the product https://github.com/odoo/odoo/blob/bfa39854e56da4bf23295d62f63d66973ad0d78e/addons/stock_account/models/res_company.py#L92 which will be the sum of the values of the product for each company inside allowed_company_id https://github.com/odoo/odoo/blob/bfa39854e56da4bf23295d62f63d66973ad0d78e/addons/stock_account/models/product.py#L274 So in case action_close_stock_valuation() was called from the 'generate entry' button from the inventory valuation view we need only the main company selected to be in the 'allowed_company_ids' so that the inventory value is computed based only on this company (as is the accounting value). The problem is that this does not work when calling the method from _cron_post_stock_valuation because then there is no 'allowed_company_ids' in the context (because it was called from _process_job() with a new env). so self.env.company will be the company of the user which will be company 1. https://github.com/odoo/odoo/blob/bfa39854e56da4bf23295d62f63d66973ad0d78e/odoo/orm/environments.py#L243 Therefore when _action_close_stock_valuation will be called on company 2, in the context, allowed_company_ids will be company 1. Then, when computing 'products', with_company() will add self (company 2) to the context. https://github.com/odoo/odoo/blob/616e82d7b3a53b1facf481e783baed3e99393d3c/addons/stock_account/models/res_company.py#L151-L152 So stock_value will return the sum of the total_value of each product for company 1 and company 2 which is 40 (instead of 10 for just company 2) https://github.com/odoo/odoo/blob/616e82d7b3a53b1facf481e783baed3e99393d3c/addons/stock_account/models/res_company.py#L242 We then create the closing accounting entry to match the accounting value with the stock value, which explains why the new initial accounting balance of company 2 is 40. **fix:** We set the context using self instead of self.env.companies This makes more sense as both in the cron use case and the generate entry use case the stock value we want is the one of the company in self. - In cron use case, it's obvious as the method is called in a for loop on each company - In the generate entry use case, self will also be the main company, because it's called, in actionGenerateEntry, on this.companyId https://github.com/odoo/odoo/blob/616e82d7b3a53b1facf481e783baed3e99393d3c/addons/stock_account/static/src/stock_valuation/controller.js#L75 which is computed based on the get_report_values https://github.com/odoo/odoo/blob/616e82d7b3a53b1facf481e783baed3e99393d3c/addons/stock_account/static/src/stock_valuation/controller.js#L21 https://github.com/odoo/odoo/blob/616e82d7b3a53b1facf481e783baed3e99393d3c/addons/stock_account/static/src/stock_valuation/controller.js#L28-L30 Which returns the main company https://github.com/odoo/odoo/blob/616e82d7b3a53b1facf481e783baed3e99393d3c/addons/stock_account/report/stock_valuation_report.py#L29 Most importantly, this is also aligned with how the accounting values are computed. https://github.com/odoo/odoo/blob/616e82d7b3a53b1facf481e783baed3e99393d3c/addons/stock_account/models/res_company.py#L103-L105 opw-6237402 Forward-Port-Of: odoo/odoo#269152 Forward-Port-Of: odoo/odoo#266932
This update optimizes how Odoo searches for documents, specifically addressing a slow and complex query when filtering by 'SHARED'. The change aligns with the production database's approach, resulting in faster search speeds and a more efficient system. This improves the overall user experience when searching for documents.
Original PR description
Searching for "not 'SHARED'" results in a very complex query. Our own production DB prefers this implementation, also easier to read. credit: https://github.com/odoo/enterprise/pull/105915#discussion_r2745148099 Task-5893183 Forward-Port-Of: odoo/enterprise#120870
This update ensures the 'pdp_identifier' field is correctly populated during company partner registration, particularly when using the PEPPOL EAS standard. Previously, the field would be left blank, causing issues with registration. Now, a user error message will appear if an invalid identifier is entered, preventing incorrect data and improving the registration process.
Original PR description
Currently when the company partner uses non 0225 peppol EAS the `pdp_identifier` field is `False`. Thus the (related) identifier field on the registration wizard is left empty. Also add a UserError when writing an invalid identifier to the `pdp_identifier` field instead of just silently failing. That way an error ill pop up in the registration wizard when trying to register with an invalid identifier. task-6307489 Forward-Port-Of: odoo/odoo#270915 Forward-Port-Of: odoo/odoo#270330
This update resolves an issue where DIAN XML files for Point of Sale (PoS) payments were being rejected due to incorrect calculations of prepaid amounts. The fix combines payment amounts into a single tag, ensuring accurate data transmission to the DIAN and preventing errors related to negative payment lines.
Original PR description
**Steps to reproduce:** To test this, you will need an official DIAN setup, because this error comes from the response to our API call to the DIAN. - Setup the DIAN in a colombian company - Open the…
**Steps to reproduce:** To test this, you will need an official DIAN setup, because this error comes from the response to our API call to the DIAN. - Setup the DIAN in a colombian company - Open the PoS - Order a product - Before paying, make the amount we are paying bigger than the amount due - We get an error response from the API, the error is saying that the total due does not match what we paid **Why the fix:** Currently, the xml is rejected because the sum of the **PaidAmount** in the **PrepaidPayment** tag is not equal to what we are trying to pay for. This is happening because to avoid the fact that we can not send a line with negative amount, we used the **abs()** function on the line amount to make it positive. The negative line comes from the fact that when we have a total due that is below the amount paid, we create a new payment line with a negative amount to balance it out. But as we can't send lines with negative amount, we needed to make it positive. This does not work, as the sum of the lines' amount will then be too much compared to what we are paying for, because instead of substracting it we will be adding it. To avoid this, we now group the amount in one single tag and send it this way. This ensures that the sent amount is correct and equals the amount due, and does not send a negative line. opw-6232575 Forward-Port-Of: odoo/enterprise#121075 Forward-Port-Of: odoo/enterprise#119255
This update resolves an issue where invoices with reverse charge VAT in Poland (fa3) were generating incorrect XML files for transmission to the tax authorities (KSEF). Specifically, the XML lacked the necessary information to accurately reflect the reverse charge amount and total sale value. This ensures proper tax reporting and compliance.
Original PR description
**STEP TO REPRODUCE** 1. Create an invoice with a tax with reverse charge (0% EU G for example). 2. Send the invoice to ksef. 3. Open the generated xml, and notice field P_18 is 2 while it should be 1 (because there is reverse charge). Also, there is not P13_10 indicated the total value of sale to which the reverse charge applies. opw-6041836 Forward-Port-Of: odoo/odoo#263764
This update fixes an issue preventing the 'Send to SII' option from appearing on newly generated vendor bills in Chile. The fix adjusts internal code to correctly display this option when a document is generated and awaiting electronic filing, ensuring compliance with Chilean tax regulations. This ensures users can properly submit their electronic invoices.
Original PR description
**Steps to reproduce:** * Install the **l10n_cl_edi** module. * Go to **Accounting → Configuration → CAFs**, create a new CAF, and upload a valid CAF…
**Steps to reproduce:** * Install the **l10n_cl_edi** module. * Go to **Accounting → Configuration → CAFs**, create a new CAF, and upload a valid CAF [XML](https://www.odoo.com/mail/message/1097235975) file. * Create a new **Purchase Journal** with **Use Documents** enabled. * Create a vendor bill using this journal. * Set the **Document Type** to **46 - Liquidación-Factura Electrónica**. * Confirm the vendor bill. **Observed behavior:** * The Send button is not visible on the confirmed vendor bill despite the DTE being generated and `l10n_cl_dte_status` being set to `not_sent`. **Cause:** * `_compute_display_send_button` in `account` only returns `True` for sale documents (`is_sale_document()`), so the "Send" button — which opens the Send & Print dialog containing the "Send to SII" option — was never shown on vendor bills. * `_get_move_constraints` in `account.move.send` unconditionally adds a `not_sale_document` constraint for non-sale documents, blocking the Send & Print dialog from processing vendor bills even if the button were visible. * The cron's `cron_run_sii_workflow` only processes moves with `l10n_cl_dte_status = 'ask_for_status'`, skipping moves still in `not_sent` state. **Fix:** * Override `_compute_display_send_button` in `l10n_cl_edi` to also show the "Send" button on posted moves with `l10n_cl_dte_status == 'not_sent'`, matching the pattern used by `l10n_br_edi`. * Override `_get_move_constraints` in `l10n_cl_edi` to remove the `not_sale_document` constraint for Chilean purchase documents with `not_sent` status, matching the pattern used by `l10n_br_edi`. **REF** During this [refactor](https://github.com/odoo/enterprise/pull/103427/changes/f5617ecf7584cf019897408df94b002622f48d9d), these two methods were inadvertently missed and were not overridden opw-6300571 Forward-Port-Of: odoo/enterprise#120818
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, improving financial accuracy.
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#270743 Forward-Port-Of: odoo/odoo#267757
This update resolves an error that occurred when reloading chart templates after removing account codes. The recent change allowed for optional account codes, but the reload process incorrectly attempted to match them, leading to a 'bool' type error. This fix ensures that accounts without codes are handled correctly during the reload process, preventing the error and maintaining chart template functionality.
Original PR description
Currently, an error occurs when reloading a chart template. Steps to Reproduce: - Install the `Accounting` module with demo data. - Go to `Accounting` > `Configuration` > `Accounting` > `Chart of…
Currently, an error occurs when reloading a chart template. Steps to Reproduce: - Install the `Accounting` module with demo data. - Go to `Accounting` > `Configuration` > `Accounting` > `Chart of Accounts`. - Open the `Current Assets` account and remove its account code. - Go to `Settings` and click the `Reload` button under `Fiscal Localization`. `TypeError: expected string or bytes-like object, got 'bool'` After this [recent commit], account codes became optional and can be removed. As a result, when the code is removed from the Fixed Assets account and the chart template is reloaded, the reload process tries to match template accounts [1] with existing accounts [2]. It retrieves the existing account using its id [3] and correctly finds the account record, but its code field is False. Later, when the reload logic compares the existing account code with the template account code, it attempts to use account code in a regular expression, which raises error [4]. This commit ensures that accounts without a code are treated as having a non-matching code. [recent commit]: https://github.com/odoo/odoo/commit/c3313b336b9f1305c363097745926f2bdf61e277 [1]- https://github.com/odoo/odoo/blob/b04678b130438d5ee1605fadb9ca567930691fdf/addons/account/models/chart_template.py#L1153-L1155 [2]: https://github.com/odoo/odoo/blob/b04678b130438d5ee1605fadb9ca567930691fdf/addons/account/models/chart_template.py#L332 [3]: https://github.com/odoo/odoo/blob/b04678b130438d5ee1605fadb9ca567930691fdf/addons/account/models/chart_template.py#L435 [4]- https://github.com/odoo/odoo/blob/b04678b130438d5ee1605fadb9ca567930691fdf/addons/account/models/chart_template.py#L439 sentry-7556472625 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This update resolves an issue where POS orders with tracked products and GS1 barcodes would fail to validate due to an incorrect search for existing lots. The fix ensures that lot names, even when partially matching GS1 barcodes, are correctly identified, preventing duplicate lot creation errors. This improves the reliability of POS order processing.
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 the system incorrectly forecasted expiring perishable products, leading to unnecessary reordering recommendations. The fix clarifies how the system handles expiration dates, ensuring accurate stock availability and preventing an endless replenishment cycle. This improves inventory management and reduces potential waste.
Original PR description
Currently when the user receives a perishable product that expires in future, it is considered for reordering regardless if the user has maximum quantity or not. ## Steps to produce: - Install…
Currently when the user receives a perishable product that expires in future, it is considered for reordering regardless if the user has maximum quantity or not. ## Steps to produce: - Install Inventory - Go to settings > Enable 'Lots and Serial Numbers' and 'Expiration Dates' - Create a product 'Vegetable Oil' that is tracked by lots. - Enable Expiration date in the 'Inventory' Section and set Removal date to 2. - Create a receipt for 'Vegetable oil' with Demand 10 and mark it as todo - Details > Set 'Expiration Date' into the future > Set a Lot Number and Save - Validate the receipt - Open Product Form for Vegetable oil > Reordering Rules. - Create a new Reordering rule with Min 5 and Max 10 and save. ## Observed Behavior: The forecasted quantity is calculated as zero, resulting in a quantity to order of 10, even though no replenishment is actually required. The product already satisfies the maximum quantity defined on the reordering rule, and there is no existing demand, as there are no delivery orders or sales order reservations for the product. **Why this is an issue:** When the user navigates from the product form to the On Hand Quantity view to verify the stock situation, the forecasted quantity is shown as 10. This is inconsistent with the value displayed on the reordering rule, creating confusion and making it difficult to understand the actual inventory status. In addition, if the product is configured with a Buy route, running the `Procurement: Run Scheduler` action repeatedly generates new purchase orders for the perishable product. Even if the generated purchase orders are cancelled or completed, since newly purchased stock will also have an expiration date. As a result, the same incorrect forecast calculation occurs again, causing the scheduler to continuously create new purchase orders and leading to an endless replenishment cycle if they expire within horizon days. ## Root cause: When an orderpoint is created or updated, `_compute_qty_to_order` [1] is triggered. This method calls `_compute_qty_to_order_computed` [2] which accesses the forecasted quantity, causing its compute method to called. The forecast computation retrieves context from `_get_product_context` as seen in [3], where the lead horizon date (route lead time + horizon days configured in settings) is passed as `to_date` at [4]. It then reads the product's `virtual_available` quantity using the orderpoint context at [5]. This ultimately invokes `_compute_quantities`, which delegates the calculation of `virtual_available` to `_compute_quantities_dict`, as shown in [6]. As a result, `max_date` is set to the lead horizon date at [7] (for example, one year in the future). Since the product expires before that date, it is included in `expired_unreserved_quant_res`, causing `virtual_available` to be reduced to zero at [8]. Consequently, the forecasted quantity also becomes zero at [9]. Because the forecasted quantity falls below the orderpoint's minimum quantity, a replenishment is incorrectly triggered. **Why did this issue not occur in previous versions?** This behavior was introduced by [commit](https://github.com/odoo/odoo/commit/8ba2c1e38b636c567511139c5e19b7189430a182 ), which fixed the calculation of fresh(unexpired) quantity displayed in the stock availability widget on sales order lines. Which expects that `scheduled_date` (typically the committed delivery date or expected delivery date, including leadtime) is passed to `read_qties` at [10] , which stores it in the context using the `to_date` key. However, the same `to_date` key is also used by the orderpoint horizon-days logic. This overlap causes the expiration-aware quantity computation to use the horizon date instead of the intended `with_expiration` date , leading to incorrect forecast calculations and the unexpected replenishment behavior described above. [1]- https://github.com/odoo/odoo/blob/9ae1df190cf5ee6a5775eddb32be7b13f5ed92c9/addons/stock/models/stock_orderpoint.py#L394-L396 [2]-https://github.com/odoo/odoo/blob/96b7eed9153eab60288d1624252df6f90ea9505e/addons/stock/models/stock_orderpoint.py#L417-L427 [3]- https://github.com/odoo/odoo/blob/96b7eed9153eab60288d1624252df6f90ea9505e/addons/stock/models/stock_orderpoint.py#L374-L381 [4]- https://github.com/odoo/odoo/blob/9ae1df190cf5ee6a5775eddb32be7b13f5ed92c9/addons/stock/models/stock_orderpoint.py#L484-L491 [5]- https://github.com/odoo/odoo/blob/96b7eed9153eab60288d1624252df6f90ea9505e/addons/stock/models/stock_orderpoint.py#L386 [6]- https://github.com/odoo/odoo/blob/9ae1df190cf5ee6a5775eddb32be7b13f5ed92c9/addons/stock/models/product.py#L152-L154 [7]- https://github.com/odoo/odoo/blob/9ae1df190cf5ee6a5775eddb32be7b13f5ed92c9/addons/stock/models/product.py#L215-L218 [8]- https://github.com/odoo/odoo/blob/9ae1df190cf5ee6a5775eddb32be7b13f5ed92c9/addons/stock/models/product.py#L260-L262 [9]- https://github.com/odoo/odoo/blob/9ae1df190cf5ee6a5775eddb32be7b13f5ed92c9/addons/stock/models/stock_orderpoint.py#L391 [10]- https://github.com/odoo/odoo/blob/afa2b6b7b47d6420146ceb6dad897405aa92c682/addons/sale_stock/models/sale_order_line.py#L129 ## Solution It does not make sense to trigger replenishment for perishable products solely because they are expected to expire before the horizon date. Even when the available quantity already satisfies the maximum quantity defined on the reordering rule, the current logic forecasts those products as unavailable in advance of their expiration. Instead, products should only be excluded from the forecast once they have actually expired, or when they must be removed according to the original expiration-handling logic. To achieve this, an additional context key can be introduced to distinguish calls originating from the forecast availability widget from SO line. When the computation is performed for the forecast widget, the existing `to_date` context key should continue to be used so that availability is evaluated at the requested future date. For all other flows, including orderpoint calculations, the system should rely on the `with_expiration` context key instead. This preserves the original behavior, where only already-expired quantities (or quantities that must be removed due to expiration rules) are excluded from availability calculations, preventing incorrect replenishment recommendations for perishable products. opw-6200644 Forward-Port-Of: odoo/odoo#268223
This update resolves an issue where reducing the PO quantity after a partial receipt in multi-step warehouses incorrectly updated the remaining backorder demand. The fix ensures accurate quantity calculations by considering the current move status, 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#269666 Forward-Port-Of: odoo/odoo#248626
This update resolves a layout issue where the 'Cancel' button in the product screen's modal would float. The change adjusts the button's sizing and grid layout to ensure it fits correctly on various screen sizes, particularly tablets, improving the user experience. This ensures a consistent and functional layout for all users.
Original PR description
This PR fixes the issue of the Cancel button floating on the last row when the buttons wrap and other overflowing issues. Before this PR, we were targetting the screen's orientation and max-height, which worked in general but still let a few layout issues through. On tablets the buttons are large and squarish for better touch usability (which has the double function of leaving plenty of space for translations), this makes fitting them within the modal container without overflowing a bit more complex. Instead, we target ranges of the aspect-ratio of the screen and adjust the buttons squarish aspect-ratio and the number of grid columns accordingly. By controlling the grid's columns we're able to tell the last button (the Cancel button) to stretch to full width when needed as well as having a more balanced layout in both landscape and portrait views. task-6235164 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This update corrects a rejection issue with the 3519 VAT reimbursement form, which was being flagged by the French tax authority (DGFiP). The fix ensures the correct 'millesime' (form version year) is used when generating the VAT report, resolving compatibility problems and allowing the form to be accepted. This prevents delays in VAT reimbursement processing.
Original PR description
The 3519 reimbursement form is rejected by the DGFiP with "Le millesime 25 du formulaire 3519 est inconnu dans la teleprocedure TVA". The 3310CA3 return is still accepted, because its layout is unchanged year-on-year, which hides the problem, but it is sent with a millesime that no longer matches the campaign. The millesime is the form-version year. The EDI-TVA 2026 campaign opened on 2026-02-09. last update: https://github.com/odoo/enterprise/pull/92542 opw-6275695 Forward-Port-Of: odoo/enterprise#120759
This update ensures that when users simultaneously rename multiple projects from the list view, the linked folder names are automatically updated as well. Previously, the system didn't correctly reflect these changes, leading to inconsistencies. This fix improves data accuracy and simplifies project management workflows.
Original PR description
Currently, when user multi-edits projects names from list view the linked folder name doesnt get updated. Steps to replicate: - Install `documents_project` and open projects. - Select multiple projects and edit their names. Issue: - The project names get updated but their respective linked folder's name doesnt get updated. Cause: - During multi-edit, `self.documents_folder_id` contains the folders of all selected projects. - As a result, `len(self.documents_folder_id.project_ids) == 1` [1] is evaluated on the combined recordset instead of per project, causing the condition to fail whenever multiple projects are renamed. Solution: - Avoided accessing `self.name` on a `multi-recordset` during multi-edit. - Filtered projects individually and updated their document folders using the name in vals. [1]: https://github.com/odoo/enterprise/blob/3c2985ca6011700c271ed14e40e08c89be822753/documents_project/models/project_project.py#L101 sentry-7452096418
This update significantly speeds up how Odoo retrieves document access permissions, particularly for large databases like odoo.com. By switching to a subquery, the system now utilizes an index more efficiently, resulting in a much faster response time. This improves overall performance and user experience.
Original PR description
The '/my/counters' route is hit a lot of times on big databases like odoo.com One thing it does is a `self.env['documents.document].search_count([])` With this commit, we use a subquery for the…
The '/my/counters' route is hit a lot of times on big databases like odoo.com
One thing it does is a `self.env['documents.document].search_count([])`
With this commit, we use a subquery for the folder access instead of the current LEFT JOIN.
This ok since the number of folders is typically small compared to regular documents and the query is fast since it can use the index on 'type'
Before as portal user
------
2x Seq Scan
```
Aggregate (cost=1900290.73..1900290.74 rows=1 width=8) (actual time=282.271..282.276 rows=1 loops=1)
Buffers: shared hit=66629
-> Hash Left Join (cost=41649.94..1900044.55 rows=98472 width=0) (actual time=184.202..282.267 rows=3 loops=1)
Hash Cond: (documents_document.folder_id = documents_document__folder_id.id)
Filter: ((hashed SubPlan 2) OR ((documents_document.owner_id = 6) AND ((documents_document.shortcut_document_id IS NULL) OR (documents_document.shortcut_document_owner_id = 6))) OR (((documents_document.access_via_link)::text = ANY ('{edit,view}'::text[])) AND (documents_document.folder_id IS NOT NULL) AND ((hashed SubPlan 4) OR ((documents_document__folder_id.owner_id = 6) AND ((documents_document__folder_id.shortcut_document_id IS NULL) OR (documents_document__folder_id.shortcut_document_owner_id = 6)))) AND (documents_document.is_access_via_link_hidden IS NOT TRUE)))
Rows Removed by Filter: 28085
Buffers: shared hit=66629
-> Seq Scan on documents_document (cost=0.00..1857903.54 rows=187073 width=26) (actual time=0.022..109.288 rows=28088 loops=1)
Filter: ((active IS TRUE) AND ((hashed SubPlan 2) OR ((owner_id = 6) AND ((shortcut_document_id IS NULL) OR (shortcut_document_owner_id = 6))) OR (((access_via_link)::text = ANY ('{edit,view}'::text[])) AND (folder_id IS NOT NULL) AND (is_access_via_link_hidden IS NOT TRUE))))
Rows Removed by Filter: 342576
Buffers: shared hit=33313
SubPlan 2
-> Nested Loop (cost=0.85..357.41 rows=99 width=4) (actual time=0.008..0.009 rows=0 loops=2)
Buffers: shared hit=6
-> Index Scan using documents_access__partner_id_index on documents_access (cost=0.43..105.55 rows=103 width=9) (actual time=0.008..0.008 rows=0 loops=2)
Index Cond: (partner_id = 7)
Filter: ((expiration_date IS NULL) OR (expiration_date >= '2026-06-18 10:16:38'::timestamp without time zone))
Buffers: shared hit=6
-> Index Scan using documents_document_pkey on documents_document documents_access__document_id (cost=0.42..2.44 rows=1 width=9) (never executed)
Index Cond: (id = documents_access.document_id)
Filter: (((access_via_link)::text <> 'none'::text) OR ((documents_access.role)::text = ANY ('{view,edit}'::text[])))
-> Hash (cost=37016.64..37016.64 rows=370664 width=16) (actual time=164.521..164.521 rows=370664 loops=1)
Buckets: 524288 Batches: 1 Memory Usage: 17824kB
Buffers: shared hit=33310
-> Seq Scan on documents_document documents_document__folder_id (cost=0.00..37016.64 rows=370664 width=16) (actual time=0.005..100.491 rows=370664 loops=1)
Buffers: shared hit=33310
SubPlan 4
-> Nested Loop (cost=0.85..357.41 rows=99 width=4) (actual time=0.003..0.003 rows=0 loops=1)
Buffers: shared hit=3
-> Index Scan using documents_access__partner_id_index on documents_access documents_access_1 (cost=0.43..105.55 rows=103 width=9) (actual time=0.002..0.003 rows=0 loops=1)
Index Cond: (partner_id = 7)
Filter: ((expiration_date IS NULL) OR (expiration_date >= '2026-06-18 10:16:38'::timestamp without time zone))
Buffers: shared hit=3
-> Index Scan using documents_document_pkey on documents_document documents_access__document_id_1 (cost=0.42..2.44 rows=1 width=9) (never executed)
Index Cond: (id = documents_access_1.document_id)
Filter: (((access_via_link)::text <> 'none'::text) OR ((documents_access_1.role)::text = ANY ('{view,edit}'::text[])))
Planning:
Buffers: shared hit=69
Planning Time: 1.708 ms
Execution Time: 282.344 ms
```
After as portal user
-----
Only 1x Seq Scan
```
Aggregate (cost=2004948.33..2004948.34 rows=1 width=8) (actual time=116.161..116.165 rows=1 loops=1)
Buffers: shared hit=37942
-> Seq Scan on documents_document (cost=145660.16..2004490.36 rows=183187 width=0) (actual time=24.635..116.155 rows=3 loops=1)
Filter: ((active IS TRUE) AND ((hashed SubPlan 2) OR ((owner_id = 6) AND ((shortcut_document_id IS NULL) OR (shortcut_document_owner_id = 6))) OR (((access_via_link)::text = ANY ('{edit,view}'::text[])) AND (hashed SubPlan 5) AND (is_access_via_link_hidden IS NOT TRUE))))
Rows Removed by Filter: 370661
Buffers: shared hit=37942
SubPlan 2
-> Nested Loop (cost=0.85..357.41 rows=99 width=4) (actual time=0.008..0.009 rows=0 loops=1)
Buffers: shared hit=3
-> Index Scan using documents_access__partner_id_index on documents_access (cost=0.43..105.55 rows=103 width=9) (actual time=0.008..0.008 rows=0 loops=1)
Index Cond: (partner_id = 7)
Filter: ((expiration_date IS NULL) OR (expiration_date >= '2026-06-18 10:15:10'::timestamp without time zone))
Buffers: shared hit=3
-> Index Scan using documents_document_pkey on documents_document documents_access__document_id (cost=0.42..2.44 rows=1 width=9) (never executed)
Index Cond: (id = documents_access.document_id)
Filter: (((access_via_link)::text <> 'none'::text) OR ((documents_access.role)::text = ANY ('{view,edit}'::text[])))
SubPlan 5
-> Index Scan using documents_document__type_index on documents_document documents_document_1 (cost=0.42..145625.73 rows=13772 width=4) (actual time=11.688..11.689 rows=0 loops=1)
Index Cond: ((type)::text = 'folder'::text)
Filter: ((hashed SubPlan 4) OR ((owner_id = 6) AND ((shortcut_document_id IS NULL) OR (shortcut_document_owner_id = 6))))
Rows Removed by Filter: 28198
Buffers: shared hit=4629
SubPlan 4
-> Nested Loop (cost=0.85..357.41 rows=99 width=4) (actual time=0.002..0.002 rows=0 loops=1)
Buffers: shared hit=3
-> Index Scan using documents_access__partner_id_index on documents_access documents_access_1 (cost=0.43..105.55 rows=103 width=9) (actual time=0.001..0.002 rows=0 loops=1)
Index Cond: (partner_id = 7)
Filter: ((expiration_date IS NULL) OR (expiration_date >= '2026-06-18 10:15:10'::timestamp without time zone))
Buffers: shared hit=3
-> Index Scan using documents_document_pkey on documents_document documents_access__document_id_1 (cost=0.42..2.44 rows=1 width=9) (never executed)
Index Cond: (id = documents_access_1.document_id)
Filter: (((access_via_link)::text <> 'none'::text) OR ((documents_access_1.role)::text = ANY ('{view,edit}'::text[])))
Planning:
Buffers: shared hit=56
Planning Time: 1.544 ms
Execution Time: 116.216 ms
```
Before as internal user
--------
```
Aggregate (cost=1902165.43..1902165.44 rows=1 width=8) (actual time=332.919..332.925 rows=1 loops=1)
Buffers: shared hit=69223 read=370
-> Hash Left Join (cost=41649.94..1901908.04 rows=102955 width=0) (actual time=176.179..332.325 rows=10040 loops=1)
Hash Cond: (documents_document.folder_id = documents_document__folder_id.id)
Filter: ((hashed SubPlan 2) OR ((documents_document.owner_id = 1054906) AND ((documents_document.shortcut_document_id IS NULL) OR (documents_document.shortcut_document_owner_id = 1054906))) OR (((documents_document.access_internal)::text = ANY ('{view,edit}'::text[])) AND ((documents_document.company_id = 1) OR (documents_document.company_id IS NULL))) OR (((documents_document.access_via_link)::text = ANY ('{view,edit}'::text[])) AND (documents_document.folder_id IS NOT NULL) AND ((hashed SubPlan 4) OR ((documents_document__folder_id.owner_id = 1054906) AND ((documents_document__folder_id.shortcut_document_id IS NULL) OR (documents_document__folder_id.shortcut_document_owner_id = 1054906))) OR (((documents_document__folder_id.access_internal)::text = ANY ('{view,edit}'::text[])) AND ((documents_document__folder_id.company_id = 1) OR (documents_document__folder_id.company_id IS NULL)))) AND (documents_document.is_access_via_link_hidden IS NOT TRUE)))
Rows Removed by Filter: 27228
Buffers: shared hit=69223 read=370
-> Seq Scan on documents_document (cost=0.00..1859756.86 rows=190950 width=35) (actual time=15.029..155.718 rows=37268 loops=1)
Filter: ((active IS TRUE) AND ((hashed SubPlan 2) OR ((owner_id = 1054906) AND ((shortcut_document_id IS NULL) OR (shortcut_document_owner_id = 1054906))) OR (((access_internal)::text = ANY ('{view,edit}'::text[])) AND ((company_id = 1) OR (company_id IS NULL))) OR (((access_via_link)::text = ANY ('{view,edit}'::text[])) AND (folder_id IS NOT NULL) AND (is_access_via_link_hidden IS NOT TRUE))))
Rows Removed by Filter: 333396
Buffers: shared hit=33931 read=370
SubPlan 2
-> Nested Loop (cost=0.85..357.41 rows=99 width=4) (actual time=0.155..7.920 rows=148 loops=2)
Buffers: shared hit=1612 read=370
-> Index Scan using documents_access__partner_id_index on documents_access (cost=0.43..105.55 rows=103 width=9) (actual time=0.110..3.448 rows=200 loops=2)
Index Cond: (partner_id = 1800102)
Filter: ((expiration_date IS NULL) OR (expiration_date >= '2026-06-18 12:17:29'::timestamp without time zone))
Buffers: shared hit=192 read=190
-> Index Scan using documents_document_pkey on documents_document documents_access__document_id (cost=0.42..2.44 rows=1 width=9) (actual time=0.022..0.022 rows=1 loops=400)
Index Cond: (id = documents_access.document_id)
Filter: (((access_via_link)::text <> 'none'::text) OR ((documents_access.role)::text = ANY ('{view,edit}'::text[])))
Rows Removed by Filter: 0
Buffers: shared hit=1420 read=180
-> Hash (cost=37016.64..37016.64 rows=370664 width=25) (actual time=157.822..157.823 rows=370664 loops=1)
Buckets: 524288 Batches: 1 Memory Usage: 21336kB
Buffers: shared hit=33310
-> Seq Scan on documents_document documents_document__folder_id (cost=0.00..37016.64 rows=370664 width=25) (actual time=0.005..96.730 rows=370664 loops=1)
Buffers: shared hit=33310
SubPlan 4
-> Nested Loop (cost=0.85..357.41 rows=99 width=4) (actual time=0.019..0.372 rows=148 loops=1)
Buffers: shared hit=991
-> Index Scan using documents_access__partner_id_index on documents_access documents_access_1 (cost=0.43..105.55 rows=103 width=9) (actual time=0.005..0.078 rows=200 loops=1)
Index Cond: (partner_id = 1800102)
Filter: ((expiration_date IS NULL) OR (expiration_date >= '2026-06-18 12:17:29'::timestamp without time zone))
Buffers: shared hit=191
-> Index Scan using documents_document_pkey on documents_document documents_access__document_id_1 (cost=0.42..2.44 rows=1 width=9) (actual time=0.001..0.001 rows=1 loops=200)
Index Cond: (id = documents_access_1.document_id)
Filter: (((access_via_link)::text <> 'none'::text) OR ((documents_access_1.role)::text = ANY ('{view,edit}'::text[])))
Rows Removed by Filter: 0
Buffers: shared hit=800
Planning:
Buffers: shared hit=69 read=8
Planning Time: 2.116 ms
Execution Time: 333.013 ms
```
After as internal user
--------
```
Aggregate (cost=2006950.17..2006950.18 rows=1 width=8) (actual time=157.117..157.121 rows=1 loops=1)
Buffers: shared hit=39918
-> Seq Scan on documents_document (cost=145798.74..2006482.26 rows=187165 width=0) (actual time=16.595..156.590 rows=10040 loops=1)
Filter: ((active IS TRUE) AND ((hashed SubPlan 2) OR ((owner_id = 1054906) AND ((shortcut_document_id IS NULL) OR (shortcut_document_owner_id = 1054906))) OR (((access_internal)::text = ANY ('{view,edit}'::text[])) AND ((company_id = 1) OR (company_id IS NULL))) OR (((access_via_link)::text = ANY ('{view,edit}'::text[])) AND (hashed SubPlan 5) AND (is_access_via_link_hidden IS NOT TRUE))))
Rows Removed by Filter: 360624
Buffers: shared hit=39918
SubPlan 2
-> Nested Loop (cost=0.85..357.41 rows=99 width=4) (actual time=0.019..1.016 rows=148 loops=1)
Buffers: shared hit=991
-> Index Scan using documents_access__partner_id_index on documents_access (cost=0.43..105.55 rows=103 width=9) (actual time=0.012..0.262 rows=200 loops=1)
Index Cond: (partner_id = 1800102)
Filter: ((expiration_date IS NULL) OR (expiration_date >= '2026-06-18 12:16:29'::timestamp without time zone))
Buffers: shared hit=191
-> Index Scan using documents_document_pkey on documents_document documents_access__document_id (cost=0.42..2.44 rows=1 width=9) (actual time=0.004..0.004 rows=1 loops=200)
Index Cond: (id = documents_access.document_id)
Filter: (((access_via_link)::text <> 'none'::text) OR ((documents_access.role)::text = ANY ('{view,edit}'::text[])))
Rows Removed by Filter: 0
Buffers: shared hit=800
SubPlan 5
-> Index Scan using documents_document__type_index on documents_document documents_document_1 (cost=0.42..145763.43 rows=14124 width=4) (actual time=0.429..14.916 rows=4625 loops=1)
Index Cond: ((type)::text = 'folder'::text)
Filter: ((hashed SubPlan 4) OR ((owner_id = 1054906) AND ((shortcut_document_id IS NULL) OR (shortcut_document_owner_id = 1054906))) OR (((access_internal)::text = ANY ('{view,edit}'::text[])) AND ((company_id = 1) OR (company_id IS NULL))))
Rows Removed by Filter: 23573
Buffers: shared hit=5617
SubPlan 4
-> Nested Loop (cost=0.85..357.41 rows=99 width=4) (actual time=0.007..0.390 rows=148 loops=1)
Buffers: shared hit=991
-> Index Scan using documents_access__partner_id_index on documents_access documents_access_1 (cost=0.43..105.55 rows=103 width=9) (actual time=0.003..0.074 rows=200 loops=1)
Index Cond: (partner_id = 1800102)
Filter: ((expiration_date IS NULL) OR (expiration_date >= '2026-06-18 12:16:29'::timestamp without time zone))
Buffers: shared hit=191
-> Index Scan using documents_document_pkey on documents_document documents_access__document_id_1 (cost=0.42..2.44 rows=1 width=9) (actual time=0.001..0.001 rows=1 loops=200)
Index Cond: (id = documents_access_1.document_id)
Filter: (((access_via_link)::text <> 'none'::text) OR ((documents_access_1.role)::text = ANY ('{view,edit}'::text[])))
Rows Removed by Filter: 0
Buffers: shared hit=800
Planning:
Buffers: shared hit=56
Planning Time: 1.569 ms
Execution Time: 157.171 ms
```
portal user
before https://explain.dalibo.com/plan/e1e755fg7bb26a21
after https://explain.dalibo.com/plan/hb5fa1d201ff164g
internal user with few documents access
before https://explain.dalibo.com/plan/f753bf2aa244dg63
after https://explain.dalibo.com/plan/538dg5ecb120ch84
internal user with *lots* of documents access
before https://explain.dalibo.com/plan/cf76h84537f7ge4a
after https://explain.dalibo.com/plan/45317a5e3168c5bc
Forward-Port-Of: odoo/enterprise#120991The Odoo builder was experiencing performance issues due to excessive requests when the shape selector panel loaded. This fix delays the panel's content rendering until it's actually needed, preventing the UI from freezing and improving responsiveness. This change ensures a smoother user experience for builders.
Original PR description
The shape selector panel was eagerly compiling and rendering its slot content on builder startup, triggering 200+ concurrent SVG thumbnail requests before the user had opened the panel or selected an image. With browsers limiting parallel connections per domain, this flooded the request queue and caused the main UI to freeze for several seconds. Slot content is now deferred behind a `contentRendered` flag that is set the first time the panel is opened, so no compilation or network activity happens until the user actually needs it. task-5973702 Forward-Port-Of: odoo/odoo#266990 Forward-Port-Of: odoo/odoo#253041
This update automatically updates the map routes when a user moves, eliminating the need for manual refreshes. Previously, users had to trigger a refresh to see accurate routes, which was inconvenient. This change improves the user experience and ensures the map always reflects the user's location.
Original PR description
In this commit, we ensure that the map is updated with the newly computed routes if the user position changes. Prior to this commit, the user had to manually trigger an update to correctly view the updated routes.
This update fixes an issue where custom fields linked to employee/applicant records weren't being updated after a signature was completed. The fix ensures that linked fields are correctly synchronized, allowing for accurate record-keeping of signed documents. This improves data integrity within the HR and Recruitment modules.
Original PR description
Version - saas-19.3 Steps to Reproduce: 1. Create a custom Sign field (e.g. "Passport No") with "Update Field" enabled, linked to Employee (For Applicant, same any custom field). 2. Employee flow:…
Version - saas-19.3 Steps to Reproduce: 1. Create a custom Sign field (e.g. "Passport No") with "Update Field" enabled, linked to Employee (For Applicant, same any custom field). 2. Employee flow: Employee app -> open an employee -> gear icon -> Signature Request -> send. Applicant flow: Recruitment app -> Applicants list view -> select an applicant -> Actions -> Signature Request -> send. 3. Complete the signature. 4. Check the linked record's "Passport No" field -> field is not updated. Issue: The field linked to the employee/applicant record is not updated after signing. Cause: Both the `hr.contract.sign.document.wizard` and `hr.recruitment.sign.document.wizard` create the `sign.request` in `validate_signature()` without setting `reference_doc`. `_get_auto_field_target_record()` relies on `reference_doc` to resolve the record to sync auto fields against. With `reference_doc` empty, it returns `None`, so `sign.request._sync_auto_field_value()` skips the item before it ever reaches the write step. Solution: Set `reference_doc` to the corresponding `hr.employee`/`hr.applicant` record when building the `sign.request` values in `validate_signature()`, so auto fields linked to those models can resolve their target record and sync back normally after signing. taskid-6308532
This update resolves an issue where validating rental orders for products created as kits would trigger a 'record not found' error. The fix ensures that the system correctly handles the explosion of kit bills during validation, preventing this error and improving the reliability of rental order processing. This ensures rentals involving kit products function as expected.
Original PR description
### Steps to reproduce: - Enable rental transfer - Create a rentable product R - Create and confirm a rental order for 1 unit of R - Create a kit bom for R: 1 x COMP - Validate the delivery of your…
### Steps to reproduce:
- Enable rental transfer
- Create a rentable product R
- Create and confirm a rental order for 1 unit of R
- Create a kit bom for R: 1 x COMP
- Validate the delivery of your unit of R
#### > Missing Error: Record does not exist or has been deleted.
### Cause of the issue:
Confirming your rental order will generate a confirm moves of R. However, since at this point the product was not a kit, these will not be exploded. Now, the issue is that at validation The move will be exploded and deleted in the super call:
https://github.com/odoo/enterprise/blob/7cceddaf086d849b8e2121e1023ef3479397534f/sale_mrp_renting/models/stock_move.py#L10-L13 https://github.com/odoo/odoo/blob/0f2f222a431627a672daf10c86ec2578a27f97bb/addons/mrp/models/stock_move.py#L550-L555 https://github.com/odoo/odoo/blob/0f2f222a431627a672daf10c86ec2578a27f97bb/addons/mrp/models/stock_move.py#L591-L593 However, since the overrides of the sale_{mrp,stock}_renting modules call self rather than the result of the super call, they still expect to work with the original move rather than its exploded result: https://github.com/odoo/enterprise/blob/7cceddaf086d849b8e2121e1023ef3479397534f/sale_mrp_renting/models/stock_move.py#L10-L13 https://github.com/odoo/enterprise/blob/7cceddaf086d849b8e2121e1023ef3479397534f/sale_stock_renting/models/stock_move.py#L61-L65
opw-6191841
Forward-Port-Of: odoo/enterprise#120793
Forward-Port-Of: odoo/enterprise#120051This update fixes two issues related to USPS shipping rates. First, it ensures that package dimensions are displayed correctly in inches, resolving confusion for users. Second, it corrects a bug where the same rate was applied regardless of the selected shipping service, now dynamically adjusting based on the chosen USPS service type.
Original PR description
Issue ----- There are 2 issues with USPS rest: 1. USPS packagings do not have their size UOM displayed. This leads to confusion as users input in inches but the dimensions are treated as feet. 2.…
Issue ----- There are 2 issues with USPS rest: 1. USPS packagings do not have their size UOM displayed. This leads to confusion as users input in inches but the dimensions are treated as feet. 2. USPS returns the same rate regardless of the package type. Steps to reproduce ----- - Set USPS up - Open the Package Type form > go to its' Dimensions tab > Issue 1 - Set USPS up (domestic) - Select a `Domestic Rating Indicator` (eg LF - Flat Rate Box) - Create a SO with some product - Open the delivery widget and add a rate with USPS - Discard the changes - Go to the delivery method and change the rating (eg SP - Single Piece) - Go back to the SO - Open the delivery widget and add a rate with USPS > Issue 2, rate is the same as before Issue 1 ----- By default, there is no displayed UOM on the form because of https://github.com/odoo/odoo/blob/38c737c2a4cc29b48235a100cfa9d6152af73826/addons/stock_delivery/models/stock_package_type.py#L20-L33 We can change this behaviour for USPS specifically as done in Envia https://github.com/odoo/enterprise/blob/20cc61e69aa3f6a59de1e962b25ce11fa402bf22/delivery_envia/models/stock_package_type.py#L37-L46 Issue 2 ----- In `usps_rest_rate_shipment`, we request rates for every package of the delivery, which we receive as lists. We then iterate over the list to find the rate matching the `mail_class`. The problem is that this only filters over whether the delivery is domestic or international. We don't filter based on the actual service selected on the carrier (`usps_domestic_rating_indicator` for domestic and `usps_international_rating_indicator` for international). https://github.com/odoo/enterprise/blob/20cc61e69aa3f6a59de1e962b25ce11fa402bf22/delivery_usps_rest/models/delivery_usps.py#L224-L236 ----- Ticket: opw-6224918 Forward-Port-Of: odoo/enterprise#120789 Forward-Port-Of: odoo/enterprise#120594
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 report accuracy and provides a more reliable view of preparation times.
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 Forward-Port-Of: odoo/enterprise#118365
This fix ensures that credit notes for returned dropshipped products accurately display the correct lot/serial number on the invoice report. Previously, the system incorrectly used a different lot number, now it correctly reflects the returned product's lot.
Original PR description
**Issue** Printing a credit note for a returned dropshipped tracked product could display the wrong lot/serial number on the invoice report. **Steps to reproduce** - Activate "Display Lots & Serial…
**Issue**
Printing a credit note for a returned dropshipped tracked product could display the wrong lot/serial number on the invoice report.
**Steps to reproduce**
- Activate "Display Lots & Serial Numbers on Invoices"
- Create a product tracked by serial/lot and enable the dropship route
- Create two lots: "lot1" and "lot2"
- Create and confirm a SO for quantity 2
- Confirm the PO and validate the dropship for both lots
- Create and post an invoice
- Return "lot2" from the dropship picking
- Create and post a credit note for quantity 1
- Click on print -> The generated PDF displays "lot1" instead of "lot2"
**Cause**
While rendering `account.report_invoice_with_payments`, the report calls `_get_invoiced_lot_values` to determine which lot/serial numbers should be displayed:
https://github.com/odoo/odoo/blob/786c373d5ac8afdfb79eb7a7d69c5eb83b919625/addons/sale_stock/models/account_move.py#L31-L32 `invoiced_qties = 1` since the credit is on a quantity of 1 https://github.com/odoo/odoo/blob/786c373d5ac8afdfb79eb7a7d69c5eb83b919625/addons/sale_stock/models/account_move.py#L44 Three stock move lines are retrieved from the SO:
- the two original dropship deliveries,
- the return move for `lot2`. https://github.com/odoo/odoo/blob/786c373d5ac8afdfb79eb7a7d69c5eb83b919625/addons/sale_stock/models/account_move.py#L63 However, none of them are considered as `is_stock_return` because the dropship locations use `supplier` instead of `internal`: https://github.com/odoo/odoo/blob/786c373d5ac8afdfb79eb7a7d69c5eb83b919625/addons/sale_stock/models/account_move.py#L72-L76 As a consequence:
- The two original delivery move lines each keep quantity `1`: https://github.com/odoo/odoo/blob/786c373d5ac8afdfb79eb7a7d69c5eb83b919625/addons/sale_stock/models/account_move.py#L69 they never pass through the return handling logic: https://github.com/odoo/odoo/blob/786c373d5ac8afdfb79eb7a7d69c5eb83b919625/addons/sale_stock/models/account_move.py#L77-L80 which would make it as -1 (since `qties_per_lot[sml.lot_id]` is 0 for the first iteration of `sml.lot_id`). Thus, it does not pass by this code (since quantity is greater than 0): https://github.com/odoo/odoo/blob/786c373d5ac8afdfb79eb7a7d69c5eb83b919625/addons/sale_stock/models/account_move.py#L87-L90 which would make it as 0.
- for the last one, `is_stock_return = False` as it should be, thus the quantity is 1 as it should be. The quantities are therefore accumulated as:
https://github.com/odoo/odoo/blob/786c373d5ac8afdfb79eb7a7d69c5eb83b919625/addons/sale_stock/models/account_move.py#L92
resulting in:
`qties_per_lot = {lot1: 1, lot2: 2}`
instead of:
`qties_per_lot = {lot1: 0, lot2: 1}`
The report then selects the first matching lot and stops: https://github.com/odoo/odoo/blob/786c373d5ac8afdfb79eb7a7d69c5eb83b919625/addons/sale_stock/models/account_move.py#L94-L99
opw-6230281
Forward-Port-Of: odoo/odoo#266716