Wednesday, September 16, 2026
12 changes · 18.0
Resolved issues and error corrections
Fixes a problem where updating a website theme could fail if the theme had removed a previously copied design view. The update now properly cleans up related website views, helping theme updates complete reliably.
Original PR description
When a record disappears from a theme's data files, updating that theme deletes its per-website copies. The context built for `copy_ids` in `_process_end_unlink_record` used the literal `'MODULE_UNINSTALL_FLAG'` instead of the constant, and passed it as a positional dict, which replaces the whole context rather than extending it. `ir.ui.view.unlink` therefore did not cascade to the inheriting views and the `inherit_id` foreign key refused the deletion, aborting the theme update. Reported by: https://github.com/odoo/odoo/issues/286171 Forward-Port-Of: odoo/odoo#288548
Customer credit checks now include confirmed sales orders even when products have not yet been delivered. This prevents customers from placing additional orders that exceed their credit limit simply because earlier delivery-based orders were not invoiced yet.
Original PR description
### Steps to reproduce Set a credit limit of 1.000 on a customer, then: 1. Create a Sale Order for a product with Invoice Policy on **Delivered Quantities**, over that amount → the warning shows.…
### Steps to reproduce
Set a credit limit of 1.000 on a customer, then:
1. Create a Sale Order for a product with Invoice Policy on **Delivered Quantities**, over that amount → the warning shows. Good.
2. Confirm it, and deliver nothing.
3. Create a second order for the same customer → the warning never shows, even though the customer is already over the limit.
### Solution
The ordered quantity is what the customer committed to, delivered or not. Using `qty_to_invoice` instead of `uom_qty_to_consider` or `qty_delivered`
Introducing invoice_status in sales domain for `_compute_credit_to_invoice`: `untaxed_amount_to_invoice` still follows the invoicing policy, while `amount_to_invoice` no longer does. On a confirmed order for a delivery-based product with nothing delivered:
```
line.untaxed_amount_to_invoice = 0 # still gated by qty_delivered
line.invoice_status = 'no'
order.amount_to_invoice = 2000 # fixed by this PR
```
The domain filters on the first one, so the order is dropped from the search before its amount is ever read and `credit_to_invoice` stays at 0.`'no'` is the stored marker for a confirmed line that is not invoiceable yet, which is exactly what the first clause misses.
ticket: [6480211](https://www.odoo.com/odoo/project/967/tasks/6480211)This fix prevents Kenyan eTIMS invoice numbering from moving backwards after certain failed invoice submissions. It reduces the risk of two invoices sharing the same eTIMS number, helping avoid filing confusion and incorrect receipt details.
Original PR description
Give an eTIMS invoice number back to the sequence only when the failing call is the one that took it, and only when it is still the last one handed out. When sending a customer invoice fails with anything other than a timeout, the number is given back so that it is not consumed for nothing. current issue: - send an invoice, let it time out, so it keeps number N - send other invoices, so the sequence moves past N - send the first one again and let it fail with a non-timeout error - the sequence drops by one and the next invoice sent reuses a number Both documents then sit under the same number. On its next attempt the one that was never accepted finds the other one's filing through selectInvoiceDetails and copies its receipt details. opw-6502563 Forward-Port-Of: odoo/enterprise#129994
Fixes an issue where dropshipped sales could double-count project-related analytic allocations in stock valuation entries. This helps keep profitability and inventory accounting reports accurate when products are shipped directly from vendors to customers.
Original PR description
Steps to reproduce: - Set the product category to Automated (perpetual) Inventory Valuation. - Set up an Analytic Distribution Model for a vendor. - Confirm a sale order linked to a project, with a…
Steps to reproduce: - Set the product category to Automated (perpetual) Inventory Valuation. - Set up an Analytic Distribution Model for a vendor. - Confirm a sale order linked to a project, with a line using the Dropshipping route for that vendor, then validate the dropship delivery. Cause of the issue: A dropship move is weird: the same stock move is linked to both a purchase line and a sale line at once (one move covers both sides). When we build the stock valuation entry for that move, two different modules each add their own analytic distribution on top: - purchase_stock adds the purchase line's distribution (vendor's ADM, already combined with the project account by project_purchase) - sale_stock also adds the sale line's distribution, which already has the project account in it too Both just do a plain dict union, no normalization. On a normal delivery or a normal receipt only one of these ever kicks in, but on a dropship move both fire on the same line, so the project account's share gets counted twice (100% + 100% = 200%). Solution: Don't fall back to the sale line's distribution when the move already has a purchase line attached (that's the dropship case) — the purchase line's distribution is already the full, correct one for that move. The test uses two Analytic Distribution Models (one on the vendor, one on the customer) instead of the project app, since stock_dropshipping doesn't depend on project. opw-6383779
Website sitemap generation now loads only the page data it needs instead of pulling large background content for every page. This reduces memory use and helps large websites generate sitemaps reliably without running out of memory.
Original PR description
- Before this commit: All fields of ir.ui.view were prefetched, including arch_db and arch_prev. This caused an out-of-memory issue when dealing with many pages. - After this commit: Only required fields are fetched, avoiding unnecessary memory consumption from view architecture data. opw-6470593 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284945
EU OSS sales are now reported correctly in French PDP Flow 10 by keeping the destination VAT on invoices while reporting the taxable base as non-French VAT. This prevents valid cross-border EU sales from being rejected due to French VAT rate checks, while still blocking unsupported or invalid tax rates.
Original PR description
OSS sales are taxed in the customer's Member State but are not subject to French VAT. They must therefore be reported under TNT1, while the Flow 10 Schematron only accepts French VAT rates. Identify taxes generated for the EU OSS scheme through their OSS tag. Keep the destination VAT on the invoice and in accounting, but report the taxable base under TNT1 with a zero tax rate and amount. Continue rejecting unsupported rates for regular taxes and invalid OSS rates. no task id
This fix prevents appointment bookings from calculating negative remaining capacity when resources are shared. It helps avoid failed website bookings after conflicting backend Gantt bookings, making appointment availability more reliable for customers and staff.
Original PR description
**Steps to reproduce:** - Install Appointment app - Create an appointment type based on resources, auto-assigned, with two shareable resources linked together - Set the first resource's capacity to 3…
**Steps to reproduce:**
- Install Appointment app
- Create an appointment type based on resources, auto-assigned, with two shareable resources linked together
- Set the first resource's capacity to 3 and the second resource's capacity to 4
- From the backend Gantt view, create a booking for 2 people on the first resource
- Create a second booking for 2 people on the same resource, at the same date and time
- First resource is now overbooked with reserved capacity of 4 out of 3
- Try to create an appointment from the website
- Error: "The capacity reserved should be positive."
**Issue:**
When bookings are created from the backend gantt view, the selected resource can be overbooked even if another linked resource still has available capacity.
Then when trying to book an appointment the new booking lines will trigger this constraint:
```py
_check_capacity_reserved = models.Constraint(
'CHECK(capacity_reserved >= 0)',
"The capacity reserved should be positive.",
)
```
This is caused by the negative values in:
```py
booking_line_values = []
if appointment_type.schedule_based_on == 'resources':
capacity_to_assign = asked_capacity
for resource in resources:
resource_remaining_capacity = resources_remaining_capacity.get(resource)
new_capacity_reserved = min(resource_remaining_capacity, capacity_to_assign, resource.capacity)
capacity_to_assign -= new_capacity_reserved
booking_line_values.append({
'appointment_resource_id': resource.id,
'capacity_reserved': new_capacity_reserved,
'capacity_used': new_capacity_reserved if resource.shareable and appointment_type.resource_manage_capacity else resource.capacity,
})
```
**Fix:**
Avoid negative remaining value in resource booking when computing available slots.
Note: Tried to take the capacity already used by overlapping bookings into account when assigning resource booking lines from the backend gantt view. And also force linked_resources booking when trying to book more
than the total capacity to properly dispatch as many slots as possible. But it was breaking `appointment_google_reserve` tests.
opw-6503147This fix prevents certain spreadsheets with repaired history records from showing or restoring corrupted past versions. It ensures version history is rebuilt from the correct saved snapshot, reducing the risk of users accidentally breaking spreadsheet files when restoring older versions.
Original PR description
…heet history Some spreadsheet can end up in a corrupted history state Original Data ⮕ no revision (XX) ⮕ Snapshot ⮕ Active Revisions and where the original data differ from the snapshot. XX: can…
…heet history Some spreadsheet can end up in a corrupted history state Original Data ⮕ no revision (XX) ⮕ Snapshot ⮕ Active Revisions and where the original data differ from the snapshot. XX: can occur after the script added https://github.com/odoo/upgrade/issues/6340. The migration script is supposed to rewrite the history of the spreadsheet when there are holes in the archived revisions continuity (eg. the history was original Data ⮕ rev **A** ⮕ rev **B** ⮕ rev **C** ⮕ snapshot ⮕ rev **D** and user deleted rev **B** for instance, the script deletes **A** and **C** and marks **D** as the very first revision). Version history was designed with the idea to replay every single revision that existed since the creation of the spreadsheet and apply it to the original data, and in case of missing revisions, in our example, B is missing, we detect the lack of continuity, we start from the snapshot, and replay every single revision since that snapshot. When the migration fixes the continuity, by deleting all the old revisions, and changing their order, we can no longer detect the lack of continuity and end up replay every available revision to the original data ,even though they are based on the snapshot. In that scenario, since the revisions will be applied on the original data but since they are based on the state of the snapshot only, the final state of the spreadsheet will be corrupted since a part of the . If a user then decides to restore the spreadsheet to the version they see in the version history (which is corrupted as mentioned) and the last snapshot is overwritten with the corrupted state, effectively breaking the spreadsheet. With this revision, we add a detection of this corrupted history state, in which case we enforce the history to be replayed from the snapshot and not the original data. Task-6533708 Forward-Port-Of: odoo/enterprise#130649
Partners created through integrations or automated processes will now have their VAT numbers checked correctly when VAT verification is enabled. Bulk imports continue to avoid immediate checks to preserve performance, while duplicate checks in the same operation are avoided.
Original PR description
**Description of the issue/feature this PR addresses:** `res.partner.create()` in `base_vat` unconditionally cancels the pending recompute of `vies_valid`, so any partner created via ORM/API with…
**Description of the issue/feature this PR addresses:**
`res.partner.create()` in `base_vat` unconditionally cancels the pending recompute of `vies_valid`, so any partner created via ORM/API with `vat` + `country_id` set together in the same call (without going through the web form's `onchange`) never gets its VAT validated against VIES, even when "Verify VAT Numbers" is enabled on the company.
**Current behavior before PR:**
`create()` always cancels the `vies_valid` recompute:
```python
res = super().create(vals_list)
res.env.remove_to_compute(self._fields['vies_valid'], res)
return res
```
`write()`, in the same file, only cancels it when `context.get('import_file')` (the standard CSV/Excel import wizard). This asymmetry is a regression: until [5b6178f](https://github.com/odoo/odoo/commit/5b6178f727d697d2308ae1327ba436c7f9abeb3a) ("[PERF] avoid checking vat validity with vies on import"), `create()` had the same `import_file` guard `write()` still has today. [7ff5f76](https://github.com/odoo/odoo/commit/7ff5f76848582576e81b2307dba0f4e80467e115) ("[FIX] base_vat: prevent double VIES vat call") removed it from `create()` to avoid a double IAP call in the "create company from contact" flow, but as a side effect broke `vies_valid` for every other `create()` that doesn't come from the web form (the only path that already carries `vies_valid` explicit in `vals`, via its `onchange`, which is why the form was never affected).
Example:
```python
partner = env['res.partner'].create({
'name': 'Acme', 'country_id': be_id, 'vat': 'BE0477472701',
})
partner.vies_valid # False, even though the VAT is valid
```
**Desired behavior after PR is merged:**
`create()` computes `vies_valid` the same way `write()` already does, except when `context.get('import_file')` is set — bulk imports keep skipping the synchronous VIES check, exactly like today.
Simply restoring that guard would reopen the double IAP call [7ff5f76](https://github.com/odoo/odoo/commit/7ff5f76848582576e81b2307dba0f4e80467e115) fixed: `create_company()` copies a contact's already-validated VAT to the new parent company it creates in the same transaction, so `create()` would query VIES again right after. To avoid that without disabling `create()`'s recompute again, `_compute_vies_valid` now caches each VAT's checked status for the duration of the transaction (`env.cr.cache`, cleared with the cursor) and reuses it for any other partner that needs the same VAT checked again before that transaction ends — regardless of whether they are related as parent/child or not. A test reproduces the `create_company()` scenario and asserts IAP is only queried once.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
@Tecnativa @carlosdauden @carlos-lopez-tecnativaThe mail plugin now lets administrators adjust how long Outlook authentication tokens remain valid, with a default of seven days. This reduces the need for users to log in every day while keeping the tokens limited to Outlook-specific access.
Original PR description
Purpose ======= Allow customizing the expiration time of tokens, so users don't need to login everyday in the plugin. This is customized with a system parameter, with a default of 7 days. Those tokens are limited to endpoints `auth="outlook"`. Task-6466253
Swiss payroll users can now manually enter an hourly wage rate on draft ELM payslips without it being reset when the payslip is saved. This prevents payroll staff from losing corrections and helps ensure hourly-paid employees are paid using the intended rate.
Original PR description
**Steps to reproduce:** 1. Create a draft Swiss ELM payslip for an hourly-paid employee with no automatic hourly work-entry/input 2. In the Wages tab, manually update the `Factor` (`rate`) field of the `Hourly Salary` line 3. Save the payslip **Issue:** The manually entered `Factor` is reset to `0.00` **Cause:** - `l10n_ch_swiss_wage_ids` is a stored computed field without explicit `readonly=False`, causing manual modifications to the line values to be discarded during field recomputation on save. opw-6536243 Forward-Port-Of: odoo/enterprise#130824
Portal users who follow a project can now reliably open tasks they are allowed to view, instead of sometimes seeing a "not found" error. This improves the customer portal experience by aligning task access from project links with the existing My Tasks page.
Original PR description
Before this commit, the portal user could have a request not found when he wants to access to a task from a project he follows even if he can access to the task in /my/tasks route. This commit makes sure the read access are checked instead of checking if the user can access to the task thanks to the token since the token if the one of the project.