Thursday, September 3, 2026
20 changes · 19.0
New functionality added to Odoo
This adds ready-to-use deployment configuration for running Odoo with Docker. It can help teams set up consistent environments faster and reduce manual setup effort for deployments.
Original PR description
…ment 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
Enhancements to existing features
The website shop now prepares Google Merchant Center product feed items more efficiently by loading pricing rules for a full product batch at once. This reduces repeated pricing lookups and can make feed generation faster, especially for larger catalogs.
Original PR description
Description of the issue/feature this PR addresses: The commit speeds up `_prepare_gmc_items` by prefetching all pricelist rules for the entire product batch once into a context-based cache (`website_sale_pricelist_rules_cache`) that `_get_applicable_rules` reuses, replacing a per-product `product.pricelist.item` search with a single batched fetch. Current behavior before PR: <img width="573" height="23" alt="image" src="https://github.com/user-attachments/assets/9eb3feda-9023-4247-b6c5-6c767f09fc5c" /> Current behavior after PR: <img width="576" height="22" alt="image" src="https://github.com/user-attachments/assets/00591525-62f9-41da-996b-e7d06395b421" />
Resolved issues and error corrections
Fixed an issue where selling and invoicing a shared product in Point of Sale could fail when another company’s vendor data was cached. The change ensures only vendor information relevant to the active company is considered, reducing interruptions for multi-company users.
Original PR description
**Steps to reproduce:** - Install PoS and Purchase - Make 2 companies, A and B - On product "Office Lamp" that is accessed by both companies, put Partner A in the vendor tab for company B - Company A…
French localization now treats Monaco as part of the European Union VAT group and adds separate handling for EU VAT rules without Monaco. New localization packages were also added for French overseas territories, improving accounting setup for businesses operating in those regions.
Original PR description
[[IMP] l10n_fr,l10n_fr_account: European union vat](https://github.com/odoo/odoo/commit/9870a204bb64bdf8a11e433eef3a9e9f8f3fad86)
This commit will add Monaco to the European union vat country group and
create a new country group that is the same than the european union but
without monaco
task-6321652
[[ADD] l10n_{gf,gp,mq,re,yt}: new localization](https://github.com/odoo/odoo/commit/58518e1d7df773a70c595bd3842a33ac6be611bb)
Like Monaco, we need to add new localization package for
the drom countries
task-6321652
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr**Steps to reproduce:**
- Install PoS and Purchase
- Make 2 companies, A and B
- On product "Office Lamp" that is accessed by both companies, put Partner A in the vendor tab for company B
- Company A should have a Partner A and Partner B in this tab
- In the stock, set a replenishment for company A, with Partner A on Office Lamp
- Go to company B and open the PoS
- Try to buy Office Lamp while requesting an invoice
- An access error appears
**Why the fix:**
When requesting an invoice in the PoS, we try to create the stock picking. Doing so will trigger the replenishment rules linked to the product to be recomputed.
Those are executed when we **flush_all()**, processing Company A's replenishments as sudo(), meaning all of Company A's **seller_id** are fetched and cached. This means the product's **seller_ids** now contains Company A's **seller_id**, even though we are currently in Company B.
While trying to get the product's code, we iterate over **product.seller_ids**, but we do not have access to every record in that product.
https://github.com/odoo/odoo/blob/b8e5291d103d9f43bd8db6d2dfe708076a57ea37/addons/product/models/product_product.py#L337-L343
As we don't have access to those, we get an access error when we stumble upon it.
To avoid those errors, we now filter the sellers to only have the ones compatible with our current Company in the given product we are currently buying.
Another solution would be to do **product.invalidate_recordset(['seller_ids'])** before looping over it, but feels more like a band-aid than the current fix IMO.
We could also write **self.lines.product_id.mapped('code')** in **_create_order_picking(self)** to have the solution be in PoS directly, but the error might arise from somewhere else at some point, and this just hides the issue by adding the code to the cache so that we don't have to fetch it again later.
opw-6308182This fix prevents Saudi e-invoices from being submitted more than once to ZATCA when a user only has read-only access to journals. It ensures the successful submission record is saved correctly, avoiding duplicate reporting and related compliance issues.
Original PR description
**Steps to reproduce:** This issue is hard to reproduce because it requires a live ZATCA connection: - As a user with read-only permission on journals, send an invoice to ZATCA. - You get an access error on the journal, and the invoice is unchanged (You can try sending it again to ZATCA). **Issue:** What happens is: - A user with read-only permission on journals sends an invoice to ZATCA. - If ZATCA responds with a 200 (successfully submitted), we try to write on the field `journal.l10n_sa_latest_submission_hash` - With no write permissions, the write fails and all changes are rolled back (on odoo, not on ZATCA) - We can send the invoice again to ZATCA, resulting in duplicates. **Solution:** - Added a sudo when writing on the field: `journal.l10n_sa_latest_submission_hash` opw-6320179 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281468 Forward-Port-Of: odoo/odoo#278728
The French reports module now checks for duplicate VAT declaration XML files before sending them to Aspone. This helps avoid rejected submissions and prevents unnecessary paid contacts with the service provider.
Original PR description
When sending an xml to aspone, they will check if a duplicate declaration exist, and if it's the case, then they will refuse it. Since contacting aspone cost us money we will block the sending before that. task-6420220
This pull request improves several Odoo Enterprise apps by fixing visibility, reporting, scheduling, testing, and timesheet issues, while also updating internal web components for the newer interface framework. Business users should see clearer email buttons, more useful overtime reporting, more reliable tax return checks, and fewer inconsistencies in planning and timesheets.
Original PR description
See commit messages for details. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fixes an issue where company-specific invoice rules could incorrectly make analytic fields mandatory on unrelated screens like Work Centers or Employees. The default analytic plan setting is now respected when a screen does not provide a business context, preventing unnecessary or confusing mandatory requirements for users.
Original PR description
### Issue: When an analytic plan has applicability lines with a company filter, the applicability is incorrectly applied on views that do not define a `business_domain`, such as Work Centers or…
### Issue: When an analytic plan has applicability lines with a company filter, the applicability is incorrectly applied on views that do not define a `business_domain`, such as Work Centers or Employee views For example, if a plan has: - Default Applicability: Unavailable - A line with Domain: Invoice, Company: My Company, Applicability: Mandatory Opening the analytic distribution on a Work Center shows `Mandatory` instead of the default `Unavailable` ### Cause: In commit https://github.com/odoo/odoo/commit/ffcf2ee1a3185ef73db93bfd95625844506692c5 `_get_score` was updated to return `0.5` when the applicability line's company matches the caller's company, even when no `business_domain` is provided In `_get_applicability`, the loop selects the first rule whose score exceeds the current minimum, which starts at `0`: https://github.com/odoo/odoo/blob/710e056e5171af2ab72d7d7793da3518f12faf5e/addons/analytic/models/analytic_plan.py#L255-L264 A score of `0.5` is enough to win over the default applicability, so a company-only match on a domain-specific rule incorrectly overrides the default when no `business_domain` is passed ### Steps to reproduce: - Install `mrp` and `accountant` with demo data - Enable Analytic Accounting in Settings - Open the Internal analytic plan and edit its applicability line: -- Remove the account prefix -- Default Applicability: Unavailable -- Domain: Invoice, Company: My Company (SF), Applicability: Mandatory - Go to Manufacturing > Configuration > Work Centers - Open any work center and click on Analytic Distribution Before the fix, Internal is shown as Mandatory instead of Unavailable Removing the company from the applicability line confirms the issue opw-6404884 Forward-Port-Of: odoo/odoo#280312
Journal entries that are reset to draft and later leave a numbering gap are now identified correctly. This helps accounting teams maintain accurate sequence controls without changing already assigned document numbers.
Original PR description
To reproduce: * create and post 2 journal entries in the same journal * reset to draft the entry with the highest number * create and post a third entry in the same journal The draft entry is not flagged as having made a gap, because at the time of resetting it to draft, it was the last entry and therefore didn't really make a gap. 3 options to solve were considered: * flag all moves when reseting to draft even if they were the last of the chain * if we reset the last move of the sequence to draft, also remove its sequence and put it back to `/` * when posting, check if the previous number was draft. If it was the case, flag it. The last options was taken in this fix to avoid changing the previous behavior while fixing the current issue.
FedEx shipments can now be validated when the main customer contact has no phone number but the delivery address does. This prevents delivery blocking errors by using the available phone number from the related parent or child contact.
Original PR description
Issue ----- Users cannot deliver to a contact's delivery address if the contact address itself doesn't have a phone number. Steps to reproduce ----- - Setup Fedex - Create a contact with no phone number - Create a delivery address for the contact (with phone number) - Create a SO with the contact using Fedex & confirm - Change the partner on the picking to use the delivery address - Validate the picking > Error: missing phone number Cause ----- To populate the `soldTo` part of thepayload, we call `_get_contact_from_partner` with the contact specified on the SO https://github.com/odoo/enterprise/blob/8e60f910a52e3b836e997e54a0b704e5786a28f2/delivery_fedex_rest/models/delivery_fedex.py#L171 https://github.com/odoo/enterprise/blob/8e60f910a52e3b836e997e54a0b704e5786a28f2/delivery_fedex_rest/models/fedex_request.py#L447-L452 The phone number is then taken directly from the contact. ----- Ticket: opw-6427962
Fixed an issue where newly created contextual actions could remain missing from a model's Actions menu, even after refreshing the page. The system now refreshes the relevant view information when action bindings change, so users see the actions they create without stale cached data getting in the way.
Original PR description
### Steps to reproduce 1. **Settings ‣ Technical ‣ Server Actions**, create a server action on any model, type *Execute Code*. 2. Click **Create Contextual Action**. 3. Open the list view of that…
### Steps to reproduce
1. **Settings ‣ Technical ‣ Server Actions**, create a server action on any model,
type *Execute Code*.
2. Click **Create Contextual Action**.
3. Open the list view of that model, select a record, open the **Actions** menu.
Expected: the new action is listed. Actual: it is not — and it is still missing
after reloading the page.
[](https://www.youtube.com/watch?v=ZfZhjLNkI8E)
### Cause
The view `toolbar` is built server side from the action bindings and travels
inside the `get_views` response. The view service serves that response from a
**disk** cache:
[`view_service.js#L98-L102`](https://github.com/odoo/odoo/blob/c3376d7854da41bcf4adae712dbc0c4bb36d8a8d/addons/web/static/src/views/view_service.js#L98-L102)
```js
const result = await orm.cache({ type: "disk" }).call(resModel, "get_views", [], {...});
```
and invalidates it for two models only:
[`view_service.js#L47-L54`](https://github.com/odoo/odoo/blob/c3376d7854da41bcf4adae712dbc0c4bb36d8a8d/addons/web/static/src/views/view_service.js#L47-L54)
```js
if (["ir.ui.view", "ir.filters"].includes(model)) {
if (UPDATE_METHODS.includes(method)) rpcBus.trigger("CLEAR-CACHES", "get_views");
}
```
`ir.actions.server` is not in that list, and `create_action` / `unlink_action`
(the *Create / Remove Contextual Action* buttons) go through `call_button`, so
they are not in [`UPDATE_METHODS`](https://github.com/odoo/odoo/blob/c3376d7854da41bcf4adae712dbc0c4bb36d8a8d/addons/web/static/src/core/orm_service.js#L88-L96)
either. Binding an action to a model therefore never invalidates `get_views`.
### Why it only shows up in 19.0
In 18.0 the same cache was a plain in-memory object rebuilt on every page load
([`view_service.js#L47`](https://github.com/odoo/odoo/blob/9486aa7413896624e01f35ad831783916c40d068/addons/web/static/src/views/view_service.js#L47),
[`#L107-L108`](https://github.com/odoo/odoo/blob/9486aa7413896624e01f35ad831783916c40d068/addons/web/static/src/views/view_service.js#L107-L108)), so a refresh hid the missing
invalidation. The cache is now persisted, so the stale entry survives the reload
and the action stays invisible for good.
### The server side is correct
Creating the action through the ORM and calling `create_action()` the way the
button does, `get_bindings` and `get_views(toolbar=True)` both return the new
action immediately. The ORM cache invalidation works and nothing filters the
binding out, so the stale data is on the client.
### Fix
Invalidate the `get_views` cache for the action models as well, including the
`create_action` and `unlink_action` buttons.
Tested using the 19.0 sha c3376d7854da41bcf4adae712dbc0c4bb36d8a8dStripe SEPA payments will again show the company name as the bank statement descriptor instead of relying on the order reference. This prevents payment failures when an order reference contains only numbers and avoids checkout disruption for affected customers.
Original PR description
**Steps to reproduce:** 1. Install Sales, l10n_be, payment_stripe and switch to belgian company 2. Switch the admin user's company to the belgian one, and update their contact address to be in Belgium 3. Enable stripe payment provider 4. Add SEPA payment method in stripe configuration 5. Create a sale order with a name that doesn't include any characters (numbers only), confirm, click preview and attempt to make a payment using SEPA **Issue:** `The statement descriptor must contain at least one Latin character.` **Cause:** The previous fix (4fde0232b821c9a1d46d589ff14495f4e19029f4) passed the order reference directly, assuming it will contain characters. The intended behavior is to actually have the company name used in the statement descriptor field: https://support.stripe.com/questions/what-is-a-statement-descriptor-and-how-do-i-update-it This was the existing behavior before the fix, so will revert back to it. opw-6497127
Users can now correct line descriptions on confirmed sales orders while the order remains open, even after delivery or invoicing. This removes confusing behavior where editability depended on the column layout, while still preventing product changes on processed lines.
Original PR description
Descriptions on order lines become impossible to change after the line is delivered or invoiced, even though the order is still open. Interestingly, hiding the product column makes the description editable again. This shows that editing descriptions is already technically allowed, but currently depends on the column layout, which is confusing for users. Keep descriptions editable until the order is locked or cancelled. This lets users correct text without allowing changes to products on processed lines. Desired behavior after PR is merged: After a sale order is confirmed, users can modify descriptions while the order remains unlocked, regardless of the column layout. Products on processed lines remain protected. @moduon MT-15454 opw-6432243 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This change corrects how Adyen payment information is read from incoming payment messages. It helps ensure payments are processed with the right details, reducing the risk of failed or incorrect payment handling for customers and businesses.
Original PR description
opw-6512723 Forward-Port-Of: odoo/odoo#284773
This fix stops the Bulgarian SAF-T setup process from recreating standard accounts or taxes that a customer has intentionally deleted. It helps avoid incorrect accounting records and prevents related setup or upgrade errors.
Original PR description
If a client deletes a standard account or tax, its XMLID can no longer be resolved during the post-init hook. In that case, _load_data() may create a new record using only the provided data, e.g.:…
If a client deletes a standard account or tax, its XMLID can no longer be resolved during the post-init hook. In that case, _load_data() may create a new record using only the provided data, e.g.:
```
('l10n_bg_691001', {'l10n_bg_saft_account_code': '624'})
```
This can lead to incorrect records.
Only update records whose XMLIDs still exist, avoiding the creation of new records when the corresponding standard record has been deleted.
```
File "/home/odoo/src/enterprise/19.0/l10n_bg_saft/__init__.py", line 10, in _add_account_saft_code
Template._load_data({'account.account': Template._get_bg_saft_account_code()})
File "/tmp/tmpbu1ntr1j/migrations/account/0.0.0/pre-ensure-deferred-accounts.py", line 37, in _load_data
return super()._load_data(data, *args, **kwargs)
File "/home/odoo/src/odoo/19.0/addons/account/models/chart_template.py", line 697, in _load_data
created_records[model] = self.with_context(lang='en_US').env[model]._load_records(all_records_vals)
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 5196, in _load_records
records = self._load_records_create([data['values'] for data in to_create])
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 5103, in _load_records_create
records = self.create(vals_list)
File "/home/odoo/src/odoo/19.0/odoo/orm/decorators.py", line 369, in create
return method(self, vals_list)
File "/home/odoo/src/odoo/19.0/addons/account/models/account_account.py", line 1055, in create
)).create(vals_list_for_company)
File "/home/odoo/src/odoo/19.0/odoo/orm/decorators.py", line 369, in create
return method(self, vals_list)
File "/home/odoo/src/odoo/19.0/addons/mail/models/mail_thread.py", line 329, in create
threads = super(MailThread, self).create(vals_list)
File "/home/odoo/src/odoo/19.0/odoo/orm/decorators.py", line 369, in create
return method(self, vals_list)
File "/tmp/tmpbu1ntr1j/migrations/util/orm.py", line 267, in wrapper
return f(*args, **kwargs)
File "/tmp/tmpbu1ntr1j/migrations/base/0.0.0/pre-models-match_uniq.py", line 25, in create
return super().create(vals_list)
File "/home/odoo/src/odoo/19.0/odoo/orm/decorators.py", line 369, in create
return method(self, vals_list)
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 4711, in create
records = self._create(data_list)
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 4887, in _create
cr.execute(SQL(
File "/home/odoo/src/odoo/19.0/odoo/sql_db.py", line 440, in execute
self._obj.execute(query, params)
psycopg2.errors.NotNullViolation: null value in column "account_type" of relation "account_account" violates not-null constraint
DETAIL: Failing row contains (734, null, 1, 1, null, null, null, t, f, f, 2026-08-25 08:18:15.751937, 2026-08-25 08:18:15.751937, no, f, null, null, null, null, 411).
```
upg-4611390
tbg-2902This fix ensures UPS shipment requests can still use a valid customer name when the invoicing address is missing one. It prevents deliveries from being rejected by UPS in cases where invoice addresses were created without a name.
Original PR description
Issue ----- By default, invoicing addresses of existing partners are created without a name. This leads to the deliveries being rejected by UPS. Steps to reproduce ----- - Set Up UPS - Create a Customer - Create an invoicing address with no name - Create a SO for the partner & confirm - UPS delivery - Open the picking and confirm it > UPS rejects the shipment /!\ I could not reproduce in testing environment, so this is based off user steps in their production DB. /!\ Cause ----- The partner being used in `_set_invoice` was changed in #119747 but this use case was missed due to the error not occuring in test mode. ----- Ticket: opw-6485164 Forward-Port-Of: odoo/enterprise#128933
Fixes an issue where creating an additional website could fail when themes and eCommerce features were involved. The system now keeps track of the correct website during theme installation, preventing crashes and allowing website setup to complete reliably.
Original PR description
During theme installation (`button_choose_theme`), Odoo triggers a registry rebuild. This rebuild spawns a fresh environment that strips out the HTTP request and context. As a result,…
During theme installation (`button_choose_theme`), Odoo triggers a registry rebuild. This rebuild spawns a fresh environment that strips out the HTTP request and context. As a result, `get_current_website()` loses track of the active website and incorrectly falls back to Website 1 when generating snippet templates via `_generate_primary_snippet_templates`. This causes a crash when creating a second website if the first website has no theme and the `website_sale` module is installed. Because the system evaluates Website 1, its `theme_id` is empty, and theme-provided configurator snippets are silently skipped. This results in a traceback (`Template not found: 'website_sale.configurator_hompage_s_dynamic_snippet_category_list'`) and prevents the new website from being created. This commit fixes the issue by temporarily injecting the active website ID into the current thread before triggering the theme installation. `get_current_website` is updated to check for `configurator_website_id` on the thread, ensuring the correct website is referenced even across an environment reset. opw-6483323
This fix stops users from creating incomplete order or invoice email templates directly from settings. It ensures new templates are created with the right setup, preventing payment post-processing errors after online orders.
Original PR description
Currently, an error occurs when the scheduled action `Payment: Post-process transactions` attempts to post-process a payment after performing the following steps: - Install `website_sale` with demo…
Currently, an error occurs when the scheduled action `Payment: Post-process transactions` attempts to post-process a payment after performing the following steps: - Install `website_sale` with demo data - Enable payment provider `Demo` for testing - Go to Settings and search for `Order Confirmation` - Under Email, enter a template name that does not exist (e.g., `Test`) - Click `Create "Test"` and save the settings - Go to the website shop, place an order, and complete the payment `KeyError: False` The issue occurs because clicks`Create "Test"` creates a `mail.template` record without setting the required `model_id` field. As a result, `render_model` remains empty, causing an error when `self.env[self.render_model]` is accessed (see code [1] and [2]). This commit fixes the issue by disabling quick creation for the `Order Confirmation` email template using the `no_quick_create` option. Also provides `default_model` in the context to ensure that `sale.order` is pre selected when creating a new email template through the form view. By forcing template creation through the form view, all required fields are properly initialized, preventing the creation of incomplete or incorrectly configured email templates. The same change has also been applied to `invoice_mail_template_id` to ensure invoice email templates are created with the correct default model and cannot be quick-created. [1]: https://github.com/odoo/odoo/blob/bb94f13b38f0bf1ce8886f4630ad42caf6c56325/addons/mail/models/mail_render_mixin.py#L739 [2]: https://github.com/odoo/odoo/blob/bb94f13b38f0bf1ce8886f4630ad42caf6c56325/addons/mail/models/mail_template.py#L125-L128 Sentry-7634490774
This fix prevents the Polish bank verification module from recalculating all existing payments during installation. It helps avoid installation crashes for companies with large payment histories, making deployment safer and more reliable.
Original PR description
account.payment model computes every record l10n_pl_verification_id at module installation (l10n_pl_bank_verification), causing crash in case of db with a large number of records wrong method name correction: _auto_init instead of init and call super after creating the db column see odoo/odoo#282504 Forward-Port-Of: odoo/odoo#285968
Manual quantity-based quality checks can now record a partial failed quantity without blocking the warehouse transfer. This prevents validation errors when checks are created directly from a picking and helps teams keep inventory operations moving.
Original PR description
Version: -------- - 19.0+ Steps to reproduce: ------------------- - Install `quality_control` module - Create a picking order with a product - From the gear menu, create an on-demand Quality Check -…
Version:
--------
- 19.0+
Steps to reproduce:
-------------------
- Install `quality_control` module
- Create a picking order with a product
- From the gear menu, create an on-demand Quality Check
- Set the check to *Control per Quantity* and back to the picking
- Set the done quantity to 10
- Open the Quality Check wizard
- Try to fail 3 units
Issue:
------
Validating the partial failure raises a `ValidationError`:
- Missing required value for the field 'Team' (team_id)
The quality check split is not performed and the picking cannot be processed.
Cause:
--------
Quality checks created on-demand from the picking (via the gear menu) have
no associated `quality.point` or `stock.move.line` — only
`picking_id` is set at creation time.
https://github.com/odoo/enterprise/blob/7d40d6b787511bf2fcbf581859ed2cd38ba3f658/quality_control/models/quality.py#L457
In `_move_to_failure_location()`, the `move_line` branch assumes
`check.move_line_id` is populated. Since it is empty for on-demand
checks, the split logic operates on an empty recordset.
The new quality check for the split is then created via:
https://github.com/odoo/enterprise/blob/7d40d6b787511bf2fcbf581859ed2cd38ba3f658/quality_control/models/quality.py#L493
At this point both `failed_move_line` (a copy of the empty move line) and
`check.point_id` are empty. `_get_check_values(False)` cannot derive
fields normally sourced from the quality point (`team_id`, `company_id`,
`measure_on`, `test_type_id`, etc.), causing the `ValidationError` on record creation.
Fix:
----
- If the quality check is not linked to a move line, find the matching move line
from the picking before splitting the failed quantity.
https://github.com/odoo/enterprise/blob/7d40d6b787511bf2fcbf581859ed2cd38ba3f658/quality_control/models/stock_move_line.py#L94-L97
- `_get_check_values()` normally takes values from a Quality Point.
Since on-demand quality checks do not have one, fill the missing
values (`team_id`, `measure_on`) from the original quality check instead.
This allows manually created quantity-based quality checks to be
split correctly after a partial failure.
---
opw-6428749