Tuesday, August 25, 2026
9 changes · saas-18.3
Resolved issues and error corrections
This fixes an issue where choosing a Gboard word suggestion on mobile could place the replacement word incorrectly and leave part of the old word behind. Users editing text on mobile should now get more reliable autocorrect and suggestion behavior in the HTML editor.
Original PR description
Before this commit: on mobile, when typing using Gboard and select a word suggestion will only delete the last character and put the new word at the beginning of the word to be replaced. This is because Gboard extends the selection to the text to be corrected, then deletes it, and inserts the corrected text. This flow falls in our previous fix for MS Swiftkey's delete backward, and wrongly uses cached old selection instead of using extended new selection from Gboard. After this commit: We strict the Swiftkey fix further, and only execute it when the cursor is at the beginning of the p element. Related commit: https://github.com/odoo/odoo/commit/822fd4e8fec7e114e6748dd8c9b4969f423fb290 task-6233756 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#278266
Cloud storage download links can now be created with a longer expiry time when a business flow needs to share them with an external service. Existing behavior remains unchanged by default, reducing failed downloads without affecting current users.
Original PR description
Some features hand a cloud storage download URL to an external service that may fetch it later. The default five-minute lifetime is too short for those flows. ### Steps to reproduce 1. Configure a cloud storage provider (e.g. cloud_storage_google). 2. Upload a large file from the web client, so it is stored in the cloud. 3. Generate a download URL for a consumer that may fetch it after five minutes. 4. The URL expires before the consumer fetches it. ### Cause The Google and Azure providers always use the default download URL lifetime, so callers cannot request a longer-lived URL. ### Fix Read an optional cloud_storage_download_url_time_to_expiry context value when generating a download URL. Keep the existing five-minute lifetime as the default for all current callers. opw-5424132 Related Enterprise PR: odoo/enterprise#105967 Forward-Port-Of: odoo/odoo#246443
This fix prevents images in email templates from being duplicated when emails are prepared for Outlook. It keeps saved mailing HTML cleaner and helps recipients see the intended layout without repeated images.
Original PR description
Problem: An `img-fluid` image ended up duplicated twice inside `[if mso]` comments instead of once when converting a mailing body to inline HTML. `classToStyle` resets the image's `width` attribute back to `100%` after the img-fluid fix already hid it and added its Outlook clone, making it match `enforceImagesResponsivity`'s selector again and get duplicated a second time. Solution: Mark images already handled by the img-fluid fix with a dedicated `mso-hidden` class and exclude them from `enforceImagesResponsivity`'s selector. Steps to reproduce: - Add an image in a new email template. - Set its width to "100%". - Save. - Observe the saved HTML has two mso comments for one image. opw-6411348 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283874 Forward-Port-Of: odoo/odoo#282199
Website pages are now correctly served to modern search and AI crawlers that send language preferences, avoiding repeated redirects that could prevent indexing or inspection. Human visitors keep the same language redirect behavior, while crawler traffic is excluded from click and visitor tracking as intended.
Original PR description
Modern crawlers now send an `Accept-Language` header (for example, `en-US,en;q=0.9`), whereas historically they did not. When that language differs from the website's default language,…
Modern crawlers now send an `Accept-Language` header (for example, `en-US,en;q=0.9`), whereas historically they did not. When that language differs from the website's default language, `ir.http._match()` issues a 303 redirect from `/page` to `/<lang>/page`. Since crawlers do not retain cookies, unrecognized agents are redirected on every request and never reach the default-language page. Customers reported that Google Search Console URL Inspection live tests only receive a redirect and that pages remain unindexed. Googlebot itself is not affected because it already matches the existing `bot` token. `_match()` already skips language redirects for recognized bots by serving the default-language page directly. Extend the `bots` user-agent list with modern crawler identifiers, each verified against vendor documentation: * `google-inspectiontool`: Search Console URL Inspection / Rich Results Test * `googleother`: Google generic crawler (`GoogleOther`, `GoogleOther-Image`, `GoogleOther-Video`) * `meta-external`: `meta-externalagent`, `meta-externalfetcher`, and `meta-externalads`, successors to the already-listed `facebookexternalhit` * `meta-webindexer`: Meta AI search indexer * `chatgpt-user`: OpenAI user-request fetcher (currently matched only through the `bot` substring in its info URL, which is fragile) * `claude-user`: Anthropic user-request fetcher * `perplexity-user`: Perplexity user-request fetcher The redirect behavior remains unchanged for human visitors. Localized pages continue to be crawlable through their own URLs (for example, `/fr/page`) via `hreflang` alternates. As a side effect, `link_tracker` and `mass_mailing_sms` no longer count clicks from these crawlers, and website visitor tracking skips them. task-6213245 Forward-Port-Of: odoo/odoo#275571
Inventory users can now adjust a product's on-hand quantity directly from the product form when they already have permission to make inventory adjustments. This removes an inconsistent restriction that forced users to go through the Inventory Adjustments menu for the same task.
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#282010Duplicating multiple projects now copies only the milestones that belong to each original project. This prevents copied projects from incorrectly receiving milestones from other projects selected at the same time.
Original PR description
Before this commit, duplicating several projects at once from the list view gave every copy the milestones of all the duplicated projects, because the copy loop assigned the milestones of the whole recordset instead of the ones of the project being copied. Duplicating a single project behaves correctly, which hid the issue. Steps to reproduce: - create two projects with milestones enabled, add a milestone to the first one and two others to the second one - select both projects in the list view and duplicate them Each copy contains the three milestones instead of only the milestones of its original project. Solution: Copy the milestones of the project being duplicated. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#278520
Corrects several SAF-T/FAIA report issues that could cause audit validation errors, including negative tax amounts, overly long software version values, incorrect foreign-currency tax amounts, and invalid customer/supplier invoice data. This improves compliance and reliability for Luxembourg electronic audit reporting, with related test updates for Luxembourg and Romania SAF-T outputs.
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#128455 Forward-Port-Of: odoo/enterprise#126121
Fixed an issue where WhatsApp recipients received empty files when attachments were stored in cloud storage. The system now sends cloud-stored files through a valid media link while keeping normal local attachments working as before.
Original PR description
WhatsApp attachments were delivered as empty (0 byte) files when they were stored through the cloud_storage module. ### Steps to reproduce 1. Install and set up whatsapp and a cloud storage module (e.g. cloud_storage_google). 2. Send a file through WhatsApp. 3. The recipient receives an empty file. ### Cause A cloud stored attachment keeps only a reference to its remote data, so its raw field holds no bytes. The integration uploaded those empty bytes to WhatsApp. ### Fix Use the attachment HTTP stream to generate a long-lived cloud storage URL and pass it to WhatsApp as the media link. Pass ordinary remote attachment URLs directly, and keep uploading local attachment bytes as before. opw-5424132 Related Community PR: odoo/odoo#246443 Forward-Port-Of: odoo/enterprise#105967
French VAT report XML submissions now split account holder names that exceed the official 35-character limit. This helps prevent rejected filings when a company or bank account holder name is too long.
Original PR description
The XSD for XML-EDI does not allow strings longer than 35 for TitulaireDesignation This commit splits the holder name in 2 parts when it is more than 35 characters task-6476440 Forward-Port-Of: odoo/enterprise#128239