Thursday, August 27, 2026
34 changes · saas-19.2
Enhancements to existing features
The Live Chat settings now explain that automatic chat popups only open on larger screens. This helps teams avoid confusion when testing on mobile devices, where visitors must tap the chat button manually.
Original PR description
The 'Open automatically' action only triggers the auto popup on larger screens (`ui.isSmall` is checked in `AutopopupService. allowAutoPopup`). On mobile/small viewports, only the chat button is shown and the visitor must tap it manually. The existing help text does not mention this, which could lead to confusion when the auto popup does not trigger during testing on mobile. Update the field's help text to explicitly state that automatic opening is limited to larger screens. opw-6459279 Forward-Port-Of: odoo/odoo#284785
This update improves internal mail-related testing so unusual test data no longer causes the test suite to crash during parallel runs. It helps maintain smoother quality checks and reduces interruptions for developers, with no direct change to end-user features.
Original PR description
If the value needs to be serialized for IPC (cough cough pytest-xdist) and a weirdo sets recordsets as message values, the serialization fails and the test suite crashes. Since this is just subtest identification it shouldn't be too much of an issue. Forward-Port-Of: odoo/odoo#284279 Forward-Port-Of: odoo/odoo#284178
Belgian localization tax records have been updated so 0% taxes now include the non-deductible fiscal position where it was missing. This improves consistency in Belgian accounting setup and helps ensure tax treatment is correctly applied in relevant business scenarios.
Original PR description
Adding non-deductible fiscal position to taxes that were missing it in the data. task-6389423 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284504 Forward-Port-Of: odoo/odoo#278020
Recruitment job listings now limit available working schedules based on the company linked to the job. This helps teams keep job postings consistent and prevents selecting schedules that are not valid for that company.
Original PR description
In order to maintain proper job listings and make sure all working schedules are valid, working schedule domain is now depeding on the job listing company. Task: 6408914 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#283939 Forward-Port-Of: odoo/odoo#282993
Recruitment job listings now limit available working schedules based on the company tied to the job. This helps keep job postings consistent and prevents selecting schedules that do not apply to the relevant company.
Original PR description
In order to maintain proper job listings and make sure all working schedules are valid, working schedule domain is now depeding on the job listing company. Task: 6408914 Forward-Port-Of: odoo/enterprise#128824 Forward-Port-Of: odoo/enterprise#128241
Resolved issues and error corrections
This fix ensures costs linked to projects are correctly matched to the right sales order for reinvoicing, even when projects share analytic accounts or costs use multiple analytic accounts. Businesses should see fewer missed reinvoiceable costs and more accurate customer billing.
Original PR description
### Before this fix --- The `_get_so_mapping_from_project()` method returns a mapping where the key is the move line ID and the value is a `sale.order` record (or `None`). Because of the issues…
### Before this fix
---
The `_get_so_mapping_from_project()` method returns a mapping where the key is
the move line ID and the value is a `sale.order` record (or `None`).
Because of the issues described below, a valid `sale.order` could be available
for reinvoicing, but the corresponding move line might still not be mapped to
that sale order. As a result, the move line is not added to the reinvoiceable
sale order.
However, the implementation has two issues:
#### 1. Projects are overwritten when they share the same analytic account
`project_per_accounts` is built as a dictionary mapping an analytic account ID
to a single project. If multiple projects reference the same analytic account,
each new assignment replaces the previous one. As a result, only the last
project associated with a given analytic account is retained.
**Example:**
* Analytic Account **AA1** is linked to **Project A** and **Project B**.
* The dictionary becomes `{AA1: Project B}`.
* **Project A** is lost, even though it also references **AA1**.
**Steps to reproduce:**
1. Create an analytic account **AA1**.
2. Create **Project A** and **Project B**, both linked to **AA1**.
3. Create **Sale Order SO1** linked only to **Project A**.
4. Create a vendor bill (or expense) that generates an AML using **AA1** for a
product configured with **Reinvoice Costs = At Sales Price**.
5. Validate the document.
**Expected behavior:**
The product should be added to **SO1** for reinvoicing.
**Actual behavior:**
The move line is not mapped to **SO1**, so no sale order line is created.
#### 2. Previously found projects are overwritten during iteration
The `project` variable is reassigned on every iteration of the loop. After the
loop completes, it only contains the project (or lack of one) corresponding to
the last processed analytic account. This can cause valid projects found earlier
in the loop to be discarded.
**Example:**
* Move line has analytic accounts **AA1** and **AA2**.
* **AA1** maps to **Project A**.
* **AA2** has no linked project.
* After the loop, `project` is `None`, even though **Project A** was found.
**Steps to reproduce:**
1. Create analytic accounts **AA1** and **AA2**.
2. Create **Project A** linked to **AA1** only.
3. Create **Sale Order SO1** linked to **Project A**.
4. Create a vendor bill (or expense) whose AML is distributed between **AA1**
and **AA2**, where **AA2** is processed after **AA1**.
5. Validate the document.
**Expected behavior:**
The move line should still be mapped to **SO1** because **AA1** references
**Project A**.
**Actual behavior:**
The last processed analytic account (**AA2**) overwrites the previously found
project, causing the move line not to be linked to **SO1**.
### After this fix
---
* `project_per_accounts` stores **all** projects associated with each analytic
account instead of keeping only the last one.
* The project lookup preserves all valid project candidates instead of
overwriting previously found results during iteration.
* As a result, the method can resolve the related `sale.order` in more cases,
improving the overall accuracy of the mapping.
> **Note:** This change prevents valid project associations from being lost
> when multiple projects share an analytic account or when multiple analytic
> accounts are processed for the same move line.
**OPW:** 6294615
Forward-Port-Of: odoo/odoo#277110Documentation and clarification updates
ERPVibe Limited has signed Odoo's Corporate Contributor License Agreement. This formalizes the legal terms for ERPVibe's contributions to Odoo, supporting compliant collaboration without changing product behavior.
Original PR description
ERPVibe Limited signs the Odoo Corporate Contributor License Agreement v1.0. Forward-Port-Of: odoo/odoo#283477
This update corrects how grouped data results are prepared when the same field is used more than once. It prevents duplicated or incomplete results that could cause errors for users or integrations relying on grouped reports.
Original PR description
`_read_grouping_sets` dispatches each SQL result row to the grouping set(s) that requested it, using the `GROUPING()` bitmask as the key. When a grouping set repeats a groupby spec, e.g. `['foo',…
`_read_grouping_sets` dispatches each SQL result row to the grouping set(s) that requested it, using the `GROUPING()` bitmask as the key.
When a grouping set repeats a groupby spec, e.g. `['foo', 'foo']`, it computes the exact same bitmask as the grouping set for the deduplicated column alone (`['foo']`).
Two grouping sets sharing a bitmask were treated as interchangeable duplicates: only the first one seen got its extractor registered, and its already-extracted result list was later blindly copied onto every other grouping set sharing that bitmask.
This is wrong whenever those sets don't actually share the same shape:
`_read_grouping_sets(grouping_sets=[['foo'], ['foo', 'foo']])`
returned
`[('my_foo_val', <aggregate>), ('my_foo_val', <aggregate>)]`
instead of
`[('my_foo_val', <aggregate>), ('my_foo_val', 'my_foo_val', <aggregate>)]` (repeating the value, as `_read_group` does).
Callers unpacking crashed with `ValueError: not enough values to unpack`.
Fix:
- Deduplicate the SQL terms of each grouping set before building its `GROUPING SETS (...)`, so that grouping sets which are physically the same always produce a single row from PostgreSQL.
- Register every grouping set's own extractor under its bitmask in a list, instead of keeping only the first one seen, and dispatch each result row to all of them.
task-6511626
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#284683Users editing product translations no longer lose unsaved text when they drag the translation pop-up. This prevents accidental rework and makes translation updates more reliable before saving.
Original PR description
Step to reproduce: - have atleast two language and install sale - open any product, hover over product, and click on Translation button - Enter a value for one of language - drag the dialog Observation: - we lose the data, we just entered and fallback to original data Cause: - Inputs used `t-att-value="term.value"`, bound to original data. Since this content is passed to Dialog via slot, it is rendered/patched as part of Dialog's render cycle, - Dragging updates Dialog's state, triggering a patch that re-evaluated the slotted template and reset input values (which comes from `term.value`) Fix: - bind value to `updatedTerms[term.id] ?? term.value` so edits survive patches triggered by the parent Dialog opw-6431521 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283514
Inventory users who already had permission to update stock counts can now make the same quantity changes directly from a product page. This removes an unnecessary detour through the Inventory Adjustments menu and makes stock updates more consistent.
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#282010Copying an image that is already attached to another record now reuses the existing file instead of leaving an unnecessary duplicate. This helps keep stored files cleaner and avoids redundant attachments without changing the user workflow.
Original PR description
Copying an image attachment already linked to another record could leave a redundant duplicate behind instead of reusing the existing one. opw-6463012 Forward-Port-Of: odoo/odoo#283221 Forward-Port-Of: odoo/odoo#282287
Setting a contact on a delivery transfer now keeps the destination location defined by the operation type instead of reverting to the generic customer location. This prevents incorrect warehouse routing for businesses using dedicated customer stock locations.
Original PR description
### Steps to reproduce: - In the settings: Enable Storage Locations - Create a customer location "Customer stock" with "Customers" as its parent location - Create a delivery operation type "Deliver…
### Steps to reproduce: - In the settings: Enable Storage Locations - Create a customer location "Customer stock" with "Customers" as its parent location - Create a delivery operation type "Deliver Super Customer" and set its default destination location to "Customer stock" - Go to Inventory > Overview > Deliver Super Customer > New - Set a contact on the transfer #### > The destination location switches from "Customer stock" to "Customers" ### Cause of the issue: The `location_dest_id` of `stock.picking` depends on its `partner_id`. So that changing the partner recomputes the locations of the transfer. However, as soon as the destination of the operation type has a `customer` usage, the `property_stock_customer` of the contact replaces it unconditionally: https://github.com/odoo/odoo/blob/04f3a7bca99d0144a4ea871be9625db368b196ca/addons/stock/models/stock_picking.py#L949-L963 However, the `property_stock_customer` falls back to an `ir.default` pointing at the default `Customers` location when nothing is set on the contact: https://github.com/odoo/odoo/blob/1c40fab04b71def8f3645c4c4bb0c1441057f307/addons/stock/data/stock_data.xml#L71-L72vs The override comes from 8a0775aa1dd9, which replaced an `elif` fallback on the contact by an "unconditional" substitution as this fallback had become unreachable once `default_location_src_id` and `default_location_dest_id` were made required: https://github.com/odoo/odoo/blob/04f3a7bca99d0144a4ea871be9625db368b196ca/addons/stock/models/stock_picking.py#L34-L41 opw-6421090 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283886 Forward-Port-Of: odoo/odoo#280016
Swedish vendor payment batches now generate files that correctly match the selected pain.001.001.09 format. This prevents banks from rejecting payments because the file content used an older format despite showing the newer version.
Original PR description
**Steps to reproduce:** - Install Accounting and l10n_se - Switch to a Swedish company (e.g. SE Company) - In Accounting settings, set "Identification" with anything - In Bank journal: * Set an…
**Steps to reproduce:** - Install Accounting and l10n_se - Switch to a Swedish company (e.g. SE Company) - In Accounting settings, set "Identification" with anything - In Bank journal: * Set an account number * Make sure "Swedish ISO20022" is available in "Outgoing Payments" * Set "pain.001.001.09" as "XML Format" in "Outgoing Payments" - Create a vendor payment: * Vendor: [a vendor with a trusted bank account] * Payment Method: Swedish ISO20022 * Amount: [any] - Confirm the payment - From the payments list, select the payment and create a batch - Validate the batch payment **Issue:** When the batch is validated, a `pain.001.001.09` file should be generated. However, its content is that of a `pain.001.001.03` file, even if the version reported in the file is `pain.001.001.09`. For example, `<ReqdExctnDt>` should contains a subnode `<Dt>` in `001.001.09`, which is not the case. It leads to the file being rejected as non-compliant to `pain.001.001.09`. opw-6472050 Forward-Port-Of: odoo/enterprise#129234 Forward-Port-Of: odoo/enterprise#128598
POS refunds in Kenya now send the original sale's KRA invoice number to eTIMS instead of the refund's own order number. This prevents valid refunds from being rejected because eTIMS was checking them against the wrong original invoice.
Original PR description
Steps to reproduce: - Set up a company in Kenya with eTIMS. - Sell an order from the POS and send it to eTIMS. - Refund that order and send the refund to eTIMS. Cause of the issue: When we build the…
Steps to reproduce: - Set up a company in Kenya with eTIMS. - Sell an order from the POS and send it to eTIMS. - Refund that order and send the refund to eTIMS. Cause of the issue: When we build the JSON for eTIMS, the "orgInvcNo" field (the KRA invoice number of the order we are refunding) was always set to `self.sequence_number`, which is just the order's own number in its session. This is wrong for a refund: eTIMS then can't find the item on "the original invoice" (since it's looking at the wrong invoice), and also can't check the amounts, since it's comparing them to the wrong order. eTIMS then rejects the refund with a 910 error, like "item sequence ... does not exist on the original invoice" or "amount is incorrect for item ...". The invoice-based flow (account_move.py) already does this the right way, using `reversed_entry_id.l10n_ke_oscu_invoice_number`, but the POS order flow was not doing the same thing. Solution: For a refund order, use the KRA invoice number of the refunded order (`refunded_order_id.l10n_ke_oscu_order_number`) instead of the refund's own sequence number. Normal sales keep working like before. opw-6445174 Forward-Port-Of: odoo/enterprise#128387
Payment XML files now use uppercase encoding declarations to better match strict bank validation requirements. This helps avoid warnings or rejections from providers such as SIX in Switzerland, without changing the payment workflow.
Original PR description
The W3C recommendations for XML state that the encoding defined for an XML document should not be case-sensitive. However, some banking providers (SIX for Switzerland) are stricter and may throw warnings or errors if upper-case is not used. https://www.w3.org/TR/2008/REC-xml-20081126/#NT-EncodingDecl opw-4948708 Forward-Port-Of: odoo/enterprise#128301 Forward-Port-Of: odoo/enterprise#125807
The Turkish reports journal form now places the sales return account field in the correct position. This prevents labels and values from appearing under the wrong captions, making accounting setup clearer for users.
Original PR description
The journal form renders `default_account_id` as six standalone labels followed by two `nolabel="1"` fields, one for bank, cash and credit journals and one for sale, purchase and general ones. The xpath matched the first of those two fields, so the return from sales account was inserted between them. Its own label then landed in the middle of the label run, shifting the group grid: both labels rendered side by side with their values underneath, each next to the wrong caption. Anchor on the second field instead, so the new field follows the whole label and field run. Task-6438412 Forward-Port-Of: odoo/enterprise#128083
Users auditing accounting report figures can now switch between available views such as list, pivot, graph, and kanban. This makes it easier to analyze the journal items behind report numbers in the format best suited to the task.
Original PR description
Problem: When auditing reports, the audit cell action was only showing the journal items in the list view, and not enabling other view modes (pivot, graph, kanban). Steps to reproduce: 1. Go to Accounting > Reporting > Balance Sheet 2. Click on any cell with a number in the report 3. Notice how the journal items are only shown in the list view, and you cannot switch to other view modes. Cause: The action was hardcoded to only show the list view. opw-6403704 Forward-Port-Of: odoo/enterprise#129214 Forward-Port-Of: odoo/enterprise#128563
French companies can now update an employee's working schedule even when the employee has approved time off on a non-working day, such as a Saturday. The fix prevents those days-off leave records from being converted into an invalid date range, avoiding save errors while preserving existing valid leave behavior.
Original PR description
**Problem:** For a French company, an employee whose working schedule differs from the company's cannot have their Working Hours changed when they have a validated time off that falls on a…
**Problem:** For a French company, an employee whose working schedule differs from the company's cannot have their Working Hours changed when they have a validated time off that falls on a non-working day (e.g. a Saturday). Saving fails with "The operation cannot be completed: The start date must be before or equal to the end date." **Steps to reproduce:** 1. Install l10n_fr_hr_holidays and work in a French company. 2. Set the company Working Hours and a reference (Paid) Time Off type. 3. Give an employee a Monday-to-Friday schedule that differs from the company's. 4. Create a one day Paid Time Off for the employee on a Saturday. 5. Change the employee's Working Hours. **Current behavior:** Saving is rejected by the date_from <= date_to constraint; the Working Hours cannot be changed as long as the weekend time off exists. **Expected behavior:** The Working Hours can be changed and the time off keeps a valid date range. **Cause of the issue:** When the French computation applies, `_get_fr_date_from_to` moves `date_start` forward to the first working day and, in a separate loop, moves `date_target` forward while the next day is a non-working day. The two loops are asymmetric: for a leave lying entirely on non-working days (a single Saturday for a Monday-to-Friday employee) `date_start` is pushed to the following Monday while `date_target` only reaches the Sunday. The pair is then written to `date_from`/`date_to` as Monday > Sunday, violating the date_from <= date_to constraint. **Fix:** A leave that contains no working day has nothing to anchor the "lost days" extension on, so the adjustment must not apply. Detecting the crossed pointers and keeping the leave's original dates preserves a valid range while leaving every leave that contains at least one working day untouched. opw-6348425 Forward-Port-Of: odoo/odoo#278833
Submitting a tax report opened from a return now uses the exact return shown on screen, including Dutch VAT corrections. This prevents corrections from accidentally submitting or marking the original VAT return for the same period.
Original PR description
Opening a tax report from a return and submitting it could act on a different return than the one on screen. _get_return_from_report_options searches by company, period and report with limit=1, but…
Opening a tax report from a return and submitting it could act on a different return than the one on screen. _get_return_from_report_options searches by company, period and report with limit=1, but that combination is not unique: l10n_nl declares two return types on l10n_nl.tax_report, nl_tax_return_type and nl_tax_correction_return_type, so a VAT return and its correction both match. Which one is returned is then decided by _order (is_completed, date_deadline, name, id). For a Dutch VAT correction it resolves to the original VAT return of the same quarter, so send_xbrl submits and flags that record instead of the correction. The options already carry the return type they were built for, in the return_periodicity filter, so restrict the search to it when it is set. l10n_nl_reports kept a return_id option for the same reason when computing the already declared amount of a suppletie; it can use _get_return_from_report_options now. opw-6421300 Forward-Port-Of: odoo/enterprise#127073
French VAT reports no longer include a direct debit payment instruction when the company is due a VAT refund rather than owing VAT. This prevents DGFiP rejection errors for refund cases while keeping the normal VAT payment process unchanged.
Original PR description
`_prepare_edi_vals` always called `_get_formatted_payment_values()`, adding an EDI-Paiement (telereglement) block to the T-IDENTIF of the 3310CA3, regardless of whether the company owes VAT or is in…
`_prepare_edi_vals` always called `_get_formatted_payment_values()`, adding an EDI-Paiement (telereglement) block to the T-IDENTIF of the 3310CA3, regardless of whether the company owes VAT or is in a credit position. Steps to reproduce: - French company in a VAT credit position, requesting a refund. - Fill a bank account line, the account to receive the refund and send the VAT report to the DGFiP. Current behaviour: The DGFiP returns a negative acknowledgement on the CA3 interchange: "Telereglement 1 rejete: Montant telereglement absent ou invalide. Code erreur : 018", even though the declaration itself is accepted. The wizard's bank account lines are reused for two opposite purposes: the account to debit when VAT is due, and the account to credit when a refund is asked. `_get_formatted_payment_values()` builds a payment order from them unconditionally, so a telereglement for the credit amount is emitted in the refund case. A telereglement is invalid when no VAT is due, hence error 018. A return nets to either a payment or a credit, never both, so the two cases are mutually exclusive. This commit guards the call with `self.is_vat_due`, so the telereglement is only generated when the company actually owes VAT. The VAT-due flow is unchanged. opw-6275695 Forward-Port-Of: odoo/enterprise#124694 Forward-Port-Of: odoo/enterprise#120840
Shop Floor cards now handle long unit names, quantities, and manufacturing notes without spilling outside the card. This keeps production information readable and prevents confusing layouts for shop floor users.
Original PR description
Steps to reproduce --- 1. Enable Units of Measure (`uom.group_uom`) and give a unit a very long name. 2. Build a product using that unit for its components, and set a long note (plain text or an HTML…
Steps to reproduce --- 1. Enable Units of Measure (`uom.group_uom`) and give a unit a very long name. 2. Build a product using that unit for its components, and set a long note (plain text or an HTML table) on its manufacturing order. 3. Open that operation in Shop Floor. Observed: the finished-product name breaks one letter per line, the long unit next to the quantity and the note both run off the right edge of the card. Expected: name, unit and note stay within the card, wrapping or scrolling. Issue --- Each of these rows is a flexbox whose children keep the default `min-width: auto`, so they never shrink below their content's intrinsic width. A long UoM name therefore forces its flex sibling (the product name) down to min-content and wraps it one character per line, while long quantities and notes push past the fixed-width card instead of wrapping. Adding `min-w-0` lets the flex items shrink, replacing `text-nowrap` with `text-wrap` on the quantity lets the value wrap, and wrapping `logNote` in a `min-w-0 overflow-auto` span contains an HTML-table note within the card. opw-6496759
Users can now insert contact property fields into email marketing messages without the editor incorrectly blocking them. This restores the Update action for these placeholders and prevents valid contact property fields from being marked as invalid.
Original PR description
Root cause: Inserting a Field placeholder for a contact property in the mail editor builds the path with a property accessor, of the form properties.get('name') for a plain property or…
Root cause:
Inserting a Field placeholder for a contact property in the mail editor builds the path with a property accessor, of the form properties.get('name') for a plain property or properties.get('name', env['res.partner']) when the property points to a relation. The field service walks a path by splitting it on every dot, so it reads get('name') as a field name and the dot inside env['res.partner'] as another separator. Neither matches a real field, so the path is reported as an invalid field chain, the Update button stays inactive and the placeholder cannot be inserted.
This validation only runs since https://github.com/odoo/odoo/commit/a4efb745e9f4ba24dfa01f1e17e65f1a7cac90a5 switched the mail editor to the generic dynamic field plugin, which re-reads the picked path through the field service instead of inserting it as is.
Fix:
Add a splitPath helper in field_service.js that splits the path on dots outside quotes and rewrites each get('name') or get('name', env['model']) accessor back to the property name, then use it in loadPath. loadPath is the single place every consumer goes through, so both the field chain shown in the popover and the Update button that reads the field info start resolving the property again.
Steps to reproduce:
1. Open a contact in Contacts, open the cog menu, click Edit Properties, add a property and save.
2. Open Email Marketing and create a new mailing.
3. Set Recipients to Contact.
4. In the mail body type /field and pick Field.
5. In the popover open the field selector, pick Properties then the property, and click Update.
=> the field shows Invalid field chain and the placeholder is not inserted
Ticket [link](https://www.odoo.com/odoo/project/49/tasks/6375568)
opw-6375568This fixes an issue where submitting expenses for multiple companies could send duplicate emails. The change helps ensure expense-related notifications are sent only as intended, reducing confusion for employees and approvers.
Original PR description
Fix a small issue resulting in mail duplication when submitting expenses from multiple companies that appeared in the infamous 704a5a19 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#283013
Deleting an active project no longer incorrectly moves document folders from archived projects to the trash. This protects files tied to archived projects from being hidden or disrupted while keeping cleanup behavior for truly unused folders.
Original PR description
Deleting a project also sends the folders of every archived project to the trash. ### Steps to reproduce - Install `documents_project`, where each project has its own Documents folder linked through…
Deleting a project also sends the folders of every archived project to the trash.
### Steps to reproduce
- Install `documents_project`, where each project has its own Documents folder linked through `project.project.documents_folder_id`.
- Create `Project 1`, `Project 2`, and `Project 3`, then archive the first two.
- Delete `Project 3`.
- The folders of `Project 1` and `Project 2` are moved to the trash with their contents, although both projects still exist and still reference them.
### Cause
`_archive_folder_on_projects_unlinked` only archives folders that are no longer used by any project. This was checked through a `documents.document` domain on `project_ids`.
The domain mixed two conditions on the same relation:
- `('project_ids', '!=', False)` checks that a folder has users,
- `('project_ids', 'not any', [('id', 'not in', self.ids)])` checks that it has no users outside the projects being deleted.
Those conditions are not evaluated the same way by the ORM. The first one keeps archived projects visible by disabling `active_test` internally, while the second one searches `project.project` normally and hides archived projects.
An archived project can therefore be counted as a folder user by one condition and ignored by the other, causing its folder to be archived.
### Fix
Check remaining users directly on `project.project` with `active_test=False`, so archived projects are included. Since only folders of deleted projects can become unused, the search starts from those folders instead of scanning all Documents.
opw-6442976
Forward-Port-Of: odoo/enterprise#127217Colombian DIAN document processing now handles missing or malformed ZIP attachments when building commercial event histories. This prevents vendor bill acknowledgement actions from crashing when earlier DIAN responses contain plain XML or corrupted attachment data.
Original PR description
When building the commercial event history for DIAN documents, the system crashes if an intermediate document's attachment is missing or is not a valid ZIP file. Steps to reproduce: - Create a vendor bill and send it to DIAN to generate a commercial event document. - Manually alter or corrupt the attachment of the first response document (e.g., save plain XML instead of a ZIP). - Click "Acusar Recibo" (Acknowledge Reception) on the bill. Issue: The system crashes when attempting to unzip a malformed, plaintext, or missing attachment while iterating through past documents to build the event history. Analysis: The system aggregates the XML of previous events to maintain the history trail. Doing so, the system assumes that all past attachments are ZIP archives. However, interacting with the DIAN API can occasionally result in plaintext XML, that raises `zipfile.BadZipFile` when unzipping. opw-5467690 Forward-Port-Of: odoo/enterprise#121494
This update corrects several issues in Luxembourg SAF-T/FAIA reports so exported files better match auditor and schema requirements. It ensures tax amounts, software version length, currency tax amounts, and customer/supplier invoice details are reported correctly, reducing validation errors during compliance checks.
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#129043 Forward-Port-Of: odoo/enterprise#126121
Refunds for POS orders with a fixed global discount now correctly mirror the original discounted total. This prevents inflated refund amounts that could block POS session accounting closure and leave sessions without accounting entries.
Original PR description
BACKPORT OF https://github.com/odoo/odoo/pull/278180 When refunding an order that had a fixed-amount global discount, the refunded total was inflated: e.g. a 65.00 order (80.00 - 15.00 fixed…
BACKPORT OF https://github.com/odoo/odoo/pull/278180 When refunding an order that had a fixed-amount global discount, the refunded total was inflated: e.g. a 65.00 order (80.00 - 15.00 fixed discount) was refunded as 95.00 instead of 65.00. On the refund order amount_paid then differed from amount_total, so the POS session's closing entry could not balance; _validate_session rolled back and no account.move was created, leaving the session closed with no accounting. When a refund is created, the product lines are negated and the discount line is excluded, then pos_discount re-applies the global discount to the refund order through applyDiscount. For a fixed amount, reduce_base_lines_to_target_amount targets an absolute value and does not follow the sign of the (now negative) base, so the discount keeps the sale sign. Percentages scale with the base and are not affected. Negate the fixed discount amount when the destination order is a refund so it mirrors the negative base. The stored discount_value is left unchanged so the auto-resync of the discount stays idempotent. opw-6458124 Forward-Port-Of: odoo/odoo#281958
This fixes a Point of Sale issue where opening an original paid order could crash if a related refund order had been cancelled but not deleted. Staff can now view those orders normally, avoiding disruption at the ticket screen.
Original PR description
When a return order is created from the front end and later cancelled without being deleted from the backend, opening the original order from the POS ticket screen causes the UI to crash. The issue…
When a return order is created from the front end and later cancelled without being deleted from the backend, opening the original order from the POS ticket screen causes the UI to crash. The issue occurs because the refundedQty getter in `addons/point_of_sale/static/src/app/models/pos_order_line.js` assumes that every refunded order line has a valid order_id. For cancelled return orders, line.order_id is no longer available, resulting in the following runtime error: `TypeError: can't access property 'state', line.order_id is undefined` As a result, selecting the original paid order from the Ticket Screen breaks the POS interface. **Steps to Reproduce** 1. Create a normal sale in the POS. 2. Keep the POS session open. 3. In the backend, open the POS order and click Return Products. 4. Cancel the generated refund order (do not delete it). 5. In the POS, navigate to Ticket Screen → Orders → Paid Orders. 6. Select the original order. Desired behavior after PR is merged: The original order should open normally without any errors, even if a related refund order has been cancelled. Issue Ticket: https://github.com/odoo/odoo/issues/260079 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279942
Sales users without accounting permissions can now open invoices created from sales orders that use cash rounding. The fix prevents an unnecessary access error while preserving their legitimate ability to view the invoice.
Original PR description
**Behavior:** When a sales user without accounting access rights tries to view an invoice created from a sale order where a cash rounding is applied, an AccessError is raised. This occurs because…
**Behavior:** When a sales user without accounting access rights tries to view an invoice created from a sale order where a cash rounding is applied, an AccessError is raised. This occurs because loading the invoice view triggers `_compute_tax_totals()`, which then passes the invoice's `invoice_cash_rounding_id` to `_get_tax_totals_summary()`. Which then ends up failing when trying to access fields on the `cash_rounding` record due to missing accounting rights, even though the user is allowed to view the parent invoice. This is fixed by ensuring reading fields on `cash_rounding` during tax total computation bypasses the access check using `sudo()`, as the user already has legitimate access to the invoice itself. **Steps to reproduce:** - As an admin, enable cash rounding then create one. - Create an invoice and set the Cash Rounding Method - In debug mode, go to 'Set Default Values' in the debug dropdown and set Cash Rounding Method = your rounding for all users - Go to users, and ensures that Demo has no accounting rights but has sales user rights - As Demo, create a sale order, confirm it, then create the related invoice. - When trying to acces said invoice, you should get an Access Error opw-6379781 Forward-Port-Of: odoo/odoo#284212 Forward-Port-Of: odoo/odoo#278815
Users can now update rental start or end dates on sales orders even if they do not have access to planning slots. The related planning entries are still updated behind the scenes, reducing errors and avoiding blocked rental order changes.
Original PR description
This commit prevents a potential access error, if a user changes the rental start date and/or end date of a sale order without the access rights to the 'planning.slot' model. In this case, we want the write to be executed and changes repercuted to the associated slots. Forward-Port-Of: odoo/enterprise#128778 Forward-Port-Of: odoo/enterprise#128365
The IoT display browser now starts only after the main Odoo service has finished initializing. This prevents the screen from first showing an error page and helps ensure the browser opens in fullscreen as expected.
Original PR description
Before this commit, the display driver (and therefore browser) were being started too early, causing the following issues: - The browser initially displays an error page, as it tries to load the status page before Odoo has finished starting. - The browser window is not fullscreen. This may be because it is started before labwc is fully initialised, as the correct fullscreen command line arguments are used. The second issue can be fixed by restarting the Odoo service, the first happens every time. After this commit, we start the IoT interfaces in the main run method, instead of when the module is imported, meaning that everything else has time to finish initialising. This solves both problems. task-6469793 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284386
This update adjusts point-of-sale self-order behavior so an unnecessary IoT connection error popup is not shown during kiosk printer testing. It helps keep automated checks aligned with the intended customer flow and reduces false test failures.
Original PR description
This PR fixes the test where iot request triggers a "failed to contact your iot box on local network popup" Forward-Port-Of: odoo/enterprise#128546 Forward-Port-Of: odoo/enterprise#128388
A costly styling rule in the messaging interface was simplified because it added little visible value. This should help keep the interface responsive without changing the user experience.
Original PR description
This PR cleans up a complex selector that is quite costly without providing any striking visual value. task-6481656 Forward-Port-Of: odoo/enterprise#128906
The Japanese localization now uses accurate labels for domestic and overseas fiscal positions. This prevents misleading wording for users configuring accounting rules in Japan and fixes a small English spelling issue.
Original PR description
Japanese translation "海外取引先" for domestic was clearly wrong.
Also fixed the misspelling ("Oversea" -> "Overseas") and removed the unnecessary "Customer" context from the name.
@qrtl
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#284608