Monday, June 22, 2026
32 changes · saas-19.2
New functionality added to Odoo
This update introduces support for several new food delivery providers – including Talabna, Mandoob, and DiDi Food – expanding Odoo's capabilities for restaurants and delivery services. The addition of these providers, along with backports of existing ones, broadens Odoo's reach to support diverse regional markets and customer needs.
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 improves the payment process by adding debtor and creditor information to the data sent to Odoofin, which is required for initiating payments through Powens and Saltedge. This change ensures smoother and more complete payment initiation workflows. It's a necessary improvement for supporting key payment integrations.
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 fix resolves an issue preventing the 'Send to SII' option from appearing on Chilean vendor bills. The update adjusts internal code to correctly display this button when a DTE is generated, ensuring invoices can be properly transmitted to the SII (Servicio de Impuestos Internos) as required by Chilean regulations. This improves compliance and streamlines the invoicing process.
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
This update resolves a crash that occurred when users manually corrected bank statement lines within the Odoo Enterprise system. The issue stemmed from a missing context key during record creation, leading to incorrect journal assignments. This fix ensures bank statement lines are created with the correct journal, preventing data inconsistencies and system instability.
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 resolves an error that prevented users from sending SMS messages to website visitors. The change updated how the system accesses visitor phone numbers, ensuring compatibility after a recent data restructuring. This ensures the SMS functionality works correctly for all visitors.
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 where text entered into a SelectMenu field was intermittently being cleared, causing data entry problems. The fix ensures the input field accurately reflects user input during asynchronous data updates, improving data accuracy and user experience. It also corrects a related issue with the SelectMenu clearing when empty.
Original PR description
Step to reproduce: 1. Install `website_link` 2. Open Website > Site > Link Tracker 3. Type in text into any pre-defined field (Campaign, Medium, Source) 4. Observe that the input is not showing all…
Step to reproduce: 1. Install `website_link` 2. Open Website > Site > Link Tracker 3. Type in text into any pre-defined field (Campaign, Medium, Source) 4. Observe that the input is not showing all the typed characters Issue: - It's randomly removing characters, for example, type "1234567890" and observe Cause: - SelectMenu updates its internal searchValue only inside the debounced onInput handler `debouncedOnInput`. When an autocomplete callback reloads choices before that debounce fires, the component rerenders with a stale searchValue and writes that outdated value back into the controlled input, overwriting newer characters already typed by the user. Solution: - Update searchValue immediately on every raw input event and keep only the search callback debounced. - Also reset searchValue to null when a required single-select is blurred while empty, so the input falls back to the selected choice label instead of staying visually cleared. opw-6000540 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#267974 Forward-Port-Of: odoo/odoo#255819
This update resolves an issue where users with limited accounting permissions couldn't properly reconcile bank statements, leading to invoices being incorrectly marked as fully paid. The fix ensures accurate reconciliation by safely bypassing a permission check during the automated process, maintaining data auditability and preventing incorrect Account Receivable line additions.
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
This update resolves an issue where users with restricted accounting rights incorrectly marked invoices as fully paid, leading to incorrect Account Receivable entries. 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 improves the speed of queries related to document access permissions, specifically for the 'my/counters' route which is frequently used. By switching to a subquery, the system now utilizes an index more effectively, resulting in a significant reduction in query response times for portal and internal users.
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/45317a5e3168c5bcThis update corrects a technical issue in the UBL BIS3 generation process for Debit Notes. Previously, the system incorrectly used 'LegalMonetaryTotal' instead of the required 'RequestedMonetaryTotal' node, leading to errors in UBL file creation. This change ensures compliance with BIS3 standards for Debit Note UBL 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 product costs weren't consistently converted to the POS currency during product loading. Previously, product costs were stored in separate currencies, leading to potential inaccuracies in sales calculations. This change ensures all product costs are correctly converted, improving the reliability of pricing in the Point of Sale system.
Original PR description
When loading products in the POS, both the sale price and the cost were converted to the POS currency using `currency_id`. However a product stores its sale price and its cost in two potentially different currencies: `currency_id` (company currency, falling back to the main company) and `cost_currency_id` (company currency, falling back to the current company). opw-6297452 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269829
This update fixes an issue where preparation prints weren't showing all items after transferring an order to a shared table. Previously, only the items on the destination table were printed. Now, when orders are merged, the preparation prints accurately reflect all items from both orders, ensuring accurate kitchen workflows.
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 optimizes how Odoo searches for documents, specifically addressing a complex query that slowed down searches for documents not marked as 'SHARED'. This change aligns with the performance practices used in our production database, resulting in faster and more efficient document searches for users. It’s a performance improvement focused on the Documents module.
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 fixes an issue where the Luxembourg eCDF XML export incorrectly reported financial year data. The change removes a problematic account mapping, ensuring the exported data aligns with the Odoo Profit & Loss view. This improves data accuracy for reporting to the Luxembourg tax authority.
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
A recent upgrade to Odoo 19.2 caused an error when accessing the partner website page. This was due to a change in how website templates are structured. This fix updates the system to align with the new template structure, resolving the error and restoring normal website functionality.
Original PR description
*= website_partnership, website_crm_partner_assign Currently, an error occurs when users try to access the `/partners` website page after upgrading database to saas-19.2. This issue occurs because…
*= website_partnership, website_crm_partner_assign Currently, an error occurs when users try to access the `/partners` website page after upgrading database to saas-19.2. This issue occurs because recent changes introduced in PR [1] added a new template as id `index_layout`. Inside this template, a `t-call` element was using a nested `t-set` element to define `additional_title`. We were referencing this `t-set` element in the XPath of the `index` template to override the value of `additional_title`. However, recent changes removed the `t-set` from the `t-call` and replaced it with a direct variable assignment inside the `t-call`. During the upgrade, the migration script automatically moves the `additional_title` attribute into `t-call` and removes the `t-set` from the `t-call` (see the script and related changes in [2]). As a result, the XPath expression that targets the `t-set` element fails because the referenced element no longer exists, which causes the error. This commit fixes the above issue by adapting the `t-call` element to match the recent changes introduced in [2]. Instead of relying on a nested `t-set` element, it now uses the variable directly as an attribute within the `t-call`, and updates the corresponding attribute child accordingly. [1]: https://github.com/odoo/odoo/commit/711c3baad58f3e0f1dc39cb90eb8176aba91e9dd [2]: https://github.com/odoo/odoo/pull/235469/changes#diff-29ae6f0bcf846a2fcaffc38fdd0d3b19ea328c133ff4dfe18cc9725715f34dd9 sentry-7400315548
This update fixes an issue where self-orders created through kiosks or mobile ordering didn't show the correct table information on the payment screen. The fix ensures that table details are accurately displayed for all self-order transactions, improving the customer experience and order accuracy.
Original PR description
### **Issue** Self-orders created through the mobile QR menu or kiosk were not displaying table information in the Order Info section of the payment screen. ### **Root Cause** The Order Details…
### **Issue** Self-orders created through the mobile QR menu or kiosk were not displaying table information in the Order Info section of the payment screen. ### **Root Cause** The Order Details dialog relied on `order.getTable()` to retrieve table information. However, self-orders only store the table reference through `table_id`, which was not properly handled when rendering the dialog, resulting in missing table information. ### **Solution** This PR introduces the following changes: * Add a dedicated `getTableInfo()` helper in `pos_restaurant` to retrieve table information from the order. * Use `getTableInfo()` when building the Order Details dialog fields. * Allow self-order flows to provide table information through `table_id`, ensuring table details are displayed correctly. ### **Steps to Reproduce** 1. Enable the following options in POS Configuration: * QR Menu & Ordering * Service at Table * Online Payment 2. Open a self-order from the mobile QR menu 3. Select a table and complete the payment 4. Open the POS session 5. Open the paid self-order 6. Click the **Details** button on the right side ### **Video Reproduction** https://drive.google.com/file/d/138AoJCTF1HNlCGFgk9BRTb0UTai-uXX7/view?usp=sharing ### **Before** Table information was not displayed in the **Order Info** section for self-orders. ### **After** Table information is now correctly displayed for self-orders. opw-6179516 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This update resolves an issue where DIAN XML files were being rejected due to incorrect payment calculations. The fix combines payment amounts into a single tag, ensuring accurate data transmission to the DIAN and preventing errors related to negative payment amounts.
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 corrects a bug where the system incorrectly forecasted expiring perishable products as unavailable, leading to unnecessary reordering. Now, the system accurately reflects stock levels and avoids triggering replenishment for perishable goods until they actually expire, resolving confusion for users.
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 significantly reduces the memory usage and processing time when loading large General Ledger reports. By optimizing how display names are retrieved, the system now handles complex reports more efficiently, leading to a smoother user experience. This change addresses a performance bottleneck impacting report loading speed.
Original PR description
### Issue Loading a large General Ledger (e.g., during an "Unfold All" action) and retrieving display names for thousands of journal lines (`account.move.line`) causes excessive memory and…
### Issue Loading a large General Ledger (e.g., during an "Unfold All" action) and retrieving display names for thousands of journal lines (`account.move.line`) causes excessive memory and performance overhead. Profiling with `memray` showed that one of the main memory hotspots was located in `custom_label_builder`. **Previous behavior:** Accessing `record.display_name` in a loop without an explicit `fetch()` call triggered lazy computation of the field via `_compute_display_name()`. When the compute method accessed stored dependency fields (such as `name`, `ref`, `move_id`), each cache miss went through `_fetch_field()`, which greedily loaded **all fields sharing the same prefetch group** on the model, far beyond the dependencies of `display_name` alone. This caused the ORM cache to be filled with many unnecessary stored fields for every record in the prefetch set. --- ### Dataset Volume The performance metrics were captured using a dataset consisting of: * **455,694** Journal Items (`account.move.line`) * **19,947** Journal Entries (`account.move`) --- ### Solution Add a single `fetch(['display_name'])` call on the browsed recordset. By calling `fetch(['display_name'])` upfront, the ORM goes through `_determine_fields_to_fetch(['display_name'])`, which walks only the declared `field_depends` of `display_name` and fetches **only those specific stored fields**. nothing more. --- ### Impact & Results | Metric | Before Optimization | After Optimization | Change / Note | | :--- | :--- | :--- | :--- | | **Peak Memory** | ~856 MB | ~223 MB | ~74% reduction | | **Execution Time** | 2.48s | 2.13s | About the same time with multiple tries | OPW-6275158 Forward-Port-Of: odoo/enterprise#121020
This update resolves an issue where invoices with reverse charge tax in Poland (fa3) were generating incorrect XML files for transmission to the 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 where delivery note costs (DDT) were incorrectly calculated for products tracked across multiple lots. The fix ensures that the total sale price of all lots is used in the cost calculation, resulting in accurate DDT pricing. This improves the reliability of financial reporting for multi-lot sales.
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 issue where manually added by-products on manufacturing orders caused errors during production closure. The fix ensures that serial numbers are correctly applied to by-products created outside of the standard BOM process, improving the reliability of the shopfloor operation.
Original PR description
**Issue** Adding a serial-tracked by-product manually on a Manufacturing Order whose BOM does not define it, can lead to inconsistencies when assigning serial numbers in the shopfloor application.…
**Issue** Adding a serial-tracked by-product manually on a Manufacturing Order whose BOM does not define it, can lead to inconsistencies when assigning serial numbers in the shopfloor application. **Steps to reproduce** - Activate by-product in the settings - Create a product with an empty BOM (final product) - Create another product tracked by serial number (by-product) - Create and confirm a MO for the final product with 1 unit of the by-product - Go to Miscellaneaous -> operation Type -> shopfloor - Activate the option "Pre fill lot/serial numbers in shop floor" - Return to the MO and open the shopfloor view - Click on the '+' button next to the by-product and assign a serial number - Try to close the production -> A user error is raised stating that the by-product requires a serial number. **Cause** When the by-product is added manually on the MO, a stock move is created with an initial move line that does not contain any serial number. Later, when assigning a serial number from the shopfloor view: https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/mrp_workorder/models/stock_move.py#L121-L122 a new move line containing the serial number is created: https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/mrp_workorder/models/stock_move.py#L116-L119 However, the original empty move line is not removed (the issue): https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/mrp_workorder/models/stock_move.py#L124-L125 Because `self.picking_type_prefill_shop_floor_lots` is True, but `self.byproduct_id` is an empty recordset since: https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/addons/mrp/models/mrp_production.py#L1304-L1311 https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/addons/mrp/models/mrp_production.py#L1279 Indeed, `byproduct_id` is only populated from BOM-defined by-products. As a result, while confirming the production, there is 2 sml and among them, the original one without SN, which triggers the error: https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/addons/stock/models/stock_move_line.py#L590 https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/addons/stock/models/stock_move_line.py#L634-L635 https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/addons/stock/models/stock_move_line.py#L658-L659 https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/addons/stock/models/stock_move_line.py#L661-L669 opw-6223158 Forward-Port-Of: odoo/enterprise#118792
This update streamlines the process for applying Early Payment Discounts (EPD) to refund transactions. Previously, a technical issue prevented correct tax repartitioning, now resolved by ensuring the system correctly identifies and maps invoice and refund lines for EPD calculations. This enhancement improves the accuracy and reliability of EPD processing on refunds.
Original PR description
This commit does not bring native support for EPD on credit notes, only makes custom support a little easier and cleaner. It is quite easy to support EPD (early payment discounts) on refunds by…
This commit does not bring native support for EPD on credit notes, only makes custom support a little easier and cleaner. It is quite easy to support EPD (early payment discounts) on refunds by extending - `_early_payment_discount_move_types` - `_is_eligible_for_early_payment_discount` However, this approach breaks when it reaches `inverse_tax_rep` in `_get_invoice_counterpart_amls_for_early_payment_discount_per_payment_term_line`, which assumes tax repartition lines with `document_type == 'invoice'` and raises when called on `tax_rep` lines of 'refund' type instead. This commit fixes that by selecting source and target repartition lines according to the `tax_rep`'s document type, which ensures: - the `.index()` no longer raises a `ValueError`, as `tax_rep` is now looked up in the matching set (`refund_` for refunds, `invoice_` otherwise) - `inverse_tax_rep` returns the corresponding line in the opposite set, preserving the original invoice->refund mapping while adding the refund->invoice one Since `inverse_tax_rep` is a closure, downstream modules cannot patch it without copying the whole ~170-line method. Making it symmetric here lets custom EPD-on-refund support work without that duplication. task-[6265601](https://www.odoo.com/odoo/all-tasks/6265601) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#271102
This update fixes issues with sending appointment invitations, ensuring they're only sent when an appointment is actually booked or requested. Previously, invitations were sent even when appointments were cancelled or updated, leading to unnecessary emails. This ensures a cleaner and more reliable invitation process for users.
Original PR description
This PR fix three issues related to the sending of the appointment invitations. Each one has its own commit: - Commit 1 sends invitations only if the event either "booked" or "request". Previously they were sent even if the appointment was cancelled. - Commit 2 prevents the sending of regular invitations and always sends appointment invitation to new attendees of existing booked appointments. - Commit 3 sent appointment invitations if the status of an existing event is set "request". It also add the status change in the log as it would have been if it was done at the creation. Community PR: https://github.com/odoo/odoo/pull/260073 Task-6139036 Forward-Port-Of: odoo/enterprise#121081 Forward-Port-Of: odoo/enterprise#114304
This fix resolves an issue where invoices for returned dropshipped products incorrectly displayed both lot numbers instead of just the correct one. The update ensures that the invoice accurately reflects the returned product's serial/lot number, improving inventory accuracy and reporting. This was caused by a misinterpretation of return move lines during invoice generation.
Original PR description
**Issue** Printing an invoice 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 an invoice 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 on the invoice
-> The generated PDF displays "lot1 & lot2" instead of "lot1"
**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 = 2` since the invoice is on a quantity of 2 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 (as they should be): https://github.com/odoo/odoo/blob/786c373d5ac8afdfb79eb7a7d69c5eb83b919625/addons/sale_stock/models/account_move.py#L77-L80
- for the last one, `is_stock_return = False` while it should not, thus the quantity is 1 instead of 0. Furthermore, it does not pass by this code:
https://github.com/odoo/odoo/blob/8759429547e42e9f63b15a7c80475be46ef437e2/addons/sale_stock/models/account_move.py#L79 which would make the quantity for lot2 equalled to 0 (1-1) 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: 1, lot2: 0}`
The report selects both lots since it starts with lot1 (qty of 1): https://github.com/odoo/odoo/blob/786c373d5ac8afdfb79eb7a7d69c5eb83b919625/addons/sale_stock/models/account_move.py#L94-L99
opw-6236855
Forward-Port-Of: odoo/odoo#270599