Monday, September 7, 2026
11 changes · saas-18.3
Resolved issues and error corrections
When portal users submit a website form to create a Field Service task, the phone number they provide is now saved on the task. Public submissions still include the phone number in the task description, while safeguards prevent users from changing another contact's details.
Original PR description
# How to reproduce - Install Field Service - Add a form on the Website - Set the form's action to "Create a Task" - Create a new internal/portal user - Login as that user - Submit the form with the required data and a phone number - Inspect the created task as an admin # Issue partner_phone is empty # Cause If the form alters an existing user, we prevent any edition of that user : https://github.com/odoo/odoo/blob/615e54ecd2722433953931e1d51be15b069288c3/addons/website_project/controllers/main.py#L52-L59 # Proposed Solution The PO asked for the following : - if the task is submitted by a portal user -> show the number in partner_phone - if the task is submitted by a public user -> show the number in the description BUT, for security reason, we can't let a user edit any other user. So we limit the assignation to partner_phone only when the current user correspond to the edited partner opw-6374641 Forward-Port-Of: odoo/odoo#285065
Inventory users without Accounting permissions can now view and create E-Waybills without access errors. This prevents disruption during stock and delivery workflows that require E-Waybill documents.
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
French point of sale transactions that encounter e-invoicing data issues will now download the regular invoice instead of an incorrect pro-forma document. This keeps sales flowing while avoiding confusion for customers and staff when external e-invoicing submission cannot proceed.
Original PR description
## Steps to reproduce: - Create a customer with France E-Invoicing (UBL2.1) as eInvoice format in a French company - Go to the PoS, select that partner - Make a sale and ask for an invoice - The…
## Steps to reproduce: - Create a customer with France E-Invoicing (UBL2.1) as eInvoice format in a French company - Go to the PoS, select that partner - Make a sale and ask for an invoice - The downloaded invoice will be a pro-forma invoice ## Why the fix: The pro-forma should not be used here, it is because it is used as a fallback when we get an error while trying to print the invoice. https://github.com/odoo/odoo/blob/4a508586970e44367bbdbbb3cbe88ffb5a1eadb7/addons/account/models/account_move.py#L6187-L6204 As we get an error while trying to send the data with this setup, it goes to the fallback and prints a pro-forma invoice, even though this should not be the case, a regular invoice would do. This happens because when an error is found, we do not populate invoice_pdf_report_id, so it goes to the fallback. We now check if there are any errors in the order, and if there are and the customer requests an ubl_21_fr invoice, we just print the invoice as it is, without going to the pro-forma fallback, as this is not the intended flow. With this fix, we now have the same flow as we do in the sales module, that allows the sale even if the customer has missing data. It will just print the invoice and allow the sale but won't send anything to external entities. opw-6428369 Forward-Port-Of: odoo/odoo#281420
Validated time off entries now keep their calendar blocking when their dates are shortened, such as when an employee leaves mid-leave. This prevents payroll from incorrectly counting affected time off days as worked attendance.
Original PR description
Problem ------- Fixes bug caused by PR odoo#249527. When a validated time off's dates are shortened while it stays validated (e.g. when the employee's departure date falls int he middle of the…
Problem ------- Fixes bug caused by PR odoo#249527. When a validated time off's dates are shortened while it stays validated (e.g. when the employee's departure date falls int he middle of the leave), the linked resource.calendar.leaves record was unconditionally unlinked. To reproduce: 1. Create and validate a time off request covering a whole month. 2. Register the employee's departure with a departure date in the middle of that time off. 3. Generate the employee's last payslip. The leave is correctly cut at the departure date, but since it never leaves the `validate` state, it never goes through `_validate_leave_request()` again, so its resource.calendar.leaves record is never recreated. The days that were covered by the deleted entry are no longer blocked in the employee's resource calendar, so the payslip's worked day lines (computed from resource.calendar.leaves) count them as attendance instead of time off. Cause ----- `hr.leave.write()` removed the resource.calendar.leaves record any time either the state changed away from `validate` or the leave's dates changed, regardless of whether the leave remained validated. Date-only changes on an already-validated leave never re-trigger validation, so the entry was not recreated. Solution -------- Only remove the resource.calendar.leaves record when the leave actually loses its validated state. When a validated leave's dates change but it stays validated, amend the existing resource.calendar.leaves record in place instead, falling back to creating one if none exists. Related PR: odoo#249527 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Early payment discount entries now preserve the invoice line cost allocation for all discount calculation methods. This prevents reporting details from being lost when discounts are applied, while leaving the existing included-tax behavior unchanged.
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#282541
Bank reconciliation rules now use the company's language when creating journal item labels. This prevents automated reconciliation and users in different languages from creating inconsistent or incorrect labels for the same rule.
Original PR description
### Problem `label` on `account.reconcile.model.line` is a **translatable** field, but its value is written onto the journal item created when the model is applied (`account.move.line.name`). That…
### Problem
`label` on `account.reconcile.model.line` is a **translatable** field, but its value is
written onto the journal item created when the model is applied (`account.move.line.name`).
That means the label is read in the language of whoever applies the model:
- a user working in another language writes the translated value;
- the auto-reconciliation cron writes the **source** value, since it runs as OdooBot.
So the very same reconcile model ends up writing two different labels on the journal items,
depending on who applied it.
### How it shows up
It becomes visible when a reconcile model is created by **duplicating** an existing one and
the label is then edited while working in a non-source language. The translation holds the
new text, while the source value silently keeps the label of the original model — and the
source value is exactly the one the cron writes. The result is a set of journal items where
some carry the intended label and some carry the label of an unrelated model.
### Fix
The journal item belongs to the company, so the label is read in the **company** language
via a small `_get_aml_label()` helper, instead of the language of the current environment.
It falls back to the current behaviour when the company has no language set.
`_prepare_aml_vals()` is the only place in 18.0 that reads `self.label` for the journal item.
### Test
Adds `TestBankRecWidget.test_auto_reconcile_model_label_uses_company_lang`: a reconcile model
whose line label is translated in the company language, applied by the auto-reconciliation
cron running in the source language, and asserts the journal item carries the company-language
label.
```
odoo -d <db> -u account_accountant --test-enable --stop-after-init \
--test-tags /account_accountant:TestBankRecWidget.test_auto_reconcile_model_label_uses_company_lang
```
Without the fix the test fails with `[{'name': 'Frais bancaires'}] != [{'name': 'Bank fees'}]`;
with the fix it passes. The full `account_accountant` suite was also run on a clean 18.0
database: 203 tests, 0 failed, 0 errors.
Forward-Port-Of: odoo/enterprise#128433The online shop now blocks combo products from being added to the cart when required choices are missing, even if someone bypasses the on-screen button controls. This prevents customers from checking out with incomplete combo orders and keeps order data consistent.
Original PR description
Steps to reproduce: =================== 1. Publish a combo product whose choices each offer more than one item, so the combo configurator is shown. 2. On the shop, open the product and click "Add to…
Steps to reproduce: =================== 1. Publish a combo product whose choices each offer more than one item, so the combo configurator is shown. 2. On the shop, open the product and click "Add to Cart". 3. Select an item for one combo choice only. The dialog's "Add to cart" button stays disabled. 4. Remove the `disabled` attribute from that button with the browser developer tools and click it. => The combo is added to the cart with one of its choices unanswered, and the order can be paid in that state. Root cause: =========== The incomplete selection is only prevented on the client side, by disabling the button until every choice has been answered. Server side, `/website_sale/combo_configurator/update_cart` only rejects a completely empty selection, never that the selected items cover every choice. Fix: ==== Reject the request unless the selected combo items cover exactly the combo choices of the product. Comparing the set of choices rather than counting the items also rejects a selection answering the same choice twice while leaving another one unanswered. opw-6478837 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283465
Restored or duplicated databases with neutralization enabled are now neutralized before background scheduled tasks can detect and run on them. This reduces the risk of copied databases accidentally sending emails, processing jobs, or triggering other automated actions before they are made safe.
Original PR description
Before this commit: If you restore a backup or duplicate a database via /web/database/manager with "Neutralize" enabled, the cron workers had a small window between the creation of the Registry and the neutralization of the database where they could try to execute crons. Since neutralize_database does not require a full registry to run (it only does raw SQL operations), we can run it before the Registry creation (which has the effect, among other things, of making the cron workers aware of the new database). opw-6517819 Forward-Port-Of: odoo/odoo#286532
Fixed an issue in Restaurant POS where items already completed by the kitchen could appear again after being split and transferred to another table. This prevents duplicate kitchen work and keeps preparation tickets accurate when staff move items between tables.
Original PR description
After a preparation ticket was marked as completed, transferring one of its items to another table through Split still kept that item in the order's kitchen history. When a new product was later…
After a preparation ticket was marked as completed, transferring one of its items to another table through Split still kept that item in the order's kitchen history. When a new product was later added and sent from the destination table, the preparation display created a new ticket containing both the new item and the already completed one. Steps to reproduce: ------------------- * Open a POS session and the Preparation Display * Select Table 1, add 2 items and send them to the kitchen * Mark the preparation ticket as Completed * On Table 1, open Split, select one item and Transfer it to Table 2 * Open Table 2, add a new item and send it to the kitchen > Observation: The new preparation ticket contains the newly added item and the previously completed transferred item. Why the fix: ------------ Split and table transfer move preparation history to the destination order with a new line uuid, but the preparation display still tracks the original ticket on the source order. Without marking the moved quantity as already sent, the server treated the transferred line as pending and included it again in the next ticket. Set transferredQty when moving preparation history on split/transfer, and read it reliably in _process_preparation_changes so only items with a real quantity increase are sent to the kitchen. opw-6146176 Related : https://github.com/odoo/odoo/pull/277780
Guest checkout customers who enter a valid EU VAT number now have it verified immediately when their address is created. This ensures eligible intra-community purchases receive the correct 0% VAT treatment instead of being charged domestic VAT.
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
Restaurant POS now correctly handles items moved between tables after kitchen preparation is completed. This prevents already completed items from appearing again on new kitchen tickets, reducing duplicate work and confusion for staff.
Original PR description
After a preparation ticket was marked as completed, transferring one of its items to another table through Split still kept that item in the order's kitchen history. When a new product was later…
After a preparation ticket was marked as completed, transferring one of its items to another table through Split still kept that item in the order's kitchen history. When a new product was later added and sent from the destination table, the preparation display created a new ticket containing both the new item and the already completed one. Steps to reproduce: ------------------- * Open a POS session and the Preparation Display * Select Table 1, add 2 items and send them to the kitchen * Mark the preparation ticket as Completed * On Table 1, open Split, select one item and Transfer it to Table 2 * Open Table 2, add a new item and send it to the kitchen > Observation: The new preparation ticket contains the newly added item and the previously completed transferred item. Why the fix: ------------ Split and table transfer move preparation history to the destination order with a new line uuid, but the preparation display still tracks the original ticket on the source order. Without marking the moved quantity as already sent, the server treated the transferred line as pending and included it again in the next ticket. Set transferredQty when moving preparation history on split/transfer, and read it reliably in _process_preparation_changes so only items with a real quantity increase are sent to the kitchen. opw-6146176 Related : https://github.com/odoo/enterprise/pull/125165