Tuesday, September 8, 2026
27 changes · saas-19.1
Resolved issues and error corrections
This fixes a display issue where text inside notebook sections, such as Contact Notes, could stop wrapping and create unwanted scrollbars. Tab titles still stay on one line, while the main content area now wraps normally for easier reading and editing.
Original PR description
Since 7d26b3444f61, `white-space: nowrap` is set on `.o_notebook.horizontal` itself instead of on `.nav-item`, so it is inherited by the whole notebook content. On Contact -> Notes, this makes the paragraphs of the html field stop wrapping. The editable is wrapped in an `overflow-auto` div (Wysiwyg template) and gets a `min-height: 180px` inside a notebook, so the resulting horizontal scrollbar shrinks the available height and a vertical scrollbar shows up as well. Scope the rule back to the tab headers: horizontal tab titles still never wrap, and vertical ones keep the wrapping behaviour introduced by that commit. task-5933892 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#287132
This fix prevents a warning from appearing when running report tests with manufacturing accounting and demo data. It keeps the work-in-progress report behavior unchanged while improving reliability for automated checks.
Original PR description
Runbot was showing warnings when running the test_reports test with the mrp_account and project_timesheet_forecast modules installed with demo data.
```
Unknown directives or unused attributes: {'data-oe-demo'} from <t t-out="', '.join(docs.account_id.mapped('name'))" data-oe-demo="Acme Corp."/>
```
**Root cause:**
Since data-oe-demo is an html attribute usage of it within `<t>` tag raises a warning after this [commit](
https://github.com/odoo/odoo/commit/ae4824640665fc639e03a13c341f18e73060349e) in saas-19.1.
**Solution:**
Usage of span tag instead of <t> tag ensures the same behaviour without the warning.
[runbot-939604](https://runbot.odoo.com/odoo/error/939604)Cancelled retail orders and cancelled order lines in German point of sale are now saved and marked properly when sent to Fiskaly. This helps businesses keep fiscal certification records accurate and compliant when transactions are voided or reversed.
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
This fixes an error that could occur when a Time Off accrual plan was created without milestones and a first milestone was added later. Employees and HR users can now create time off requests normally in this scenario, avoiding a save-time crash.
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 fixes how Spanish TicketBAI data handles gift card refunds in point-of-sale orders. Gift card lines now use the overall order refund status, preventing mismatches between line totals and the final invoice total.
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
Customers can now complete checkout when their cart includes a free promotional reward item, even if the website blocks regular zero-priced products. This prevents valid orders from being sent back to the cart with an unnecessary warning.
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
Early payment discount entries now preserve the original invoice line’s analytic distribution for all discount calculation modes. This prevents reporting details from being lost when invoices use mixed or excluded discount settings, improving accuracy in analytic accounting reports.
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
Purchase events now use the same product identifier as the Google Merchant Center feed. This helps Google Ads and Analytics correctly connect Shopping ad clicks with resulting sales, improving reporting accuracy.
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
Fixed an issue in Mail where replacing an accidental mention with a longer correct name could still notify the originally selected person. This prevents hidden, unintended recipients from receiving chatter messages when the visible mention has been corrected.
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
When an email cannot be delivered, Odoo now displays the configured outgoing mail server name instead of showing 'None'. This makes delivery problems easier for users and support teams to identify and resolve.
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
Users can now download attachments opened in the file viewer from Discuss channels without seeing an error. The change uses the correct download method for these files, preventing failed downloads while preserving the intended 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#281330
Forward-Port-Of: odoo/odoo#279232This fix prevents duplicate registration attempts from leaving a company database with outdated credentials for Odoo's electronic invoicing proxy service. By checking for an existing local user before contacting the external service, it avoids failed future proxy calls and reduces the need for 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
Users sending Colombian support documents to DIAN now see a clear message if the required operation mode is missing. This replaces a confusing server error with guidance on what needs to be configured, helping businesses resolve the issue faster.
Original PR description
**Steps to reproduce:** * Install `l10n_co_dian`. * Configure a company with the Colombian localization. * Set up a DIAN operation mode for Electronic Invoices only (no Support Documents mode). *…
**Steps to reproduce:** * Install `l10n_co_dian`. * Configure a company with the Colombian localization. * Set up a DIAN operation mode for Electronic Invoices only (no Support Documents mode). * Create a vendor bill marked as a Support Document and click **Send to DIAN**. **Observed behavior:** * A cryptic server error is raised: *"TypeError: unsupported operand type(s) for +: 'int' and 'str'"* * No actionable information is shown to the user. **Cause:** * `_add_document_config_vals` assigns `vals['l10n_co_dian_operation_mode']` via `.filtered()`, which returns an empty recordset when no matching operation mode exists. * Accessing a `Char` field on an empty recordset returns `False` (a `bool`, which is a subclass of `int` in Python). * Concatenating `False` with strings in the `sha384` hash calculation raises the `TypeError`. * The missing-mode guard only existed in the commercial events path, not in the main invoice export path. **Fix:** * Raise a descriptive `UserError` that tells the user which mode is missing and where to configure it. opw-6499321
This fixes an issue where existing Accounting dashboards could use an outdated filter behavior after an upgrade, causing dashboard data to be interpreted incorrectly. Existing databases are adjusted so the dashboard keeps using the intended filter logic without requiring manual action.
Original PR description
Context: With https://github.com/odoo/odoo/commit/682d6ec1ddb825674999e0db1fc0cc48c3be67c5 we introduced `ODOO.FILTER.VALUE.V18` which behaves like `ODOO.FILTER.VALUE` was behaving before 19.0 All…
Context: With https://github.com/odoo/odoo/commit/682d6ec1ddb825674999e0db1fc0cc48c3be67c5 we introduced `ODOO.FILTER.VALUE.V18` which behaves like `ODOO.FILTER.VALUE` was behaving before 19.0 All spreadsheets upgrading from <19.0 to 19.0 have their ODOO.FILTER.VALUE functions converted to the V18 version. The goal is to preserve the backward compatibility for existing spreadsheets. The issue: The dashboard Accounting was updated[^1] to work with the new behavior but the file was not updated to the latest version. It means the data goes through upgrade[^2] and the function is converted, even though the rest of the dashboard is designed to work with the new version. The file was updated by commit https://github.com/odoo-dev/enterprise/commit/58811392bf7813cff39d874916b8dde117921d69, but it requires a module update for existing databases. This commit attempts to fix the issue for existing databases. [^1]: https://github.com/odoo-dev/enterprise/commit/c4e69e36dbd79386193ffbd16e54809dbba8dba8 [^2]: spreadsheet has a client-side upgrade mechanism when it's open. Forward-Port-Of: odoo/enterprise#106450
Peruvian electronic invoices now include cash rounding in the final amount due sent to SUNAT. This prevents mismatches between the rounding field and the payable total, helping businesses submit accurate compliant invoice XML files.
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
This update fixes a typo in the Italian electronic invoicing withholding tax reason. It helps ensure tax descriptions are accurate and professional in localized accounting documents.
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
Guest shoppers who enter a valid EU VAT number during checkout now have that number verified immediately. This ensures eligible cross-border EU orders receive the correct 0% intra-community VAT treatment instead of being charged domestic VAT by mistake.
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
This fix ensures restaurant tables in the German point-of-sale certification flow open only after the required fiscal update request is finished. It prevents delays or blocked navigation when staff select a table, making the checkout workflow more reliable.
Original PR description
In this commit: ------------------ - The API call is triggered when an order is updated in Fiskaly. - Previously, the request could be awaited while opening a table, blocking the table from opening before the product screen was ready. - Now, the request is awaited before allowing the table click, ensuring the table opens only after the request is completed. Task: 6522079 Forward-Port-Of: odoo/enterprise#130132
Users can now update analytic information while editing bank reconciliation lines without being stopped by lock date restrictions. This prevents an unnecessary error in a common accounting workflow while keeping the lock date control in place for other changes.
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
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 required eTransport information at the appropriate operational stage 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
This fix ensures Saudi Arabia tax settings are updated automatically during Odoo 19 upgrades. It prevents outdated VAT tags from remaining in use, reducing manual cleanup and helping invoices and tax reports stay accurate 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
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
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
## 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 pricel
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
CRM users will no longer see a warning about potentially expensive opportunity updates when creating a new stage. The warning now appears only when editing an existing stage, reducing confusion during setup while still alerting users when the message is relevant.
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
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 the tab detection language-independent, improving reliability for users working in different locales.
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
Inventory users without Accounting access can now view and create Indian E-Waybills without running into access errors. This removes a blocker in warehouse and delivery workflows where E-Waybill documents are required.
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
Dropshipped kit products now keep an accurate cost on sales orders when using FIFO or average costing. This prevents margins from incorrectly showing inflated profits because the kit cost was previously recorded as zero.
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