Tuesday, September 8, 2026
44 changes · saas-19.2
Enhancements to existing features
Dutch SBR and ICP report submissions now go through Odoo's IAP proxy instead of connecting directly to Digipoort. This reduces duplicated technical handling and allows companies to submit using either their own certificate or Odoo's shared group certificate.
Original PR description
*: l10n_nl_reports_sbr{,_icp,_status_info}
---
Description of the issue this commit addresses:
Dutch SBR and ICP reports still send directly to Digipoort with duplicated SOAP/signature code and no shared group-certificate flow.
---
Desired behavior after this commit is merged:
This commit routes both reports through IAP proxy signing/sending, and lets users submit with either a personal certificate or Odoo's group one.
---
IAP PR: https://github.com/odoo/iap-apps/pull/1593
task-3439634
Forward-Port-Of: odoo/enterprise#117617Opening Point of Sale registers for newly created companies is now much faster in databases with very large accounting histories. The change avoids an inefficient accounting data check, reducing delays from seconds to milliseconds in large multi-company environments.
Original PR description
When you create a new company in a database that has a lot of existing account move lines and you attempt to open a PoS register from the list view, `_compute_company_has_template` checks…
When you create a new company in a database that has a lot of existing
account move lines and you attempt to open a PoS register from the list
view, `_compute_company_has_template` checks `_existing_accounting` for
the new company and wil run a sequential scan on the entire account_move_line
table followed by a nested loop as the query planner assumes AMLs company_ids
will be roughly evenly distributed.
This is not the case in a new company that has no/very few AMLs.
This is because the query ran is:
`SELECT COUNT(*) FROM
(SELECT FROM "account_move_line"
WHERE (
"account_move_line"."company_id" IN
(SELECT "res_company"."id" FROM
"res_company" WHERE
("res_company"."parent_path" LIKE '3/%')
))
LIMIT 1)`
and the values of company_id being searched for aren't known until the
subquery runs.
Running a query more like
`SELECT COUNT(*) FROM
account_move_line
WHERE company_id IN (%s)`
is much faster
Since res_company will always be a smaller table, we can do the inexpensive
search first and then pass in the values so Postgres can do a cheaper
search and return faster.
Benchmark time of `_existing_accounting`:
| Company 1 AML count | Company 2 AML count | Pre-fix | Post-fix | Multiplier |
|---|---|---|---|---|
| 10,000,000 | 0 | 700 Milliseconds | 500 Microseconds | 1,400x |
| 20,000,000 | 0 | 1.35 Seconds | 1 Millisecond | 1,350x |
| 20,000,000 | 20,000,000 | 2 Milliseconds |1.3 Milliseconds | 1.5x |
| 50,000,000 | 0 | 3.25 Seconds | 1 Millisecond | 3,250x |
| 100,000,000 | 0 | 5.3 Seconds | 1.3 Milliseconds | 4,075x |
Query Plan Before:
```
"Aggregate (cost=0.08..0.09 rows=1 width=8) (actual time=5573.001..5573.002 rows=1.00 loops=1)"
" Buffers: shared read=571435"
" -> Limit (cost=0.00..0.08 rows=1 width=0) (actual time=5572.995..5572.997 rows=0.00 loops=1)"
" Buffers: shared read=571435"
" -> Nested Loop (cost=0.00..https://github.com/odoo/odoo/commit/1571439c0c70c1f1dc3229421e696b97ce1678f8.33 rows=20000086 width=0) (actual time=5572.988..5572.989 rows=0.00 loops=1)"
" Join Filter: (account_move_line.company_id = res_company.id)"
" Buffers: shared read=571435"
" -> Seq Scan on account_move_line (cost=0.00..971432.72 rows=40000172 width=4) (actual time=0.432..1913.934 rows=40000000.00 loops=1)"
" Buffers: shared read=571431"
" -> Materialize (cost=0.00..4.03 rows=1 width=4) (actual time=0.000..0.000 rows=0.00 loops=40000000)"
" Storage: Memory Maximum Storage: 17kB"
" Buffers: shared read=4"
" -> Seq Scan on res_company (cost=0.00..4.03 rows=1 width=4) (actual time=0.785..0.785 rows=0.00 loops=1)"
" Filter: ((parent_path)::text ~~ '3/%'::text)"
" Rows Removed by Filter: 2"
" Buffers: shared read=4"
"Planning:"
" Buffers: shared hit=574 read=77"
"Planning Time: 15.323 ms"
"Execution Time: 5573.060 ms"
```
Query Plan After:
```
"Aggregate (cost=4.46..4.47 rows=1 width=8) (actual time=1.972..1.973 rows=1.00 loops=1)"
" Buffers: shared read=3"
" -> Limit (cost=0.44..4.46 rows=1 width=0) (actual time=1.967..1.968 rows=0.00 loops=1)"
" Buffers: shared read=3"
" -> Index Only Scan using account_move_line__company_id_index on account_move_line (cost=0.44..4.46 rows=1 width=0) (actual time=1.965..1.966 rows=0.00 loops=1)"
" Index Cond: (company_id = 3)"
" Heap Fetches: 0"
" Index Searches: 1"
" Buffers: shared read=3"
"Planning:"
" Buffers: shared hit=3"
"Planning Time: 0.219 ms"
"Execution Time: 1.998 ms"
```
opw-6513885
Forward-Port-Of: odoo/odoo#285096Duplicating sections with many lines is now much faster and less likely to time out. The change batches the recalculation work that previously happened line by line, reducing waiting time for users working with large sales documents.
Original PR description
Issue: Sections containing a large number of subsections or lines can take too long to duplicate and may eventually time out. The slowdown stems from the onchange issues in `_duplicateRecords()` in…
Issue: Sections containing a large number of subsections or lines can take too long to duplicate and may eventually time out. The slowdown stems from the onchange issues in `_duplicateRecords()` in sale.order.line. Each sale.order.line is first created as an empty datapoint, which triggers an onchange RPC. The copied values are then applied, triggering a second onchange RPC for every duplicated line. Fix: Prepare the copied values before creating the datapoints and send them through a batched onchange. This retrieves the required onchange values for all duplicated lines in a single RPC. Benchmarks: sale.order.line onchanges: Note: "Timing Before" is calculated by the difference between the first and last sale.order.line onchange completion times. | Lines | RPCs Before | RPCs After | Timing Before | Timing After | Speedup | | ----: | ----------: | ---------: | ------------: | -----------: | ------: | | 10 | 20 | 1 | 0.31s | 0.04s | 5.52x | | 50 | 50 | 1 | 1.62s | 0.14s | 11.67x | | 100 | 200 | 1 | 3.41s | 0.25s | 13.64x | | 500 | 1000 | 1 | 10.98s | 0.96s | 11.43x | | 1000 | 2000 | 1 | 31.07s | 2.74s | 11.34x | Related: opw-6395475 Forward-Port-Of: odoo/odoo#280418
Installing the Colombian DIAN integration is now faster on large databases. The change avoids a lengthy one-time recalculation during setup while keeping normal invoice processing unchanged afterward.
Original PR description
- Pre-create the stored computed columns `l10n_co_edi_type`, `l10n_co_dian_state`, and `l10n_co_edi_cufe_cude_ref` in `_auto_init()`. - This prevents Odoo from computing and writing these fields for all existing `account.move` records when installing `l10n_co_dian`. - This is particularly important for large databases with a high volume of Colombian accounting moves, where the initial computation can take too long and cause the module installation to hit the time limit. - The compute methods are kept unchanged, so the fields continue to be computed normally for subsequent record creation or dependency changes. **opw-6451331** Forward-Port-Of: odoo/enterprise#130381 Forward-Port-Of: odoo/enterprise#129507
Resolved issues and error corrections
Completing a field service shift now recalculates the planned allocated hours again. This helps keep scheduling, timesheets, and related sales tracking aligned so teams see accurate time information after work is completed.
Original PR description
This reverts commit b642ea4d5d2c0b4bd83c70103f7e8dfa6ef736dd. opw-6542896
This fix prevents loyalty reward changes from being lost after users edit and save a promotion. It ensures updated discount or reward values remain stored, avoiding confusion and manual rework for sales teams using loyalty programs.
Original PR description
**Steps to reproduce:** 1. Install Sales and Loyalty modules 2. Create a Discount & Loyalty Program (type: Promotion) and save the form 3. Open the Reward modal and edit any values (e.g., 5% discount…
**Steps to reproduce:** 1. Install Sales and Loyalty modules 2. Create a Discount & Loyalty Program (type: Promotion) and save the form 3. Open the Reward modal and edit any values (e.g., 5% discount instead of 10%), save the new changes in the modal and then save the form 4. Re-open the reward modal **Issue:** The updated value (5% discount) is not saved, and the reward reverts to its previous state (10% discount). This issue will occur for any modifications. **Why this happens:** - The write method in `loyalty_program` uses `convert_to_cache` on `reward_ids` to make a constraint check before executing the actual super().write() - A recent commit (https://github.com/odoo/odoo/commit/9c52f6246d24d02457d34df6b559eecf7ec50687) modified `convert_to_cache` to update the cache for `Command.UPDATE` to fix premature computations during `onchange` - This update alters the real record's cache without marking the fields as dirty - When super().write() executes afterwards, the ORM sees the incoming values already match the cache, assumes no changes occurred, and drops the SQL UPDATE **Fix:** - Restrict the cache mutation to only apply to virtual/draft records - This preserves the intended onchange behavior for NewId records while preventing cache corruption on real database records prior to write opw-6527124 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Deleting a depreciation model that is still linked to active assets is now blocked. This prevents assets from losing key accounting settings and becoming impossible to manage, reset, or delete.
Original PR description
**Steps to reproduce:** * Install the **Accounting** (`account`). * Create a **Depreciation Model** (e.g. Linear, 5 years). * Create an asset, assign the depreciation model, and confirm it. * Go to…
**Steps to reproduce:** * Install the **Accounting** (`account`). * Create a **Depreciation Model** (e.g. Linear, 5 years). * Create an asset, assign the depreciation model, and confirm it. * Go to **Accounting → Configuration → Depreciation Models** and delete the model used by the running asset. * Try to **Reset to Draft** on **asset**. **Observed behavior:** * The depreciation model is deleted silently. * The asset's `model_id` FK becomes `NULL`, causing all related fields (`method`, `method_number`, `method_period`, `journal_id`, etc.) to become empty. * Any subsequent attempt to cancel, reset to draft, or delete the asset fails with a **missing required field** error, leaving the asset permanently unmanageable. **Cause:** * `account.depreciation.model` had no `@api.ondelete` guard — deletion was entirely unprotected, unlike `write()` which already blocks edits on models used by running assets. * `model_id` on `account.asset` had no `ondelete` constraint, so the database silently NULLed the FK on model deletion. **Fix:** * Add an `@api.ondelete(at_uninstall=False)` method `_unlink_if_no_running_assets()` on `account.depreciation.model` that raises a `UserError` listing the affected asset names when a deletion is attempted while any linked asset is in `open`, `paused`, or `close` state — mirroring the protection already present in `write()`. opw-6233210
Users can now download attachments opened in the file viewer from Discuss channels without seeing an error. This restores a broken download flow by using the request type expected by the server, while preserving the correct downloaded filename.
Original PR description
Description of the issue/feature this PR addresses: Downloading an attachment from the file viewer fails with a `405 Method Not Allowed` error when the attachment belongs to a Discuss channel. I've…
Description of the issue/feature this PR addresses:
Downloading an attachment from the file viewer fails with a `405 Method Not Allowed` error when the attachment belongs to a Discuss channel.
I've already submitted a ticket to Odoo: #6430586
Steps to reproduce (on a 18.0 runbot):
1. open Discuss and send an image in a channel
2. click the image to open the file viewer
3. click the download button (either the one in the header or the one in the bottom
toolbar)
The server rejects the request:
```
POST /discuss/channel/1/image/519861?filename=image.png&unique=32647b0f&download=true 405
```
and the user gets a `RPC_ERROR: Arbitrary Uncaught Python Exception` dialog reporting `405 Method Not Allowed`.
Cause: `download()` always issues a POST request, while the routes serving the attachments of a discuss channel only allow GET:
* `/discuss/channel/<int:channel_id>/attachment/<int:attachment_id>`
* `/discuss/channel/<int:channel_id>/image/<int:attachment_id>`
so the request never reaches the controller. Downloading the very same attachment from the attachment card in the conversation still works, because that one is a plain anchor navigation (GET).
This is a regression from fb152985f4b8 ("[FIX] web: download FileViewer files via blob helper"), which routed the file viewer download through `download()` in order to honor the filename sent by the server in the `Content-Disposition` header.
Only 18.0 is affected: saas-18.1 and saas-18.2 do not have the commit that introduced the regression, and from saas-18.3 on, the `urlRoute` override was dropped and channel attachments are served through the standard `/web/content` and /web/image` routes, which are not restricted to GET.
The download is still sent with POST on those branches though, hence forward-porting this up to master.
Current behavior before PR:
Downloading a Discuss channel attachment from the file viewer raises a 405 error and the file is not downloaded. Images and other file types are equally affected.
Desired behavior after PR is merged:
The file is downloaded, keeping the filename advertised by the server. The download is performed with a GET request through `downloadFile()`, which still goes through the blob helper, so the fix of fb152985f4b8 is preserved. This is already the way a file is downloaded from its url in `readonly_file.js`.
Added a test that downloads an image attachment of a channel from the file viewer and asserts the request is a GET on the channel attachment route. It fails before this fix with `POST /discuss/channel/1/image/1`.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#287024
Forward-Port-Of: odoo/odoo#279232Helpdesk users can now create tickets for teams with automatic assignment without being blocked by Time Off access restrictions. The assignment process can still check who is unavailable, ensuring tickets are routed correctly while avoiding unnecessary permission errors.
Original PR description
Before this commit, a helpdesk user creating a ticket on a team that assigns tickets automatically got "You are not allowed to access 'Time Off' (hr.leave) records". This happens because picking the next assignee reads hr.leave as the acting user, to skip the members who are off, and a plain helpdesk user has no access to Time Off. This commit reads the employees of the members and their leaves in sudo, as whom to assign is a system decision. https://runbot.odoo.com/odoo/error/947053
This fix makes the restaurant point-of-sale order tracking test wait until order updates are fully saved before finishing. It helps prevent false test failures where the system appeared to keep outdated order quantities or edit status.
Original PR description
The order tracking tour only waited for the feedback screen to be shown after validating the payment. Since order validation is performed asynchronously while the feedback screen is displayed, the tour could finish before the updated order was synced to the backend. This caused the Python test to still see the original quantity and `is_edited` set to false. To fix we wait for the feedback screen continue button to be enabled, which ensures order validation and synchronization have completed before the tour ends. [error-940386](https://runbot.odoo.com/odoo/error/940386) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282726
This fixes a messaging issue where correcting a mistaken mention could still notify the originally selected person, even though their name was no longer visibly mentioned. Messages now only notify the people actually mentioned, reducing accidental or confusing notifications.
Original PR description
When a user mentions the wrong partner and corrects it by continuing to type, e.g. picking "John" by mistake, typing further so the text becomes "@ John Doe" and picking "John Doe" in the suggestion…
When a user mentions the wrong partner and corrects it by continuing to type, e.g. picking "John" by mistake, typing further so the text becomes "@ John Doe" and picking "John Doe" in the suggestion popup, the discarded first pick stays in the composer's mentioned partners. On post, mentions are validated by searching the body for "@<name>", and "@ John" is found inside "@ John Doe", so the partner the user tried to replace is kept in the recipients and gets notified even though no mention of them remains visible in the message. Validate mentions from the longest mention text to the shortest, counting the occurrences of each text and blanking them out before looking for shorter ones. A partner whose mention text only appears inside a longer mention is dropped, while distinct partners sharing the same name each consume one occurrence. Steps to reproduce: - Create contacts "John" and "John Doe" - On any record, open the chatter and type "@John", pick "John" by mistake, then keep typing " Doe" and pick "John Doe" in the suggestion popup to correct it - Send the message => The message is also sent to "John" although only "@John Doe" appears in the body. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284239
Cancelled point-of-sale orders and order lines in Germany are now saved and clearly marked as cancellations when sent for fiscal certification. This helps ensure Fiskaly receives accurate cancellation information, reducing compliance and reporting risks for retailers.
Original PR description
In this commit: --------------- - We now store cancelled retail orders on the server and send the `storno` flag as `true` for cancelled lines and orders to ensure proper handling in Fiskaly. task: 6326042 Relataed PR: https://github.com/odoo/odoo/pull/277648 Forward-Port-Of: odoo/enterprise#130175 Forward-Port-Of: odoo/enterprise#120410
Customers can now complete checkout when their cart includes a legitimate free gift from a loyalty promotion, even if the website blocks regular zero-priced products. This prevents promotional reward items from incorrectly stopping purchases and keeps checkout behavior aligned with intended campaign rules.
Original PR description
As of commit b8e790b2, a cart containing a product priced at 0 while the website forbids the sale of zero-priced products is no longer payable: the customer is redirected back to the cart with a warning. Reward lines were caught by that new rule. A promotion offering a free gift whose product has no sale price adds a reward line priced at 0 to the cart, so the whole cart became unpayable even though nothing was wrong with it. This commit excludes reward lines from the zero-priced rule, the same way delivery lines already are. opw-6526396 Forward-Port-Of: odoo/odoo#286863 Forward-Port-Of: odoo/odoo#286464
Peruvian electronic invoices now include cash rounding in the final amount due sent to SUNAT. This prevents discrepancies between the rounded total shown on the invoice and the amount reported in the official XML.
Original PR description
Steps to reproduce ------------------ 1. On a peruvian company, set a cash rounding method on an invoice 2. Post the invoice and generate the SUNAT UBL 2.1 XML -> the rounding is in…
Steps to reproduce ------------------ 1. On a peruvian company, set a cash rounding method on an invoice 2. Post the invoice and generate the SUNAT UBL 2.1 XML -> the rounding is in `PayableRoundingAmount` but `PayableAmount` still has the amount before rounding! Why it's happening ------------------ The generic code computes `PayableAmount` from `amount_residual`, and Peru overrides it to be the total tax included of the XML minus the prepaid amounts, because the residual can not be used there. Then odoo/odoo@b847552872ad changed the meaning of the totals. The cash rounding line is not part of the base lines anymore, its amount is kept aside in `cash_rounding_base_amount_currency` and the totals do not contain it anymore. The generic code stays correct because `amount_residual` already has the rounding inside but the Peru total using `tax_inclusive_amount_currency` is now the amount before rounding and this is what ends up in the `PayableAmount`! The fix ------- Add the cash rounding amount when computing the `PayableAmount`. opw-6509677 Forward-Port-Of: odoo/enterprise#129983
Backend point-of-sale refunds now check that the refunded quantity cannot exceed the original sale quantity. This aligns backend behavior with the storefront and helps prevent accidental over-refunds and incorrect sales records.
Original PR description
Currently, if you refund an order fro the backend it is possible to modify the qty as if to refund more than the original order qty. Steps to reproduce: ------------------- * Make an order from the…
Currently, if you refund an order fro the backend it is possible to modify the qty as if to refund more than the original order qty. Steps to reproduce: ------------------- * Make an order from the shop (1 product, qty 1) * Validate the order * Go backend * Find the order and select the refund button * Change qty from -1 to -3 * Save and continue the refund process > No problem refunding more than the original quantity Why the fix: ------------ In the frontend we cannot refund more than the original quantity, we assume the same should be in the backend process. The most simple way to do this is by doing a difference between the quantity from the original order and all the refund lines linked. From `self.refunded_orderline_id.refund_orderline_ids` we need to exclude the line that represents self as it holds the quantity before the onchange and we care about the quantity we're trying to write not the previous (allegedly correct). opw-6328635 Forward-Port-Of: odoo/odoo#284558 Forward-Port-Of: odoo/odoo#281405
This fix prevents an error when a company adds the first milestone to an existing Time Off accrual plan that already has approved allocations. Employees can create time off requests normally after the plan is updated, improving reliability for accrual-based leave management.
Original PR description
## Steps to Reproduce: - Install the Time Off module. - Create an Accrual Plan without any milestones. - Create an Accrual Allocation using the newly created accrual plan. - Approve the allocation. -…
## Steps to Reproduce: - Install the Time Off module. - Create an Accrual Plan without any milestones. - Create an Accrual Allocation using the newly created accrual plan. - Approve the allocation. - Add a milestone to the accrual plan. - Create a new Time Off request after the allocation start date. - Save the record. ## Error: `TypeError - '>' not supported between instances of 'datetime.date' and 'bool'` ## Cause: `lastcall` is initialized by method `_add_lastcalls()`, which is only called at create and write. When an accrual allocation is created with an accrual plan that has no milestones, `_add_lastcalls()` returns early because `level_ids` is empty, leaving `lastcall` set to `False`. - [1] If a milestone is added later, `lastcall` is compared with `first_level_start_date`, resulting in a comparison between boolean and datetime, which raises an error. ## Fix: When `lastcall` is not set, default it to `first_level_start_date`. [1] - https://github.com/odoo/odoo/blob/27036bea232572ba692fbb95387911eb453266bf/addons/hr_holidays/models/hr_leave_allocation.py#L703-L706 sentry-7615375197 Forward-Port-Of: odoo/odoo#281550 Forward-Port-Of: odoo/odoo#280674
This fix prevents an error that could occur when automatically drawing a user's signature after the signing component has already closed. It helps keep the signing flow stable and avoids interruptions for users completing documents.
Original PR description
backport of https://github.com/odoo/odoo/pull/256039 drawCurrentName() read canvas.width without checking the ref, which can be null if the component unmounts while an async caller (e.g. SignNameAndSignature.onClickSignAuto, which awaits a font RPC) is still running. runbot-238520
Purchase orders for products billed on ordered quantities now correctly create accrued expense entries even before goods are received. This ensures expected accounting accruals are not missed and amounts remain accurate when receipts and invoices differ.
Original PR description
Version: -------- - 19.0+ Steps to reproduce: ------------------- - Install `stock`, `purchase`, and `accountant` - Create a storable product with: - Tracking Inventory enabled - Control Policy set…
Version: -------- - 19.0+ Steps to reproduce: ------------------- - Install `stock`, `purchase`, and `accountant` - Create a storable product with: - Tracking Inventory enabled - Control Policy set to **Ordered Quantities** - Create and confirm a Purchase Order with some unit price - Do not receive or invoice the order - From the Purchase Order gear menu, click **Accrued Expense Entry** Issue: ------ The Accrued Expense Entry wizard opens, but no accounting lines are generated. For products invoiced on **Ordered Quantities**, the ordered quantity should already be accrued even though nothing has been received. Cause: ------ This issue was introduced after this changes [commit](https://github.com/odoo-dev/odoo/commit/81f25bc57b8433a65bf33950c64dc7582240a229) Previously, the accrual wizard relied on the stored `qty_to_invoice` field, whose computation already respected the product's Control Policy. For products invoiced on **Ordered Quantities**, `_compute_qty_invoiced()` computes the quantity to invoice from the ordered quantity: https://github.com/odoo/odoo/blob/810a02a577c2811dc5c12f0abf45eebb9cf96d00/addons/purchase/models/purchase_order_line.py#L147-L152 The refactoring replaced this logic with the new `amount_to_invoice_at_date` field, which always computes the invoicable quantity as: `qty_received_at_date - qty_invoiced_at_date` https://github.com/odoo/odoo/blob/65dbcabcd243abf24d6d3c3788d2caff66485790/addons/purchase/models/purchase_order_line.py#L282-L285 This formula ignores the product's Control Policy. For products invoiced on Ordered Quantities, before any receipt: `qty_received_at_date` = 0 `qty_invoiced_at_date` = 0 therefore: `amount_to_invoice_at_date` = 0 The Accrued Expense wizard filters out lines whose `amount_to_invoice_at_date` is zero: https://github.com/odoo/odoo/blob/65dbcabcd243abf24d6d3c3788d2caff66485790/addons/account/wizard/accrued_orders.py#L168-L178 As a result, the purchase order line is excluded entirely and the wizard produces no accounting entries. The same assumption is also used later in `account.accrued.orders.wizard._compute_move_vals()` when computing tax-included amounts, causing incorrect accrual values for Ordered Quantities products whenever receipts and invoices differ. Fix: ---- Introduce `_get_qty_to_invoice_at_date()`, mirroring the existing purchase_method logic used by _compute_qty_invoiced(). The helper returns: product_qty - qty_invoiced_at_date for Ordered Quantities products; `qty_received_at_date` - `qty_invoiced_at_date` for Received Quantities products. Now products invoiced on `Ordered Quantities` become accruable as soon as the Purchase Order is confirmed; --- opw-6290782 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284507 Forward-Port-Of: odoo/odoo#276435
This fix ensures Saudi e-invoices submitted successfully to ZATCA are properly recorded even when the user only has read-only access to journals. It prevents invoices from being resent after a local permission error, reducing the risk of duplicate tax submissions.
Original PR description
**Steps to reproduce:** This issue is hard to reproduce because it requires a live ZATCA connection: - As a user with read-only permission on journals, send an invoice to ZATCA. - You get an access error on the journal, and the invoice is unchanged (You can try sending it again to ZATCA). **Issue:** What happens is: - A user with read-only permission on journals sends an invoice to ZATCA. - If ZATCA responds with a 200 (successfully submitted), we try to write on the field `journal.l10n_sa_latest_submission_hash` - With no write permissions, the write fails and all changes are rolled back (on odoo, not on ZATCA) - We can send the invoice again to ZATCA, resulting in duplicates. **Solution:** - Added a sudo when writing on the field: `journal.l10n_sa_latest_submission_hash` opw-6320179 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286570 Forward-Port-Of: odoo/odoo#278728
Guest customers who enter a valid EU VAT number during checkout will now have it verified immediately. This ensures the correct intra-community 0% VAT rate is applied instead of domestic VAT, preventing overcharging and tax calculation errors.
Original PR description
# Description **Steps to Reproduce** 1. Install Belgium Localization, then go to **Settings → Accounting/Invoicing**. 2. Enable **Verify VAT Numbers** (`vat_check_vies`). 3. Go to **Accounting →…
# Description **Steps to Reproduce** 1. Install Belgium Localization, then go to **Settings → Accounting/Invoicing**. 2. Enable **Verify VAT Numbers** (`vat_check_vies`). 3. Go to **Accounting → Configuration → Fiscal Positions** and confirm or create an **Intra-Community** fiscal position with: - Detect Automatically (`auto_apply`): enabled - VAT Required (`vat_required`): enabled - Country Group: EU, no specific country configured 4. Open an incognito/private browser window and make sure the session is unauthenticated. 5. Go to the website's `/shop` page and add any product to the cart. 6. Proceed to checkout until reaching the Address step (`/shop/address`). 7. Enter a delivery address in an EU country different from the company's country (e.g. company in Belgium, delivery address in the Netherlands). 8. In the VAT Number field, enter a real, valid, VIES-registered VAT number corresponding to the delivery country (e.g. a valid NL VAT number for a Netherlands address). 9. Click **Save Address / Continue** and proceed to the Payment step (`/shop/payment`). 10. Check the tax applied to the delivery line and the resulting order total. **Issue** The delivery product and the overall order are taxed at the standard/domestic VAT rate instead of the expected 0% intra-community rate — even though the customer provided a valid, VIES-registered EU VAT number matching the delivery country. **Root Cause** In `base_vat`, `res.partner.create()` unconditionally removes `vies_valid` from the ORM's pending computation queue via `env.remove_to_compute()`, relying on a subsequent `write()` to trigger the actual VIES check. This holds for the standard backend flow, where creation is followed by a `write()` — but the website guest checkout flow differs: - `website_sale` creates the guest partner through `_create_new_address()`. - The partner is created via a single `create()` call, with no follow-up `write()`. - `_compute_vies_valid()` is therefore never triggered. - `vies_valid` remains permanently unset (`NULL`), despite a VAT number being provided. Downstream, `account.fiscal.position._get_vat_required_valid()` reads this unset value as falsy, so the Intra-Community fiscal position's `vat_required` condition fails and is rejected in favor of another applicable position (e.g. EU B2C or Domestic). **Solution** After partner creation, explicitly trigger `_compute_vies_valid()` when the partner has a VAT number and the operation is not part of a file import (`import_file` context) — performing the VIES check immediately instead of relying on a `write()` that guest checkout never issues. **Result** Guest customers providing a valid EU VAT number now get `vies_valid` computed immediately at creation. Fiscal position detection correctly identifies the Intra-Community position, and the expected 0% VAT treatment is applied to the delivery and order. OPW: 6522992 Forward-Port-Of: odoo/odoo#286201
Inventory users without Accounting permissions can now view and create E-Waybills without running into access errors. This helps warehouse teams complete shipping and compliance workflows more smoothly without needing extra accounting access.
Original PR description
Before this commit Inventory users without Accounting permissions could get an Access Error when viewing or creating an E-Waybill because they could not access the required document types. After this commit Inventory users can now view and create E-Waybills without an Access Error. task-6515093 Forward-Port-Of: odoo/odoo#285023
A typo was corrected in the Italian electronic invoicing withholding tax reason. This helps ensure clearer, more accurate tax wording for Italian localization users without changing business processes.
Original PR description
Correction of a typo in italian withholding tax reason. opw-6514615 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284992
This fixes the Saudi Arabia localization upgrade so tax tags and VAT reporting grids are updated automatically when moving to Odoo 19. Businesses using the Saudi localization should no longer need manual corrections or localization reloads after migration.
Original PR description
**Issue:** During migration to Odoo 19, the SA tax tag migration logic is present in: "migrations/2.1/pre-migrate.py" was not executed during the migration of databases because this: * SA compact tax…
**Issue:** During migration to Odoo 19, the SA tax tag migration logic is present in: "migrations/2.1/pre-migrate.py" was not executed during the migration of databases because this: * SA compact tax tags were not renamed * VAT tax grids still use old tags * Localization reload was partially updating taxes * manual intervention was required Added logs/debugging in: * 2.1/pre-migrate.py * 2.1/end-migrate.py * 2.2/end-migrate.py - Verified in both local and customer databases that only the 2.2 migration path was executed during upgrade, while the 2.1 migration scripts were skipped because the Odoo 19 manifest upgrade path already targeted the 2.2 migration version. - 19:https://github.com/odoo/odoo/blob/67510fd36f7af31f83ef110602f9922ed5a43024/addons/l10n_sa/__manifest__.py#L6 **Solution:** - Bumped the l10n_sa module version to 2.3 to apply these changes to databases that are already in production. - Moved `migrations/2.1/pre-migrate.py` to `migrations/2.3/pre-migrate.py` to ensure the SA tax tag migration logic is executed during the `2.3` upgrade flow. - Moved `migrations/2.2/end-migrate.py` to `migrations/2.3/end-migrate.py` to align with the `2.3` version bump and refresh the SA tax mappings correctly during migration. * SA tax tags migrate correctly * VAT tax grids update automatically * invoices use new tags correctly * No manual localization reload required OPW - [6117408](https://www.odoo.com/odoo/project/70/tasks/6117408), [6224788](https://www.odoo.com/odoo/project/70/tasks/6224788) Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#264571
The Send to eTransport button is now shown when a Romanian stock transfer is ready as well as when it is completed. This helps users submit eTransport information at the right operational moment instead of waiting until the transfer is done.
Original PR description
Currently, the Send to eTransport button on `stock.picking` is only visible when picking is done. This PR fixes this behaviour and makes it visible when picking is ready or done both. task-5930984 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285429 Forward-Port-Of: odoo/odoo#257321
Point of Sale now correctly includes product variant extra prices when applying a pricelist that is based on another pricelist. This prevents undercharging or inconsistent checkout prices for products with paid variants.
Original PR description
## Steps to reproduce: - Create a product, with never variant, the variant has an extra price of 100 - Make Pricelist 1, just leave it as default - Make Pricelist 2, make it a discount, based on Pricelist 1, for all products - Go to the PoS, click on the created product - Change the pricelist to Pricelist 2 -> the price does not take the extra price into account ## Why the fix: When we have a pricelist based on another pricelist, we recursively calculate the price on the base pricelist. Before this commit, in the recursive call, we gave 0 as the extra price. We now give the extra price in the recursive function call. opw-6500086 Forward-Port-Of: odoo/odoo#286608 Forward-Port-Of: odoo/odoo#285371
Portal users can now successfully update the Electronic Format field when they add a company name to their address details. This prevents the field from appearing blank later and helps ensure customer invoicing preferences are retained correctly.
Original PR description
Steps: - Install accounting app. - Login with portal user and set `Company name` on `my/address`. - Try to edit `Electronic Format` field on my details. Issue: - `Electronic Format` field stays empty. Cause: - Since [PR](https://github.com/odoo/odoo/pull/211043) when user set `Company name` on the portal it'll create parent company and since `Electronic Format` is computed from `commercial_partner_id`, so when I update `Electronic format` field on `my/address` it'll set that value on `invoice_edi_format_store` on current address and now when I re-open `my/address` it'll compute `invoice_edi_format` from `commercial_partner_id`'s `invoice_edi_format_store` which is 'none' and it'll set `invoice_edi_format` to False and there is no way portal user can update that company's record Fix: - Update inverse of `Electronic Format` field to properly store invoice_edi_format_store value on commercial partner. Forward-Port-Of: odoo/odoo#277527
Users can now update analytic details in the bank reconciliation widget without being blocked by accounting lock date checks. This prevents unnecessary errors when only the analytic distribution is changed, while keeping normal lock date protections in place for other edits.
Original PR description
In the case of the bank rec widget, when modifying a line, we actually unlink it and create a new one. In that case, modifying the analytic would trigger the lock date error. But user should be allowed to modify the analytic all the time. In the edit of the bank rec widget, we add a context key that will be added if analytic distribution is the only key modified. task-6397993 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#277260
Website builder tests were adjusted to handle a Chrome change in how background image sizes are reported. This keeps automated checks stable across browser versions without changing what users see or how the website builder works.
Original PR description
In Chrome 152, single-value `background-size` properties may be serialized or expanded to include implicit dimensions (e.g., appending `auto` like `100px auto`), causing strict exact-string test assertions to fail. This commit updates `website` builder test expectations to use regex prefix matching or substring inclusion so tests remain reliable across different Chrome versions. runbot-946570 Forward-Port-Of: odoo/odoo#286094 Forward-Port-Of: odoo/odoo#285591
Early payment discount entries now keep the correct analytic distribution for all discount calculation methods. This prevents reporting details from being lost when invoices with mixed cost allocations are paid early.
Original PR description
_get_invoice_counterpart_amls_for_early_payment_discount_per_payment_term_line only split the early payment discount amount per invoice line (and thus preserved each line's own analytic distribution)…
_get_invoice_counterpart_amls_for_early_payment_discount_per_payment_term_line only split the early payment discount amount per invoice line (and thus preserved each line's own analytic distribution) when the payment term's early_pay_discount_computation was "included". For "mixed" and "excluded", it instead built a single counterpart line, discarding the analytic distribution of the invoice lines. Compute the per-invoice-line base amounts (and the corresponding price_unit for "mixed") for all three computations, keeping only the tax calculation restricted to "included". This way "mixed" and "excluded" discount entries now keep the analytic distribution of the invoice line they come from, while behavior for "included" is unchanged. Steps: - Create 3 payment terms with EPD, each one with a different `early_pay_discount_computation` setting - For each payment terms, create one invoice with two lines, only one having an analytic distribution - Confirm and pay the 3 invoices (early payment, discount applied) Issue: For 'mixed' and 'excluded', the discount line is the sum of the discount from the 2 invoice lines, and the analytic distribution is lost. opw-6380278 Forward-Port-Of: odoo/odoo#286816 Forward-Port-Of: odoo/odoo#282541
Restored or duplicated databases using the neutralize option now complete their safety changes before background scheduled jobs can start. This prevents copied environments from accidentally running automated tasks too early, reducing operational risk during database recovery or testing setup.
Original PR description
Before this commit: If you restore a backup or duplicate a database via /web/database/manager with "Neutralize" enabled, the cron workers had a small window between the creation of the Registry and the neutralization of the database where they could try to execute crons. Since neutralize_database does not require a full registry to run (it only does raw SQL operations), we can run it before the Registry creation (which has the effect, among other things, of making the cron workers aware of the new database). opw-6517819 Forward-Port-Of: odoo/odoo#286870 Forward-Port-Of: odoo/odoo#286532
This update makes product IDs sent to Google Analytics match the IDs used in Google Merchant Center feeds. It helps Google Ads correctly connect Shopping ad clicks with purchases, improving attribution and diagnostic reporting.
Original PR description
**Issue:** When google analytics (GA) and google merchant center (GMC) are setup, Google Ads diagonistic reports that item IDs cannot be matched to Merchant Center. **Why this happens:** `order_lines_2_google_api` sets `product.barcode or product.id` for `item_id`, while `product.feed._prepare_gmc_items` defaults to `product.default_code or product.id` for the feed's `id` field. Google Ads/Analytics attribution relies on GA4's `item_id` matching GMC's `id` for the same product to connect Shopping ad clicks to purchase events. **References:** https://support.google.com/merchants/answer/6324405?sjid=15224664355638221483-NC https://support.google.com/google-ads/answer/14943675?hl=en opw-6443326 Forward-Port-Of: odoo/odoo#285010
This fix ensures restaurant table orders keep their pending-sync status when another device refreshes the same table. It prevents locally added order lines from disappearing before they are saved and shared across devices.
Original PR description
Steps to reproduce: - Device A opens table 10 and adds a product - Device B opens table 10, then goes back to the floor plan - Device A goes back to the floor plan => The lines added on A are lost, they are never synced to the database, nor to the other device When B triggers a synchronisation, A reads the open orders from the server. The local lines of A are kept, but `setup`, which is also called when a record is updated, resets the dirty flag of the order. Going back to the floor plan calls `syncAllOrders`, which filters the order out because it is not dirty anymore, so the lines are never sent. The dirty flag is now kept when the record is updated with server data, since that data does not contain the changes made locally and must be synced in the next synchronization call task-id: 6486258 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286279 Forward-Port-Of: odoo/odoo#284701
Email delivery failure messages now display the configured outgoing mail server name instead of showing an empty or 'None' value. This makes failed email notifications clearer, helping users and support teams identify which mail server needs attention.
Original PR description
Steps to reproduce: 1. Run a local SMTP server that responds with a server error. The server file is provided in the task [refuse_smtp.py](https://github.com/user-attachments/files/27166855/refuse_smtp.py) 2. Create an outgoing server for this SMTP server 3. Send an email Issue: The delivery failure reason displays Mail delivery failed via SMTP server 'None' instead of the configured server name. Cause: ir.mail_server.send_email() builds the failure message from the smtp_server argument, but in the common path the mail is sent via mail_server_id. In that case, the actual SMTP server is resolved in connect(), while smtp_server remains unset, so the error message shows None. Solution: Store the resolved server label on the SMTP connection when opening it, and reuse that value when formatting send failures. opw-6139168 Forward-Port-Of: odoo/odoo#280750 Forward-Port-Of: odoo/odoo#261776
This fix prevents duplicate registration attempts from leaving a company database with outdated proxy credentials. It checks for an existing local user before contacting the external IAP service, reducing failed proxy connections and avoiding manual re-registration.
Original PR description
In the current flow, _register_proxy_user first calls IAP create_user, then inserts the returned credentials in account_edi_proxy_client.user. At the same time, IAP create_user_2 may replace an existing user with a new one with different credentials before local persistence settles. So Tx A calls IAP and gets credentials for remote user U1. Tx B calls IAP and gets credentials for remote user U2 and unlinks U1. Tx A persists U1 locally. Tx B fails local insert due to a unique constraint. Client DB keeps U1 credentials, but IAP now expects U2. Subsequent proxy calls from the client fail. The DB is left with stale, unusable credentials and cannot recover without re-registration. IAP: https://github.com/odoo/iap-apps/pull/1816 task-6520501 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285648
This fix ensures gift card lines in Spanish POS TicketBAI records use the correct refund direction based on the overall order. It prevents mismatches between product line totals and invoice totals, helping keep fiscal reporting consistent.
Original PR description
The issue fixed in commit[1] is again reproducible after commit [2] the pos order line for gift card is now not considered as a refund Step to reproduce: - Install pos_loyalty and…
The issue fixed in commit[1] is again reproducible after commit [2] the pos order line for gift card is now not considered as a refund Step to reproduce: - Install pos_loyalty and l10n_es_edi_tbai_pos with demo data - Create a gift card (add a tax to the discount product, any 0%) - start pos, add a product and use the gift card - fulfill the order - go to backend and open that order - In the TicketBAI XML, the values for the giftcard product will be positive, causing an inconsistency between the product line total and the invoice total [1] https://github.com/odoo/odoo/commit/0bbc5ebdcc7e014d87130b2ff9cee98e3aa7479a [2] https://github.com/odoo/odoo/commit/b17c9713e7a3305c240298fa54fcf1bc87a9bb8c FIX - we used to determine `sign` based on each order line's `is_refund` property - this property is quite sensitive as it depends on factors like line's qty, price, is reward or not. - so its better to depend on order's refund property for sign reversal https://github.com/odoo/odoo/blob/4fef2c5b69fac10594fac81b149d542ee4621b13/addons/point_of_sale/models/pos_order.py#L1768 opw-6226003 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286448 Forward-Port-Of: odoo/odoo#278042
Fixes incorrect totals in the French association balance sheet so active and passive totals reflect the right accounting entries. This helps associations relying on Odoo reports avoid misstated financial positions and duplicated amounts.
Original PR description
## [FIX] l10n_fr_reports: fix missing lines in association balance sheet ### Issue: The Active part of the Balance Sheet for associations shows an incorrect `TOTAL ACTIVE` — some accounts are not…
## [FIX] l10n_fr_reports: fix missing lines in association balance sheet ### Issue: The Active part of the Balance Sheet for associations shows an incorrect `TOTAL ACTIVE` — some accounts are not propagated to their parent aggregations ### Cause: `ACTIF_IMMOBILISE` was missing `BIEN_PAR_DONATION` in its formula `ACTIF_CIRCULANT` was missing both `DISPONIBILITES` and `INSTRU_FINAN` in its formula ### Steps to reproduce: - Install `l10n_fr_reports` and `accountant` - Create a new FR asso company and switch to it - In Accounting Settings, select: France - Associations accounting plan - Create a Journal Entry: Account: 240000, Debit: 100 (BIEN_PAR_DONATION) Account: 512001, Debit: 100 (DISPONIBILITES) Account: 520000, Debit: 100 (INSTRU_FINAN) Account: 509000, Credit: 300 - Open the Balance Sheet and select Balance Sheet for associations Before the fix, `TOTAL ACTIVE` is 0 instead of 300 -------------- ## [FIX] l10n_fr_reports: add force_date_scope to asso cross_report ### Issue: The Passive part of the Balance Sheet for associations shows an incorrect `TOTAL PASSIVE` — the same move line is counted twice, once in `Retained earnings` and once in `Profit or loss for the year` ### Cause: In 19.1, `cross_report` aggregations were refactored: https://github.com/odoo/enterprise/commit/e699a14a1922d6b8a5bc6b88ad9c02a411e4b073 By default, a `cross_report` no longer forces its `date_scope` to the terms it calls — `force_date_scope` must now be explicitly passed in the subformula The association balance sheet was added in 19.1 without this parameter, so `RESULT_LEXERCICE` (`from_fiscalyear`) and `REPORT_NOUVEAU` (`to_beginning_of_fiscalyear`) both used the current report's `date_scope` instead of their own This caused both expressions to match the same entries and double the `TOTAL PASSIVE` ### Steps to reproduce: - Install `l10n_fr_reports` and `accountant` - Create a new FR asso company and switch to it - In Accounting Settings, select: France - Associations accounting plan - Create a Journal Entry: Account: 512001, Debit: 100 Account: 701100, Credit: 100 - Open the Balance Sheet and select Balance Sheet for associations Before the fix, `TOTAL PASSIVE` is 200 instead of 100 opw-6520639 Forward-Port-Of: odoo/enterprise#130147
Users can now update analytic information in the bank reconciliation widget even when accounting lock dates would otherwise block line changes. This prevents unnecessary errors when only the analytic distribution is being adjusted, keeping reconciliation edits smoother and compliant with expected behavior.
Original PR description
In the case of the bank rec widget, when modifying a line, we actually unlink it and create a new one. In that case, modifying the analytic would trigger the lock date error. But user should be allowed to modify the analytic all the time. In the edit of the bank rec widget, we add a context key that will be added if analytic distribution is the only key modified. task-6397993 Forward-Port-Of: odoo/enterprise#124852
Export invoices in the Argentinian electronic invoicing module now send the correct recipient identification information for ARCA validation. This prevents incorrect identification labels and allows invoices to be verified as valid legal documents on the ARCA website.
Original PR description
Before this change we were sending id type code 0 and this generate two problems * ARCA verification page it was wrongly taking "CI Policia Federal" as the identification type of the receptor * We were not able to validate the expo invoice, we get always an error With this change the expo invoice can be checked as a real legal document in the ARCA page https://servicioscf.afip.gob.ar/publico/comprobantes/cae.aspx Forward-Port-Of: odoo/enterprise#126704
This fixes an issue where default values entered in website forms did not properly revert when using undo. It also keeps date and date-time field values consistent when changing field types, improving reliability for website editing and translation workflows.
Original PR description
The `value` property of the elements is not tracked by the history plugin, because `MutationObserver` does not produce mutations for that. This commit uses custom mutations when the `value` is changed, to restore the previous value on undo. Steps to reproduce: - Open website builder - Add a form - Set a "Default Value" on a text field - Press enter (to end preview) - Undo (with the button, or with focus out of the option's input) - Bug: The value shown in the page did not revert with undo Similar bug in translate mode task-6229671 Forward-Port-Of: odoo/odoo#286805 Forward-Port-Of: odoo/odoo#281232
This fixes an issue where the editor's color picker could fail to recognize the solid color tab when Odoo was used in a translated language. The change makes tab detection language-independent, improving reliability for users working outside English.
Original PR description
### Purpose of this PR: - The color picker tabs are registered with a translated name (`_t(Solid)`), and the tab button renders that name as its only content. ColorUIPlugin read the active button's `innerHTML` and compared it to the literal string Solid to know whether the solid tab was the one in use. - Rely on the `solid-tab` class instead, which is built from the untranslated tab id. task-6441654 Forward-Port-Of: odoo/odoo#279941
Creating a new CRM stage no longer displays a warning about recalculating opportunities. The warning now appears only when changing an existing stage, reducing confusion for sales teams while preserving useful guidance for edits.
Original PR description
Changing whether a CRM stage is won may trigger the recomputation of its opportunities. An onchange warning was added to inform users about this potentially expensive operation. However, the warning was also displayed when creating a stage because the onchange was triggered while initializing the form. Fix: Only display the warning when editing an existing stage, as it's useless to show this warning when creating a new stage. Task-6424174 Forward-Port-Of: odoo/odoo#284679
Currently, when a user dropships a kit product with FIFO/AVCO category costing, the computed cost of the kit on the Sales Order becomes 0. ## Steps to replicate: - Install Sales, Purchase, and Manufacturing modules. - Enable Margins and Dropshipping in settings. - Go to Product Categories > Goods and set costing method to FIFO - Create two products MOBO and CPU: - Cost: $300 - Category: Goods - Add a vendor in Purchase section with unit price same as cost - Enable Drop
Original PR description
Currently, when a user dropships a kit product with FIFO/AVCO category costing, the computed cost of the kit on the Sales Order becomes 0. ## Steps to replicate: - Install Sales, Purchase, and…
Currently, when a user dropships a kit product with FIFO/AVCO category costing, the computed cost of the kit on the Sales Order becomes 0. ## Steps to replicate: - Install Sales, Purchase, and Manufacturing modules. - Enable Margins and Dropshipping in settings. - Go to Product Categories > Goods and set costing method to FIFO - Create two products MOBO and CPU: - Cost: $300 - Category: Goods - Add a vendor in Purchase section with unit price same as cost - Enable Dropshipping route - Create a kit product Computer Kit with the same configuration as above (except cost) and add MOBO and CPU as components on its BoM. - Open the Computer Kit and click Compute Price from BoM. - Create and confirm a Sales Order with the Computer Kit. - Confirm the related Purchase Order and validate the dropship picking. - Return to the Sales Order > make the `Cost` field visible on SO lines . ## Observed Behavior: The product cost appears as 0 on the Sales Order, even though a price is set on the related Purchase Order. ## Root cause: When the dropshipping picking is confirmed, the method `_compute_purchase_price` is triggered to compute the cost on the Sales Order line. It calls `_get_price_unit_delivery` at [1], which then calls `_get_price_unit_dropshipped` at [2] since the products are dropshipped. Because dropshipping moves do not carry stock values, it calls `_get_value` at [3] to determine an appropriate value. This method uses `_get_value_data` at [4], which retrieves the value from the quotation via `_get_value_from_quotation` at [5]. Here, a cost ratio is applied at [6] to distribute the cost based on the BoM cost share (i.e., the percentage split of cost across kit components). Since no cost share is defined on the BoM, the ratio is 0, causing the final computed cost ratio to also be 0 at [7] and the cost value being returned as zero as shown in at [6]. **Why not in lower versions?** This issue did not occur in versions 18.4 and earlier due to the presence of the stock valuation layer and the defined logic for kit products to calculate cost, as shown in [8]. [1]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/sale_stock_margin/models/sale_order_line.py#L18-L21 [2]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/stock_account/models/stock_move.py#L672 [3]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/stock_account/models/stock_move.py#L681-L684 [4]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/stock_account/models/stock_move.py#L336 [5]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/stock_account/models/stock_move.py#L392-L397 [6]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/purchase_stock/models/stock_move.py#L225-L241 [7]: https://github.com/odoo/odoo/blob/d92feb4a6f463cae59aa107f46b5699f83f8ef9c/addons/purchase_mrp/models/stock_move.py#L12-L25 [8]: https://github.com/odoo/odoo/blob/7f1cd04259202bcafc94965d6420df360f9152c1/addons/mrp_account/models/product.py#L65-L89 ## Solution: It should not be assumed that users will always define a cost share on the Bill of Materials. In many cases, they may expect the kit price to be derived directly from the costs of its component products. To support this, we can override `_get_price_unit_dropshipped` to properly handle kit products, ensuring the cost is computed based on the component product costs instead. opw-6113398 Forward-Port-Of: odoo/odoo#261705
### Steps to Reproduce: 1). Install l10n_vn_edi_viettel ('Vietnam E-Invoicing') module in v18. 2). Migrate the database in any version above v18. 3). AccessError will appear while generating ('Send to SInvoice') on invoice for non-admin users. ### Issue: - In v18, users were able to send and generate documents via (Send to SInvoice). Since v18.1 onwards, field access [check] is enforced during this flow, and since `l10n_vn_edi_username` is restricted to admin users only [here], non-admi
Original PR description
### Steps to Reproduce: 1). Install l10n_vn_edi_viettel ('Vietnam E-Invoicing') module in v18. 2). Migrate the database in any version above v18. 3). AccessError will appear while generating ('Send…
### Steps to Reproduce:
1). Install l10n_vn_edi_viettel ('Vietnam E-Invoicing') module in v18.
2). Migrate the database in any version above v18.
3). AccessError will appear while generating ('Send to SInvoice') on invoice for non-admin users.
### Issue:
- In v18, users were able to send and generate documents via (Send to SInvoice). Since v18.1 onwards, field access [check] is enforced during this flow, and since `l10n_vn_edi_username` is restricted to admin users only [here], non-admin users hit an AccessError as soon as
`_l10n_vn_edi_get_credentials_company` reads this field on`res.company`.
```py
You do not have enough rights to access the field "l10n_vn_edi_username" on Companies (res.company). Please contact your system administrator.
Operation: read
User: 12
Groups: allowed for groups 'Role / Administrator'
```
### Solution:
- This commit fixes the issue by adding a `sudo()` call on the company inside [_l10n_vn_edi_get_credentials_company] itself, so that non-admin users can successfully send and generate documents like in the previous version, without any hassle.
[check]: https://github.com/odoo/odoo/blob/5ca10578a2fd1b40cd371ed5ad20c1654dfe54d3/odoo/orm/models.py#L3384
[here]: https://github.com/odoo/odoo/blob/5ca10578a2fd1b40cd371ed5ad20c1654dfe54d3/addons/l10n_vn_edi_viettel/models/res_company.py#L9
[_l10n_vn_edi_get_credentials_company]: https://github.com/odoo/odoo/blob/ecc267a231958c2dd99a7287c6bd1adbdbd22965/addons/l10n_vn_edi_viettel/models/account_move.py#L885
Ticket [link](https://www.odoo.com/odoo/project.task/6434854)
opw-6434854
Forward-Port-Of: odoo/odoo#281396Fixed an issue in Discuss where the emoji picker could crash after users selected emojis during a search and then cleared the search field. This improves chat reliability by keeping the picker stable during normal emoji selection workflows.
Original PR description
Steps to reproduce: - open Discuss, open any chat, open the emoji picker (no 'Frequently used' emojis) - search a term and select emojis without closing the picker (shift+click on desktop, plain…
Steps to reproduce:
- open Discuss, open any chat, open the emoji picker (no 'Frequently used'
emojis)
- search a term and select emojis without closing the picker (shift+click on
desktop, plain click on mobile)
- clear the search with backspace
=> traceback: 'Cannot read properties of null (reading
`getBoundingClientRect`)' in adaptNavbar().
This happens because when we clear the search input it calls
`highlightActiveCategory()`, which sets `categoryId` to the topmost category of
the grid, which is now the 'Frequently used' category (sortId 0), added to the
picker since the emojis we just picked updated the recent state. To update the
navbar, `currentNavbarPanel` then looks for the panel holding it in
`emojiNavbarRepr`, but that representation is only built in `adaptNavbar()`,
which runs on mount and from the `ResizeObserver` only, so it was built without
the 'Frequently used' category and no panel contains it. It returns undefined,
the navbar renders empty, its size change wakes the `ResizeObserver`, and
`adaptNavbar()` crashes on querySelector('.o-Emoji').getBoundingClientRect()`.
This commit solves the issue by rendering the `recentEmojis` from a snapshot
taken when the picker is opened, so they are not added to the picker while the
while `emojiNavbarRepr` does not contain their category id.
partial backported PR: https://github.com/odoo/odoo/pull/281104
Task-[6204249](https://www.odoo.com/odoo/project/1519/tasks/6204249)
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#286850
Forward-Port-Of: odoo/odoo#284372