Thursday, August 27, 2026
34 changes · saas-19.4
Enhancements to existing features
Timesheet Assistant now matches Gmail activities more reliably by prioritizing learned subject-based matches before falling back to email address matching. When an email address is shared by multiple contacts, it also prefers the contact linked to a user with the same email, reducing incorrect timesheet suggestions.
Original PR description
Forward-Port-Of: odoo/enterprise#127850
Resolved issues and error corrections
The Inventory Valuation report now includes accounting balances for products that currently have zero stock when multiple valuation accounts are used. This helps finance teams see and reconcile required balancing entries without losing the performance benefit of skipping full value calculations for zero-quantity products.
Original PR description
## Problem If multiple valuation/variation accounts are used, the Inventory Valuation report will not show the balance of accounts attached to products that have 0 quantity available if a different…
## Problem If multiple valuation/variation accounts are used, the Inventory Valuation report will not show the balance of accounts attached to products that have 0 quantity available if a different account has quantity. ## Solution In order to maintain the performance improvements intended by the commit that introduced the `qty_available != 0` filter, we will avoid calculating `total_value` for products with 0 quantity. We will still run `stock_accounting_value` on these products in order to capture interim accounting value on the Inventory Valuation report. ## Steps to Reproduce (Runbot v19) (defer to the test for more info) 1. Create an extra set of valuation/variation accounts 2. Create a product, avco perpetual accounting the default valuation/variation accounts 3. Create a second product, avco perpetual accounting the new valuation/variation accounts 4. Purchase 1 unit of each of the products and receive, bill both 5. Sell 1 unit of the product attached to the new valuation/variation 6. Go to Accounting > Review > Inventory Valuation, and note that the new valuation/variation accounts are not present. If you click Generate Entry, you will see that these accounts need to be balanced opw-6473319 Forward-Port-Of: odoo/odoo#283787 Forward-Port-Of: odoo/odoo#282819
Inventory users can now adjust a product's quantity on hand directly from the product form, matching the access they already had in the Inventory Adjustments menu. This removes an unnecessary workaround and makes stock updates more consistent for day-to-day inventory operations.
Original PR description
Steps to reproduce the bug: - Log in as a user with only the "Inventory / User" access right and Products/Create (product.group_product_manager) granted (write access to…
Steps to reproduce the bug:
- Log in as a user with only the "Inventory / User" access right and
Products/Create (product.group_product_manager) granted (write access
to product.product/product.template)
- Open a storable product's form view.
- Observe the "Quantity On Hand" field is readonly, and the "On Hand"
quants popup opened from it is read-only too.
- Go to the Inventory > Physical Inventory / Inventory Adjustments menu instead.
- Observe the same user can freely edit the quantity and apply the inventory adjustment.
Problem:
A stock user could apply inventory adjustments from the Inventory
Adjustments menu, but could not perform the exact same action from
the product form, forcing an unnecessary detour.
Three places in `stock` still gated editing to `stock.group_stock_manager`,
even though `inventory_mode` is already granted to any `stock.group_stock_user`
by `stock.quant._set_view_context()`, and the underlying write is already
guarded correctly by `_is_inventory_mode()`:
- `stock.quant._get_quants_action()` only picks the editable tree view
(used by the "On Hand" quants popup) for managers:
https://github.com/odoo/odoo/blob/19.0/addons/stock/models/stock_quant.py#L1328
- The product form's own "Quantity On Hand" field/link
(`product_views.xml`) is only made editable for managers, and forced
readonly for everyone else:
https://github.com/odoo/odoo/blob/19.0/addons/stock/views/product_views.xml#L192-L196
- The `inventory_quantity_auto_apply` field itself (the one actually
rendered in the editable quants list, whether opened from the product
form or the Forecasted Report) is restricted to managers at the Python
field-definition level:
https://github.com/odoo/odoo/blob/19.0/addons/stock/models/stock_quant.py#L100-L104
All three checks were left over from before commit
https://github.com/odoo/odoo/commit/37d96f49ccc85fa651f092b6c32bab1af2c34f2d,
which gave `stock.group_stock_user` write access on `stock.quant`
(see `ir.model.access.csv`) and dropped the manager-only restriction on
`action_apply_inventory`. The ACL and the Inventory Adjustments flow
were updated at the time, but these three entry points were not, leaving
them stricter than the rest of the permission model.
opw-6439844
Forward-Port-Of: odoo/odoo#282010Swedish ISO20022 vendor payment batches now generate files that match the configured pain.001.001.09 format. This prevents banks from rejecting payment files that were mislabeled as the newer format but still contained older-format content.
Original PR description
**Steps to reproduce:** - Install Accounting and l10n_se - Switch to a Swedish company (e.g. SE Company) - In Accounting settings, set "Identification" with anything - In Bank journal: * Set an…
**Steps to reproduce:** - Install Accounting and l10n_se - Switch to a Swedish company (e.g. SE Company) - In Accounting settings, set "Identification" with anything - In Bank journal: * Set an account number * Make sure "Swedish ISO20022" is available in "Outgoing Payments" * Set "pain.001.001.09" as "XML Format" in "Outgoing Payments" - Create a vendor payment: * Vendor: [a vendor with a trusted bank account] * Payment Method: Swedish ISO20022 * Amount: [any] - Confirm the payment - From the payments list, select the payment and create a batch - Validate the batch payment **Issue:** When the batch is validated, a `pain.001.001.09` file should be generated. However, its content is that of a `pain.001.001.03` file, even if the version reported in the file is `pain.001.001.09`. For example, `<ReqdExctnDt>` should contains a subnode `<Dt>` in `001.001.09`, which is not the case. It leads to the file being rejected as non-compliant to `pain.001.001.09`. opw-6472050 Forward-Port-Of: odoo/enterprise#129234 Forward-Port-Of: odoo/enterprise#128598
This fixes cases where grouped data requests using the same field more than once could return incomplete or duplicated-looking results. It prevents downstream errors in reports or views that rely on those grouped results being returned in the expected format.
Original PR description
`_read_grouping_sets` dispatches each SQL result row to the grouping set(s) that requested it, using the `GROUPING()` bitmask as the key. When a grouping set repeats a groupby spec, e.g. `['foo',…
`_read_grouping_sets` dispatches each SQL result row to the grouping set(s) that requested it, using the `GROUPING()` bitmask as the key.
When a grouping set repeats a groupby spec, e.g. `['foo', 'foo']`, it computes the exact same bitmask as the grouping set for the deduplicated column alone (`['foo']`).
Two grouping sets sharing a bitmask were treated as interchangeable duplicates: only the first one seen got its extractor registered, and its already-extracted result list was later blindly copied onto every other grouping set sharing that bitmask.
This is wrong whenever those sets don't actually share the same shape:
`_read_grouping_sets(grouping_sets=[['foo'], ['foo', 'foo']])`
returned
`[('my_foo_val', <aggregate>), ('my_foo_val', <aggregate>)]`
instead of
`[('my_foo_val', <aggregate>), ('my_foo_val', 'my_foo_val', <aggregate>)]` (repeating the value, as `_read_group` does).
Callers unpacking crashed with `ValueError: not enough values to unpack`.
Fix:
- Deduplicate the SQL terms of each grouping set before building its `GROUPING SETS (...)`, so that grouping sets which are physically the same always produce a single row from PostgreSQL.
- Register every grouping set's own extractor under its bitmask in a list, instead of keeping only the first one seen, and dispatch each result row to all of them.
task-6511626
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#284683Website orders now use the customer’s saved delivery and invoice addresses when selecting the default fiscal position. This prevents repeat purchases from applying taxes based on the account holder’s main country instead of the actual shipping destination.
Original PR description
Issue: --- Fiscal position is wrongly set to `self.env.user.partner_id.country_id` instead of `partner_shipping` country, if `partner_shipping_id` is not changed in the checkout process. Steps: 1-…
Issue: --- Fiscal position is wrongly set to `self.env.user.partner_id.country_id` instead of `partner_shipping` country, if `partner_shipping_id` is not changed in the checkout process. Steps: 1- Create two auto detect fiscal positions: France, Germany 2- Set portal user's partner address country to France. 3- Using portal user, shop from website, and create a delivery address. 4- Pay and confirm the order. 5- Using the admin user, you check the SO's FP which is correctly set to Germany. 6- Using portal user, again shop from website, and don't change address. Keep previous shipping address which is Germany. 7- Confirm and pay the order. 8- Using admin user, check the new SO's FP. It's set to France. Cause: --- `_compute_fiscal_position_id` in SO depends on `partner_shipping_id`. When the `partner_shipping_id` is not changed, the fiscal position value set in create will remain. This value is set in `Website._prepare_sale_order_values()`. The `fiscal_position_id` is set to self.fiscal_position_id, which is `_get_fiscal_position(self.env.user.partner_id)`. Fix: --- If the user has already a SO, we can use last SO's shipping address and invoice address to calculate FP in `_prepare_sale_order_values`. opw-6357638 Forward-Port-Of: odoo/odoo#281201 Forward-Port-Of: odoo/odoo#276485
The Documents search panel now handles multiple shortcuts pointing to the same document without crashing. This improves reliability for users organizing documents with shortcuts and avoids interruptions when browsing or filtering documents.
Original PR description
This commit prevents the Documents search panel from crashing with an "Expected singleton" error. `grouped` does not de-duplicate: it extends each group with the record ids as given, so as soon as two shortcuts point to the same document, the browsed target ids contain that id twice and reading `user_permission` on the group fails. Browsing through an `OrderedSet` keeps the batched read while guaranteeing one record per group. Forward-Port-Of: odoo/enterprise#129394 Forward-Port-Of: odoo/enterprise#129134
Customers placing self-orders with no amount due are no longer sent to an unnecessary payment page. This makes checkout faster and smoother for free items, fully discounted orders, or event flows where the total is zero.
Original PR description
Before this commit: -------- - Self-orders with a total amount of zero are still redirected to the payment page, which was unnecessary. After this commit: -------- - The payment step is now skipped for zero-amount self-orders, providing a smoother checkout flow. task-5106938 Forward-Port-Of: odoo/odoo#283832 Forward-Port-Of: odoo/odoo#230218
This fixes planning slots so allocated hours stay accurate when resources are added or removed. Field service schedules now calculate break time using average working hours per resource, preventing incorrect totals such as doubling hours or reducing them unexpectedly.
Original PR description
## Steps to reproduce: - Install planning_field_service - Create a planning slot with one resource - Add another resource for the slot we created - Notice the allocated_hours now equals 16h - Remove one of the resources - Notice now the allocated hours are 5h instead of 8h ## Cause: When adding a second resource in a slot and while computing the break_time we use the working hours of all of the resources combined instead of dividing by the number of resources and this messes up the break_time calculation which affects the allocated_percentage and at the end when trying to compute the allocated hours it will be wrongly calculated ## Fix: We divide the working hours by the number of resources to be able to compute the break_time correctly. opw-6307906 Forward-Port-Of: odoo/enterprise#125641
This fix prevents the online shop from showing an error when a published product has had all of its variants deleted. The shop now uses the main product information as a fallback, keeping the storefront accessible for customers.
Original PR description
Currently, an error occurs when user deletes all the variants of a product and opens the website raises an error. Steps to replicate: - Install `website_sale`, open settings and turn on `Product…
Currently, an error occurs when user deletes all the variants of a product and opens the website raises an error. Steps to replicate: - Install `website_sale`, open settings and turn on `Product Reference Price` and `Product Variants`. - Create a new product `test` , give an attribute and 2 values. - Click on the `Variants` smart button, select all and delete. - Publish this produce in website and open shop on website. Error: ``` ValueError: Expected singleton: product.product() ``` Cause: - Issue occurs after a recent addition of a feature that shows product's unit price on website (check [PR]). - As the user deleted all the variants of the product `test`, when trying to get the unit price of the product's variants [1] causes this error to occur. Solution: - If the product doesnt have any variants we should get the unit price from the product template itself. [PR]: https://github.com/odoo/odoo/pull/254309 [1]: https://github.com/odoo/odoo/blob/22ede9cd601f54ebf248a7ddace433746f066e68/addons/website_sale/models/product_template.py#L686 sentry-7635385579 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
The shop search now uses the same product information for both displayed results and filter counts. This prevents hidden website formatting text from creating misleading matches, making search results and facets more consistent for shoppers.
Original PR description
The `/shop` product results and facets use different search fields. In particular, facets search raw `website_description` HTML, causing terms such as `weight` to match CSS like `font-weight` and process far more products than are displayed. Use one shared field list for both paths: - `name` - `variants_default_code` - `description_sale` - `description_ecommerce` Stop searching `default_code`, internal `description`, and raw `website_description`. opw-6391984 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282470 Forward-Port-Of: odoo/odoo#280720
Kenyan POS refunds sent to eTIMS now reference the original sale's KRA invoice number instead of the refund's own order number. This prevents valid refunds from being rejected by eTIMS due to mismatched invoice, item, or amount checks.
Original PR description
Steps to reproduce: - Set up a company in Kenya with eTIMS. - Sell an order from the POS and send it to eTIMS. - Refund that order and send the refund to eTIMS. Cause of the issue: When we build the…
Steps to reproduce: - Set up a company in Kenya with eTIMS. - Sell an order from the POS and send it to eTIMS. - Refund that order and send the refund to eTIMS. Cause of the issue: When we build the JSON for eTIMS, the "orgInvcNo" field (the KRA invoice number of the order we are refunding) was always set to `self.sequence_number`, which is just the order's own number in its session. This is wrong for a refund: eTIMS then can't find the item on "the original invoice" (since it's looking at the wrong invoice), and also can't check the amounts, since it's comparing them to the wrong order. eTIMS then rejects the refund with a 910 error, like "item sequence ... does not exist on the original invoice" or "amount is incorrect for item ...". The invoice-based flow (account_move.py) already does this the right way, using `reversed_entry_id.l10n_ke_oscu_invoice_number`, but the POS order flow was not doing the same thing. Solution: For a refund order, use the KRA invoice number of the refunded order (`refunded_order_id.l10n_ke_oscu_order_number`) instead of the refund's own sequence number. Normal sales keep working like before. opw-6445174 Forward-Port-Of: odoo/enterprise#128387
Inventory users can now validate dropship transfers for average-cost products when landed costs are enabled. This prevents unnecessary access errors and keeps the sales-to-purchase dropshipping flow moving without requiring administrator rights.
Original PR description
# How to reproduce - Activate the stock_landed_costs module - Enable Dropshipping - Create a product with : - Category : - Costing Method : AVCO - Inventory Valuation : Perpetual - Routes : Dropship…
# How to reproduce - Activate the stock_landed_costs module - Enable Dropshipping - Create a product with : - Category : - Costing Method : AVCO - Inventory Valuation : Perpetual - Routes : Dropship - Atleast one vendor - Create a SO for that product - Confirm the SO & then Confirm the associated PO - Login as an user with "User" rights for Inventory - Try to validate the Dropship transfer # The issue You get an access error. If the same flow is done with a product with a Standard Price costing method, then the Dropship is properly validated # Cause When validating the Dropship, we'll call `_action_done` on the moves. This will trigger an update of the standard price of the product : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/stock_move.py#L177 https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/stock_move.py#L345-L349 Since we're in avco, this will run the `_run_average_batch` method : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/product.py#L675 That will fetch the value of each moves. For the Dropship moves, it'll do so by calling the `_get_value()` method : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/product.py#L486 This method will compute the value of the move, notably by using the associated landed costs : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/stock_move.py#L431 https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_landed_costs/models/stock_move.py#L14 Now the issue is that this computation calls `_read_group` on 'stock.valuation.adjustment.lines' that are retricted to inventory administrators : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_landed_costs/models/stock_move.py#L11 https://github.com/odoo/odoo/blob/5f6fb63d5d7585805642c702d096b2f882e73761/addons/stock_landed_costs/security/ir.model.access.csv#L4 # Proposed solution Get the value of the move in sudo like previously done in the flow : https://github.com/odoo/odoo/blob/60bc7ae38e335958589c172df88e059bf0738cac/addons/stock_account/models/stock_move.py#L314 opw-6323645 Forward-Port-Of: odoo/odoo#283420 Forward-Port-Of: odoo/odoo#273102
This fix prevents the main website menu from being marked as a mega menu when it already contains child menu items. It avoids migration failures and blocked databases caused by later modules adding menus under that main menu.
Original PR description
Issue: ------- After the fix: https://github.com/odoo/odoo/commit/f1557211d9e7f83761bb36e4800e4c2f62b234c5 we can't create a child menu for a mega menu or a menu can't be a mega menu when there's an…
Issue:
-------
After the fix:
https://github.com/odoo/odoo/commit/f1557211d9e7f83761bb36e4800e4c2f62b234c5 we can't create a child menu for a mega menu or a menu can't be a mega menu when there's an existing child menu except the case of top level menu i.e; (url: /default-main-menu) and that menu will have no parent_id obviously...
Now, as per the above pr conditions the top level can be set as mega menu since it has no parent id. And in version 17.3 in the pr https://github.com/odoo/odoo/commit/47af533e9f5f721b63570d3b301951f3855384a1 a 'Jobs' menu is being created and its parent_id refers to that top level menu which we have set as mega menu. And when the records gets validated during migration the database will get blocked.
Solution:
-----------
Restrict the user by throwing the same user error, when checking/selecting the top level menu as mega menu since it has existing child menus.
Step to reproduce:
-----------------------
1. Create a database in version 17.0 with 'website_hr_recruitment' installed.
2. Go to website menus, set a top level menu(/default-main-menu) as mega menu.
3. Migrate the database to version 18.0 or more.
Traceback:
```
File "/home/odoo/src/odoo/18.0/odoo/models.py", line 5297, in _create
records._validate_fields(name for data in data_list for name in data['stored'])
File "/home/odoo/src/odoo/18.0/odoo/models.py", line 1636, in _validate_fields
check(self)
File "/home/odoo/src/odoo/18.0/addons/website/models/website_menu.py", line 95, in _validate_parent_menu
raise UserError(_("A mega menu cannot have a parent or child menu."))
odoo.exceptions.UserError: A mega menu cannot have a parent or child menu.
File "/home/odoo/src/odoo/18.0/odoo/tools/convert.py", line 603, in _tag_root
raise ParseError('while parsing %s:%s, somewhere inside\n%s' % (
odoo.tools.convert.ParseError: while parsing /home/odoo/src/odoo/18.0/addons/website_hr_recruitment/data/config_data.xml:13, somewhere inside
<record id="website_menu_jobs" model="website.menu">
<field name="name">Jobs</field>
<field name="url">/jobs</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence">59</field>
</record>
```
Ref Images:
Before Fix:
<img width="1598" height="599" alt="image" src="https://github.com/user-attachments/assets/ef719945-a11b-4134-97f8-4b583c4ea6bc" />
After Fix:
<img width="1582" height="633" alt="image" src="https://github.com/user-attachments/assets/da326e45-0de8-4d42-ad47-845bfaedc84e" />
OPW - 6094298
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#263025Peruvian electronic invoices no longer get stuck when SUNAT has registered them but has not yet made the confirmation document available. Odoo now keeps retrying automatically until the confirmation can be retrieved, reducing manual follow-up and failed invoice processing delays.
Original PR description
Steps to reproduce: - Post a Peruvian invoice so it is sent to SUNAT (directly or through Estela/Digiflow). - SUNAT's sendBill call hangs and Odoo's request times out (ReadTimeout / ConnectionError),…
Steps to reproduce: - Post a Peruvian invoice so it is sent to SUNAT (directly or through Estela/Digiflow). - SUNAT's sendBill call hangs and Odoo's request times out (ReadTimeout / ConnectionError), even though SUNAT actually finishes registering the document on its side a moment later. - Odoo retries sending the same invoice (either automatically through the EDI cron, or manually). SUNAT now replies with a "document already exists" SOAP fault (code 1033/4000), since it processed the previous attempt. - Odoo tries to recover from this by fetching the CDR through getStatusCdr, but SUNAT has not finished generating it yet, so the lookup also fails. Cause of the issue: _l10n_pe_edi_post_invoice_web_service() already has recovery logic for error codes 1033/4000: it calls _l10n_pe_edi_retrieve_cdr() to fetch the CDR and treat the invoice as sent. But when that lookup itself fails (CDR not generated yet), the resulting error keeps the 'blocking_level' set to 'error' from the original SOAP fault. Documents with blocking_level 'error' are excluded from the automatic EDI cron retries (see account.edi.document._cron_process_documents_web_services), so the invoice gets stuck needing a manual retry, which can lose the same race against SUNAT again and again. Solution: When the CDR can't be retrieved yet after a 1033/4000 duplicate error, mark the result as 'blocking_level': 'warning' instead of leaving it at 'error'. This keeps the invoice eligible for the automatic EDI cron retries, so Odoo keeps polling SUNAT until the CDR becomes available, instead of requiring manual intervention every time this race is lost. opw-6393231 Forward-Port-Of: odoo/enterprise#127484 Forward-Port-Of: odoo/enterprise#125053
Uploading a refund from bank transactions now creates a vendor credit note in the correct purchase journal instead of a customer credit note. This helps refunds reconcile properly against supplier accounts and avoids accounting errors during bank reconciliation.
Original PR description
**Steps to reproduce:** * Install **Accounting** module. * Go to **Accounting → Bank → Transactions** (bank reconciliation widget). * Open a transaction with a **positive** amount (e.g. a vendor…
**Steps to reproduce:** * Install **Accounting** module. * Go to **Accounting → Bank → Transactions** (bank reconciliation widget). * Open a transaction with a **positive** amount (e.g. a vendor sending money back). * Click the three-dot menu on the transaction line and choose **Upload a Refund**. * Upload any document (XML or PDF). **Observed behavior:** * A **Customer Credit Note** (`out_refund`) is created in a **sale** journal instead of a **Vendor Credit Note** (`in_refund`) in a **purchase** journal. * The wrong document type means the reconciliation fails to link the refund against the correct payable account. **Cause:** * In `create_document_from_attachment` (`account_bank_statement.py`), the JS widget sends `type='sale'` in context when the transaction amount is positive (JS: `amount > 0 ? "sale" : "purchase"`). * The original code mapped `type='sale'` → `default_move_type='out_refund'` (customer credit note) and searched for a `sale` journal — both wrong. * Uploading from a bank statement is always a **vendor-side** operation: negative amount = vendor bill (`in_invoice`), positive amount = vendor refund (`in_refund`). The `type` context key from JS reflects transaction direction, not the accounting document type. * Additionally, `in_refund` is a purchase document; Odoo's `_check_journal_move_type` constraint raises a `ValidationError` if a purchase document is created in a non-purchase journal, meaning the old code would crash at the ORM level for the refund path. **Fix:** * Map `type='sale'` → `default_move_type='in_refund'` (vendor credit note) instead of `out_refund`. * Always search for a **purchase** journal regardless of the `type` context value, since both `in_invoice` and `in_refund` are purchase-side documents. opw-6468779 Forward-Port-Of: odoo/enterprise#127912
The website setup assistant now gives clearer guidance when recommending visual themes, so illustration-heavy designs are suggested mainly for abstract industries. This helps businesses get website themes that better match customer expectations, especially when real photos are more appropriate.
Original PR description
The AI theme recommendation prompt gave no guidance on illustration vs photo themes, so illustration-based themes were picked for industries better served by real pictures.
The Uzbekistan accounting setup now classifies current-year and period profit/loss accounts correctly. This prevents profit figures from being counted twice in the Equity section of the balance sheet, improving accuracy for financial reporting.
Original PR description
Accounts 8710 (Current Year Profit/Loss) and 9910 (Net Profit for the Period) were equity_unaffected, causing their balances to be picked up both by the retained earnings tag-based formula and by the current-year-earnings domain formula in l10n_uz_reports, double- counting them in the balance sheet's Equity section. This commit changes both accounts' type to Equity and adds the BS Line 0540 tag to 9910 (8710 already carried it), so their balances are captured through the tag alone. see https://github.com/odoo/enterprise/pull/129350 task-6361059
Pricing rules created from a product variant now remain linked to that specific variant instead of being applied to the broader product template. This ensures variant-level prices are saved accurately and prevents unintended pricing changes across other variants.
Original PR description
Issue: --- When you apply pricing on product variant form, pricing is instead applied on product template. Steps to reproduce: 1- Open a product variant. 2- From prices tab, add a pricelist rule. Save the variant. 3- Re-open pricelist rule. As you see, the variant is not set. Cause & Fix: --- This is because `applied_on` is changed to `1_product` when `display_applied_on` is set to `1_product`. However, `display_applied_on` is also set to `1_product` when item is created from variant. We can check that case using `default_product_id`. opw-6421193 Forward-Port-Of: odoo/odoo#284167 Forward-Port-Of: odoo/odoo#280303
Belgian Acerta payroll exports now include weekend days when an employee's qualifying leave overlaps a weekend. This ensures the report matches Acerta's expected format and avoids missing leave information in payroll reporting.
Original PR description
## Steps to reproduce: - Install l10n_be_hr_payroll_acerta - Create an employee in a belgian company - Create a sick time off for the created employee that overlaps with a weekend - Export acerta report for the employee - Notice the weekend that overlaps with the time off is not present in the report ## Cause: While exporting the report file we only loop over the created work entries' dates and since weekends doesn't have work entries we don't consider them in the report. ## Fix: When generating the line of a leave's start date we check if the leave overlaps with a WE, we fetch the WE's date and we generate a line for each day of the WE. According to Acerta this is the correct behavior for their reports for specific types of leaves. **opw-6313534** Forward-Port-Of: odoo/enterprise#129241 Forward-Port-Of: odoo/enterprise#124500
The Uzbek balance sheet now includes current-year profit or loss in the equity section, so totals remain balanced before year-end closing. This improves the reliability of statutory financial reporting without changing the official report line numbering.
Original PR description
The Uzbek balance sheet was unbalanced because the current year's unclosed profit/loss was not reflected in the Equity section. This commit restructures '[0540] - Retained Earnings' into an aggregate of three lines: realized retained earnings (existing tag-based formula), current year unallocated earnings, and previous years' unallocated earnings, the latter two computed from income, expense and equity_unaffected accounts, scoped to the current and prior fiscal years respectively. This keeps the balance sheet correct both before and after year-end closing, without changing the report's official line numbering. see https://github.com/odoo/odoo/pull/284693 task-6361059
French VAT declarations in a refund position will no longer include an unnecessary electronic payment order. This prevents DGFiP rejections for refund cases while keeping normal VAT payment submissions unchanged.
Original PR description
`_prepare_edi_vals` always called `_get_formatted_payment_values()`, adding an EDI-Paiement (telereglement) block to the T-IDENTIF of the 3310CA3, regardless of whether the company owes VAT or is in…
`_prepare_edi_vals` always called `_get_formatted_payment_values()`, adding an EDI-Paiement (telereglement) block to the T-IDENTIF of the 3310CA3, regardless of whether the company owes VAT or is in a credit position. Steps to reproduce: - French company in a VAT credit position, requesting a refund. - Fill a bank account line, the account to receive the refund and send the VAT report to the DGFiP. Current behaviour: The DGFiP returns a negative acknowledgement on the CA3 interchange: "Telereglement 1 rejete: Montant telereglement absent ou invalide. Code erreur : 018", even though the declaration itself is accepted. The wizard's bank account lines are reused for two opposite purposes: the account to debit when VAT is due, and the account to credit when a refund is asked. `_get_formatted_payment_values()` builds a payment order from them unconditionally, so a telereglement for the credit amount is emitted in the refund case. A telereglement is invalid when no VAT is due, hence error 018. A return nets to either a payment or a credit, never both, so the two cases are mutually exclusive. This commit guards the call with `self.is_vat_due`, so the telereglement is only generated when the company actually owes VAT. The VAT-due flow is unchanged. opw-6275695 Forward-Port-Of: odoo/enterprise#124694 Forward-Port-Of: odoo/enterprise#120840
This fix updates Belgian payroll rules so the ONSS 3000 deduction is calculated correctly for the second and third quarters of 2026. It helps ensure payroll declarations and related accounting remain compliant with the latest Belgian requirements.
Original PR description
Forward-Port-Of: odoo/enterprise#125838 Forward-Port-Of: odoo/enterprise#124034
Colombian electronic invoicing now handles missing or malformed DIAN response attachments when building commercial event history. This prevents crashes during acknowledgement actions and keeps vendor bill processing more reliable when past responses are incomplete or invalid.
Original PR description
When building the commercial event history for DIAN documents, the system crashes if an intermediate document's attachment is missing or is not a valid ZIP file. Steps to reproduce: - Create a vendor bill and send it to DIAN to generate a commercial event document. - Manually alter or corrupt the attachment of the first response document (e.g., save plain XML instead of a ZIP). - Click "Acusar Recibo" (Acknowledge Reception) on the bill. Issue: The system crashes when attempting to unzip a malformed, plaintext, or missing attachment while iterating through past documents to build the event history. Analysis: The system aggregates the XML of previous events to maintain the history trail. Doing so, the system assumes that all past attachments are ZIP archives. However, interacting with the DIAN API can occasionally result in plaintext XML, that raises `zipfile.BadZipFile` when unzipping. opw-5467690 Forward-Port-Of: odoo/enterprise#121494
This fixes an accounting issue where product costs could be overstated after a sale, return, credit note, and re-sale sequence. Credit notes are now included when offsetting earlier cost entries, so reported cost of goods sold stays accurate and avoids inflated margins or inventory cost reporting.
Original PR description
**Steps to reproduce:** - create a storable product with a positive quantity a cost of 10 and average perpetual category - confirm a SO for 1 quantity, validate delivery - confirm invoice for 1 (COGS…
**Steps to reproduce:** - create a storable product with a positive quantity a cost of 10 and average perpetual category - confirm a SO for 1 quantity, validate delivery - confirm invoice for 1 (COGS should be 10) - return the delivery and validate - create a credit note from the invoice for 1 and confirm (COGS should be 10) - return the return and validate - change the standard price to 100 - create an invoice from the SO for 1 and confirm **Current behavior:** cogs are 190 **Expected behavior:** cogs should be 100 **Cause of the issue:** _get_posted_cogs_value doesn't take into account the credit notes (only the account moves with type 'out_invoice' are taken into account in the sum) https://github.com/odoo/odoo/blob/0824dc24665de6bfa805d540e756cdcb006edba6/addons/sale_stock/models/account_move.py#L185-L186 So in our case the first invoice and the credit note don't cancel out each other. The same goes for _get_cogs_qty (which returns the total cogs past + current), in the past cogs it doesn't take into account the quantities of the credit note. https://github.com/odoo/odoo/blob/0824dc24665de6bfa805d540e756cdcb006edba6/addons/sale_stock/models/account_move.py#L172-L174 So the quantity of the first invoice and the one of the credit note don't cancel out each other. As a result, the return value from _get_cogs_value() for the second invoice is : price unit = 100 returned by _get_cogs_price_unit() https://github.com/odoo/odoo/blob/0824dc24665de6bfa805d540e756cdcb006edba6/addons/stock_account/models/account_move_line.py#L68 which returned the standard price because the product has an average cost method https://github.com/odoo/odoo/blob/0824dc24665de6bfa805d540e756cdcb006edba6/addons/stock_account/models/stock_move.py#L275-L280 cogs_qty = 2 (instead of 1 if credit was taken into account as -1 in the sum) self._get_posted_cogs_value() = 10 (instead of 0 if credit note cogs were taken into account in the sum as -10) return value = (100 * 2 -10)/1 = 190 https://github.com/odoo/odoo/blob/0824dc24665de6bfa805d540e756cdcb006edba6/addons/stock_account/models/account_move_line.py#L75 **fix:** if we take into account the credit note the return value will be : (100 * 1 - 0)/1 = 100 the mechanism of the already posted cogs value is there for cases where we only delivered a part of the quantity and then delivered the rest, but in the case where we delivered and then returned (with credit notes) it shouldn't have an impact. Thus the idea to include the credit note so that it can cancel out the first invoice opw-6426111 Forward-Port-Of: odoo/odoo#284151 Forward-Port-Of: odoo/odoo#282893
This fix prevents tables and other resizable content inside the HTML editor from being stretched or reset in ways that overflow their parent area. Users can now resize nested tables and columns more reliably, avoiding broken layouts and blocked resizing when editing rich content.
Original PR description
### Steps to reproduce **Issue 1:** * Create a table inside another table (e.g. `/table`). * Resize the last cell of the inner table beyond the outer `td` boundary. * Then try to resize the outer…
### Steps to reproduce **Issue 1:** * Create a table inside another table (e.g. `/table`). * Resize the last cell of the inner table beyond the outer `td` boundary. * Then try to resize the outer table `td` containing the inner table. * The outer table `td` can no longer be resized. **Issue 2:** - Create a table in the editor - Inside any cell, create a nested table - Resize the nested table by dragging its last column to the left to give it a fixed width - Resize the outer column that contains the nested table to the left - Observe that the nested table overflows its parent cell **Issue 3:** - Create a table in the editor - Inside any cell, create a nested table - Resize the nested table columns to give it a fixed width - Double-click the outer column border to reset its width - Observe that the nested table overflows its parent cell ### Purpose of this PR Prevent resizable elements from growing beyond the bounds of their nearest resizable ancestor. This fixes resize behavior for nested resizable structures such as: * tables inside tables * text columns inside tables * tables inside text columns The resize logic now clamps width expansion once the nearest resizable ancestor boundary is reached. - When resizing an outer table column that contains a fixed-width nested table, the outer column could be shrunk below the nested table's width, causing the nested table to overflow its parent cell. - The fix computes an effective minimum size per column by inspecting any fixed-width nested tables in that column's cells before the resize begins. This effective minimum size is passed through the resize_target_processors resource so ResizePlugin remains generic. - When resetting an outer column width, any fixed-width nested table inside the affected cells is also reset so it adapts naturally to the new outer column width instead of overflowing its parent cell. task-6215674 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fixes a Point of Sale issue where opening an original paid order could crash if a related refund order had been cancelled but not deleted. Staff can now view those orders normally from the ticket screen, reducing disruption during sales or follow-up service.
Original PR description
When a return order is created from the front end and later cancelled without being deleted from the backend, opening the original order from the POS ticket screen causes the UI to crash. The issue…
When a return order is created from the front end and later cancelled without being deleted from the backend, opening the original order from the POS ticket screen causes the UI to crash. The issue occurs because the refundedQty getter in `addons/point_of_sale/static/src/app/models/pos_order_line.js` assumes that every refunded order line has a valid order_id. For cancelled return orders, line.order_id is no longer available, resulting in the following runtime error: `TypeError: can't access property 'state', line.order_id is undefined` As a result, selecting the original paid order from the Ticket Screen breaks the POS interface. **Steps to Reproduce** 1. Create a normal sale in the POS. 2. Keep the POS session open. 3. In the backend, open the POS order and click Return Products. 4. Cancel the generated refund order (do not delete it). 5. In the POS, navigate to Ticket Screen → Orders → Paid Orders. 6. Select the original order. Desired behavior after PR is merged: The original order should open normally without any errors, even if a related refund order has been cancelled. Issue Ticket: https://github.com/odoo/odoo/issues/260079 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279942
This fix improves the accuracy and validity of Luxembourg FAIA/SAF-T reports by correcting tax amount signs, software version length, foreign currency tax reporting, and invoice customer/supplier details. It helps businesses produce reports that better match official validation rules and reduces the risk of audit or filing discrepancies.
Original PR description
This is one of many errors fixing the FAIA report, which is the SAF-T report for Luxembourg. See PR #113316 for a list of similar PRs. ### Error 1: Negative tax amounts Auditors from Luxemborg…
This is one of many errors fixing the FAIA report, which is the SAF-T report for Luxembourg. See PR #113316 for a list of similar PRs. ### Error 1: Negative tax amounts Auditors from Luxemborg provided one Odoo user with analysis files of their FAIA xml report. The following discrepancy was present in more than 300 lines: `[TaxInformation/TaxAmount/Amount] # is negative. Only postive values are admitted. The sign is automatically determined by the corresponding CreditAmount (-) Or DebitAmount (+) on the same Line.` This discrepancy was caused by two different scenarios. The first was a negative `unit_price` line, such as a Discount product. The second was a tax with negative and positive repartition lines, such as a tax with xml ID `lu_2015_tax_AP-EC-17`. Luxembourg officials confirmed the following behavior: 1. The TaxInformation/TaxAmount/Amount element must be positive. 2. The TaxInformationTotals/TaxAmount/Amount element may be negative. 3. There may only be one TaxInformationTotals element per TaxCode in an Invoice element. This commit ensures that these conditions are met for the FAIA report. I'm not sure if the TaxInformation changes should also be applied to the base `account_saft saft_report.xml` file. ### Error 2: SoftwareVersion The SoftwareVersion element is limited to 18 characters. The relevant error from a customer's analysis file is below. Error: Value exceeds maxLength of "18". ### Error 3: CurrencyAmount The `account_saft` method `GeneralLedgerCustomHandler._saft_fill_report_tax_details_values()` does not report the amount of tax in foreign currency, instead replacing this value with the amount in company currency. No errors prompted this change; it just seems wrong on its face. ### Error 4: PR #113720 ensured that the TaxType element is always TVA. This means that the TaxType should no longer should be ignored in our example documents. ### Error 5: Schema validation failure The elements Inovice/CustomerInfo and Invoice/SupplierInfo are defined with the element `<xs:choice>` in the XSD file linked below. Only one can be present at any time, not both. https://pfi.public.lu/dam-assets/backup/FAIA/FAIA/XSD_Files.zip. note: currently the link is broken. PR #100749 allowed many parts of SAF-T code to display both customer and supplier data, including these elements. This commit ensures that the elements are mutually exclusive. opw-6344914 [Link](https://www.odoo.com/odoo/project.task/6344914) Forward-Port-Of: odoo/enterprise#129043 Forward-Port-Of: odoo/enterprise#126121
UAE companies can now create and save salary bank accounts directly from Payroll Settings. This removes a payroll setup blocker and helps companies complete the UAE WPS payment configuration.
Original PR description
Issue: UAE companies cannot create their salaries bank account directly from Payroll Settings. The bank account cannot be saved, leaving payroll configuration incomplete and preventing the UAE WPS…
Issue: UAE companies cannot create their salaries bank account directly from Payroll Settings. The bank account cannot be saved, leaving payroll configuration incomplete and preventing the UAE WPS process from being completed. Steps to reproduce: * Configure an Emirati company with the UAE Payroll localization. * Open Payroll > Configuration > Settings. * Create a new Salaries Bank Account from the settings field. * Fill in the bank details and try to save the account. Cause: Since saas-19.2, the bank account form hides the required account holder and expects the opening field to provide it through `default_partner_id`. The UAE salaries bank account setting only restricts selectable accounts through its domain and does not provide that creation default. Newly created accounts therefore have no owner and cannot be saved. Domains only filter selectable records and do not initialize fields on new records. Since the shared bank account form hides the required partner, accounts created from Payroll Settings have no owner and cannot be saved. Solution: We need to provide the current company partner as the account creation default while retaining the existing selection domain. This preserves the company and country restrictions and guarantees that newly created salaries accounts satisfy the required ownership invariant. opw-6441848 Forward-Port-Of: odoo/enterprise#127777
Deleting an active project no longer incorrectly moves document folders from archived projects to the trash. This protects documents linked to archived projects and prevents unexpected loss or disruption for users managing project files.
Original PR description
Deleting a project also sends the folders of every archived project to the trash. ### Steps to reproduce - Install `documents_project`, where each project has its own Documents folder linked through…
Deleting a project also sends the folders of every archived project to the trash.
### Steps to reproduce
- Install `documents_project`, where each project has its own Documents folder linked through `project.project.documents_folder_id`.
- Create `Project 1`, `Project 2`, and `Project 3`, then archive the first two.
- Delete `Project 3`.
- The folders of `Project 1` and `Project 2` are moved to the trash with their contents, although both projects still exist and still reference them.
### Cause
`_archive_folder_on_projects_unlinked` only archives folders that are no longer used by any project. This was checked through a `documents.document` domain on `project_ids`.
The domain mixed two conditions on the same relation:
- `('project_ids', '!=', False)` checks that a folder has users,
- `('project_ids', 'not any', [('id', 'not in', self.ids)])` checks that it has no users outside the projects being deleted.
Those conditions are not evaluated the same way by the ORM. The first one keeps archived projects visible by disabling `active_test` internally, while the second one searches `project.project` normally and hides archived projects.
An archived project can therefore be counted as a folder user by one condition and ignored by the other, causing its folder to be archived.
### Fix
Check remaining users directly on `project.project` with `active_test=False`, so archived projects are included. Since only folders of deleted projects can become unused, the search starts from those folders instead of scanning all Documents.
opw-6442976
Forward-Port-Of: odoo/enterprise#127217The IoT display browser now starts only after the Odoo service has finished its main startup steps. This prevents error pages on launch and helps the display open correctly in fullscreen, improving reliability for IoT screen setups.
Original PR description
Before this commit, the display driver (and therefore browser) were being started too early, causing the following issues: - The browser initially displays an error page, as it tries to load the status page before Odoo has finished starting. - The browser window is not fullscreen. This may be because it is started before labwc is fully initialised, as the correct fullscreen command line arguments are used. The second issue can be fixed by restarting the Odoo service, the first happens every time. After this commit, we start the IoT interfaces in the main run method, instead of when the module is imported, meaning that everything else has time to finish initialising. This solves both problems. task-6469793 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284386
Sales users without accounting permissions can now open invoices that use cash rounding when those invoices come from sales orders. This prevents an unnecessary access error while preserving legitimate invoice visibility for users who are allowed to view the document.
Original PR description
**Behavior:** When a sales user without accounting access rights tries to view an invoice created from a sale order where a cash rounding is applied, an AccessError is raised. This occurs because…
**Behavior:** When a sales user without accounting access rights tries to view an invoice created from a sale order where a cash rounding is applied, an AccessError is raised. This occurs because loading the invoice view triggers `_compute_tax_totals()`, which then passes the invoice's `invoice_cash_rounding_id` to `_get_tax_totals_summary()`. Which then ends up failing when trying to access fields on the `cash_rounding` record due to missing accounting rights, even though the user is allowed to view the parent invoice. This is fixed by ensuring reading fields on `cash_rounding` during tax total computation bypasses the access check using `sudo()`, as the user already has legitimate access to the invoice itself. **Steps to reproduce:** - As an admin, enable cash rounding then create one. - Create an invoice and set the Cash Rounding Method - In debug mode, go to 'Set Default Values' in the debug dropdown and set Cash Rounding Method = your rounding for all users - Go to users, and ensures that Demo has no accounting rights but has sales user rights - As Demo, create a sale order, confirm it, then create the related invoice. - When trying to acces said invoice, you should get an Access Error opw-6379781 Forward-Port-Of: odoo/odoo#284212 Forward-Port-Of: odoo/odoo#278815
Corrected an internal setup issue that could stop Turkish Nilvera e-invoices from processing when a zero VAT warning was calculated. This helps prevent invoice errors for Turkish businesses using this localization.
Original PR description
The `l10n_tr_zero_vat_warning` field is a boolean field but was incorrectly defined as [binary], causing the compute method to fail when assigning a boolean value. ```py File…
The `l10n_tr_zero_vat_warning` field is a boolean field but was incorrectly defined as [binary], causing the compute method to fail when assigning a boolean value.
```py
File "/home/odoo/src/odoo/saas-19.3/addons/l10n_tr_nilvera_einvoice/models/account_move.py", line 156, in _compute_l10n_tr_l10n_tr_zero_vat_warning
invoice.l10n_tr_zero_vat_warning = exempt_zero_tax and invoice.l10n_tr_gib_invoice_type == 'SATIS' and exempt_zero_tax in invoice.line_ids.tax_ids
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-19.3/odoo/orm/fields.py", line 1892, in __set__
self.write(protected_records, value)
File "/home/odoo/src/odoo/saas-19.3/odoo/orm/fields_binary.py", line 151, in write
super().write(records, value)
File "/home/odoo/src/odoo/saas-19.3/odoo/orm/fields.py", line 1583, in write
cache_value = self.convert_to_cache(value, records)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-19.3/odoo/orm/fields_binary.py", line 83, in convert_to_cache
raise TypeError(f'{self}: use BinaryValue instead of {value.__class__.__name__}')
TypeError: account.move.l10n_tr_zero_vat_warning: use BinaryValue instead of bool
```
upg-4608394
[binary]: https://github.com/odoo/odoo/pull/242043/changes#diff-d3cbb345d0a5855b7d7aa91e64a0ff480e3e5acfa3b2c71503a23ca7f3c0c511R132
Forward-Port-Of: odoo/odoo#284165The pickup point search no longer pre-fills a visitor's ZIP code from an imprecise location estimate, helping avoid irrelevant pickup options. The search field is clearer with “Zip or City,” and the country selector is simplified when only one country is available.
Original PR description
GeoIP guesses a visitor's location is not precise resulting in showing pickup points that are not close to the customer. Drop the GeoIP zip prefill.
Also clarify the search placeholder ("Zip or City") and hide the country dropdown's caret when there's only one option to pick. Safely fallback on the first country in the selector.
Forward-Port-Of: odoo/odoo#284465
Forward-Port-Of: odoo/odoo#284392