Thursday, August 27, 2026
36 changes · 19.0
Resolved issues and error corrections
Users auditing accounting reports can now switch between available views, such as pivot, graph, kanban, and list, when opening journal item details from a report cell. This makes it easier to analyze report figures in the format that best supports their review.
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
This fixes link entry in the website and HTML builders so addresses like odoo.com, test@test.com, and phone numbers are automatically saved with the correct format. This prevents broken or inconsistent links on images and social media options, improving reliability for website editors.
Original PR description
Steps to reproduce: - Add an image and add a link on that image. - Set the URL input to "odoo.com". - Click outside the image and click on the image again. => The "http" protocol was added instead of "https". - Set the URL input to "test@test.com". - Click outside the image and click on the image again. => The "http" protocol was added instead of "mailto". The issue also occurs for phone numbers. The builder URL picker did not normalize entered URL values like the editor link popover does. Actions using the picker could therefore receive raw values and would need to normalized the URL in a different way. This commit reuses the editor link input normalization helper in the builder URL picker so committed and previewed URL values are handled consistently. task-6384411 Forward-Port-Of: odoo/odoo#275936
Appraisals now keep the template chosen by the user when they are confirmed or reset, instead of switching back to the first generic template. This prevents missing results when teams filter appraisals by the intended template and keeps appraisal records consistent.
Original PR description
Issue: When a user selects a generic appraisal template other than the first one, confirming or resetting the appraisal silently replaces that selection with the first generic template. Filtering…
Issue: When a user selects a generic appraisal template other than the first one, confirming or resetting the appraisal silently replaces that selection with the first generic template. Filtering appraisals by the originally selected template then fails to return the appraisal. Steps to reproduce: * Configure multiple appraisal templates without department restrictions. * Create an appraisal and select a template other than the first one. * Confirm the appraisal. * Filter appraisals by the selected template. Cause: `_compute_appraisal_template()` only preserved templates directly linked to the appraisal's department. Generic templates have no department relation, so a valid selected template was discarded whenever the computation was triggered by a state dependent department recomputation. The generic fallback then stored the first template instead. https://github.com/odoo/enterprise/blob/5c57ccbb13269af28de0a6c7f35454be52424f43/hr_appraisal/models/hr_appraisal.py#L195-L209 Solution: We need to distinguish the generic default loaded on an unsaved form from a compatible generic template already stored on an appraisal. Preserve the latter across recomputations while retaining department template priority during creation and rejecting templates that no longer match the appraisal's department or company. opw-6449041
Manufacturing orders in warehouses using a three-step process are now counted correctly in stock forecasts. This prevents replenishment screens from showing too little expected inventory, helping teams avoid unnecessary purchasing or production decisions.
Original PR description
### Steps to reproduce: - In the settings enable Multi-Steps Routes - Put your warehouse in manufacture in 3 steps - Create a storable product P - Create and confirm an MO for 1 unit of P - Go to…
### Steps to reproduce: - In the settings enable Multi-Steps Routes - Put your warehouse in manufacture in 3 steps - Create a storable product P - Create and confirm an MO for 1 unit of P - Go to Inventory > Operations > Procurement > Replenishment - Create a new one for P in WH/stock #### > The forecasted quantity in stock is still 0 but should be at 1 ### Cause of the issue: This is the exact use case already fixed in 85dd3369ed17b98b2ce485be04f140cf4cfa8aa3, which stamped the finished move with a `location_final_id` pointing at WH/Stock so that the move contributes to the forecast there even though its `location_dest_id` is the intermediate WH/Post-Production. That fix was reverted in practice by 42275f83dc5350822a625e19d65148e8b41ab1d4, which replaced the value with `mo.location_dest_id`: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_move.py#L466-L467 Its reasoning was that in a single-warehouse setup `location_dest_id` equals the warehouse stock location, so the behaviour would be unchanged. That holds in 1 and 2 steps, where the extra step is on the component side and only moves `default_location_src_id` to the pre-production location. It breaks in 3 steps, the only mode that also moves `default_location_dest_id`, to the post-production location: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L246-L247 and `_compute_locations` propagates it to the MO: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/mrp_production.py#L334-L341 WH/Post-Production is a sibling of WH/Stock under the warehouse view location, not a child of it. Since `location_final_id` takes precedence over `location_dest_id` for the non-done part of the move chain: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L331-L334 the finished move stopped being counted in the WH/Stock forecast. Why the test did not catch it: `test_3_steps_manufacturing_forecast` stayed green through the whole regression, because it scoped `virtual_available` with a `location_id` context key. `_get_domain_locations` only reads `location` and `warehouse_id`; `location_id` is silently ignored: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L284-L287 The call therefore fell through to the branch scoping the forecast to every warehouse view location: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L303-L309 and the warehouse view location is the common parent of both WH/Stock and WH/Post-Production. The assertion held regardless of where `location_final_id` pointed, so the test was a false positive from the start: it also passes with 85dd3369ed17b98b2ce485be04f140cf4cfa8aa3 fully reverted. Using the `location` key makes it fail without the fix and pass with it. ### Fix: Neither fix proposition was right on its own; each one was correct only in its own scenario. The`mo.warehouse_id.lot_stock_id` resolves the warehouse from the components, so it points at the wrong warehouse as soon as the finished product is produced for another one. `mo.location_dest_id` is the post-production location as soon as the warehouse manufactures in 3 steps, so it drops the quantity from the forecast of the manufacturing warehouse itself. What separates the two is not the warehouse but whether the destination is a transit step. In 3 steps the finished product only reaches the stock through the post-production push rule: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L57 so the final location is the stock of the warehouse owning that destination. Any other destination is already final and is kept as is, which leaves cross-warehouse MOs and destinations set to a sub-location of the stock untouched. The rule's destination is read rather than `warehouse.lot_stock_id` because a push move takes its destination from the rule and not from the operation type: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/stock_rule.py#L256-L260 so the forecast stays correct when the store step is reconfigured to land somewhere else than the warehouse stock. The rule is looked up on `pbm_route_id` by its `picking_type_id` instead of through `warehouse.sam_rule_id`, because that field is no longer set. It used to be an entry of `_generate_global_route_rules_values`, and it is that entry which made the generic warehouse machinery create the rule and store it back on the warehouse: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/stock_warehouse.py#L403-L410 11e69870db1c49d9a6af79ffd263e4e162b34b6b removed it when the post-production step stopped being a pull rule on the Manufacture route and became a push rule generated from `get_rules_dict`. Only the field declaration was left behind, and nothing writes it any more: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L21-L22 so reading it would silently give an empty recordset. The lookup is not delegated to `_get_push_rule` to avoid a search per finished move. opw-4882390 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283721 Forward-Port-Of: odoo/odoo#283234
Opening the Assets list no longer gets stuck when some assets contain outdated or invalid analytic account references. This keeps users able to view and manage asset records while the incorrect analytic distribution data can be handled separately.
Original PR description
Issue - If there are any account.asset records with analytic distributions with accounts that do not exist, it causes a recursive traceback when opening the list view of the account.asset model. The issue stems from `jsonToData` attempting to save the distributions json via the `save` call, where one (or multiple) accounts are non existent, which in turn runs `jsonToData` after refetching via the `load` call - overwriting `record.data` with the original, still-corrupt JSON. This creates a loop with no exit condition. Solution - In the Assets list every row is readonly, so save() is never reached, so root.load() never fires, so there is no reload to re-read the corrupt JSON. Makes the list accessible, even though the JSON values for the `analytic_distribution` are invalid. opw-6500446
The trial balance report no longer shows the unallocated earnings or losses line when it has a zero balance in all columns. This reduces clutter and helps users focus on meaningful financial information.
Original PR description
… zero The unallocated earnings/losses line was displayed even when its balance was zero in every column group, cluttering the report with uninformative rows. We therefore filter out lines whose balance is zero across all column groups.
Appointment invitation emails can now safely include public calendar links without triggering permission errors. This helps ensure attendees receive the expected email invitations reliably.
Original PR description
Since calendar attendee access tokens are restricted to system users, appointment mail templates must sudo token reads when generating public calendar links. This follows the same pattern as the calendar mail templates and avoids an AccessError when rendering attendee invitation emails. ref: https://github.com/odoo/enterprise/commit/88a3cca752a5f726cd0260b485fc93f65a268cf8 Task-4711415
Self-order websocket notifications no longer include full order details. This reduces unnecessary data sharing while keeping order status updates working for customers and staff.
Original PR description
Remove the order data from the websocket notification.
Fixes an issue where changing a product quantity in the customer portal could overwrite a manually adjusted unit price on a sales quotation. This helps ensure customers see the agreed price and prevents unintended repricing from pricelists.
Original PR description
Steps to reproduce: - Create a Sales Order in the backend - Add section order line and enable 'Set Optional' - Add a product under this section - Manually change the unit price of this product - Save and share quotation to open it from customer portal - In the portal, change the quantity of the product Expected Behavior: The unit price should stay the same Current Behavior: The unit price changes Why this issue occurs: There's currently no check to see if the price was manually changed. This leads to the permanent application of pricelist unit prices and disobeys inline pricing. Bugfix-6485742
Customers can no longer set optional products on a sales order to zero or negative quantities through the portal. This prevents invalid order lines and keeps quantity adjustments that may reduce an order under salesperson control.
Original PR description
Since the fusion of `sale.order.option` model into `sale.order.line` model, the optional products (editable from portal) lines are not deleted when reaching a quantity of 0 or below. This could allow some customers to set negative quantities, which makes no sense as it's only something that should be set by the salesman if necessary. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fixes a compatibility issue that prevented documents from being signed when running Odoo on Python 3.13 or newer. The PDF handling layer now supports the expected page box fields, allowing the signing flow to complete normally.
Original PR description
On Python 3.13 and above, `requirements.txt` installs `pypdf` instead of `PyPDF2`: ``` PyPDF2==2.12.1 ; python_version > '3.10' and python_version < '3.13' # (Noble and below) PyPDF==5.4.0 ;…
On Python 3.13 and above, `requirements.txt` installs `pypdf` instead of `PyPDF2`:
```
PyPDF2==2.12.1 ; python_version > '3.10' and python_version < '3.13' # (Noble and below)
PyPDF==5.4.0 ; python_version >= '3.13' # (Trixie)
```
so `odoo.tools.pdf` resolves to the `_pypdf` backend. That backend back-fills the PyPDF2 1.x camelCase API onto pypdf so callers do not have to branch on the installed library, but the alias set is incomplete in two ways:
- `cropBox` is missing entirely, while `mediaBox` is aliased.
- `mediaBox` is a getter-only property, so assigning to it raises.
## Impact
`sign/models/sign_document.py` needs all three forms, and dies on the first:
```python
box = page.cropBox if page.get('/CropBox') else page.mediaBox # 436
...
overlay_page.mediaBox = page.mediaBox # 444
if page.get("/CropBox"):
overlay_page.cropBox = page.cropBox # 446
```
```
File ".../sign/models/sign_document.py", line 436, in render_document_with_items
box = page.cropBox if page.get('/CropBox') else page.mediaBox
AttributeError: 'PageObject' object has no attribute 'cropBox'
```
`render_document_with_items` is reached from `sign_request_item.sign` → `_post_fill_request_item` → `_send_completed_documents` → `_generate_completed_documents`, so **signing any document fails on Python 3.13+**.
Fixing the shim rather than the caller keeps the design the module already follows: `_pypdf2_2.py` does the same normalisation for the 2.x backend, and `sign` is written against the camelCase API on purpose.
## Why CI does not catch it
The test that covers exactly this branch, `sign/tests/test_sign_request.py::test_origin_offset_translation`, patches the backend by module path:
```python
with patch('PyPDF2._page.PageObject.add_transformation', create=True) as mock_add, \
patch('PyPDF2._page.PageObject.cropBox', new_callable=PropertyMock, return_value=offset_box):
```
On 3.13+ `PyPDF2` is not installed at all, so the target never resolves and the branch is never exercised. A follow-up on the enterprise side should patch through the backend `odoo.tools.pdf` resolved instead of naming `PyPDF2`; that change is what would keep this from regressing.
## Verification
Reproduced and fixed on Python 3.14.4 with pypdf 5.4.0, running the exact `sign_document.py` sequence:
```
L436 read cropBox/mediaBox -> (0.0, 0.0)
L444/L446 writes -> OK
round-trip cropbox: (0.0, 0.0)
```
Before the patch, line 436 raises the `AttributeError` above.
## Forward-port
Not needed. On `master` the monkeypatch block is gone and the remaining shims carry `@deprecated("PyPDF2 1.x compatibility shims are deprecated, switch to modern API")`, so callers there are expected to move to the modern API rather than lean on these aliases. This is a 19.0-only stable-branch fix.This fixes an accounting issue where undoing a payment reconciliation could place the reversing cash basis tax entry in today’s period instead of the original tax period. Tax reports now correctly cancel the original and reversing amounts in the same period, reducing reporting discrepancies.
Original PR description
When unreconciling a payment from an invoice with a cash basis tax, the tax cash basis (CABA) entry is reversed. The reversal is supposed to land in the same period as the origin entry so the tax…
When unreconciling a payment from an invoice with a cash basis tax, the tax cash basis (CABA) entry is reversed. The reversal is supposed to land in the same period as the origin entry so the tax report nets to zero for that period. Steps to reproduce: - Enable cash basis and create a cash basis tax (exigibility on payment) - Post an invoice dated in the past with that tax - Reconcile a bank statement line to the invoice - Resequence the cash basis entry so the month is dropped from the name (CABA/08/2026/0001 -> CABA/2026/0001) - Unreconcile the statement line Issue: The reversal CABA entry created on the last unreconcile is dated today instead of the origin entry's month. In the tax report the original tax amount stays in the statement's month while the reversal amount appears in the current month, so the two no longer cancel out. Analysis: While under a monthly journal sequence a past date returns the last day of that month, under a yearly sequence a past date within the current year returns the latter between the move date and today, moving the reversal out of the origin period. opw-6301553 Forward-Port-Of: odoo/odoo#284408 Forward-Port-Of: odoo/odoo#281250
This fix stops users from deleting a card's cover image in a way that leaves the page editor in an inconsistent state. It helps prevent confusing editor behavior and avoids an error that could occur when using the Cover Image options.
Original PR description
It was possible to remove the image inside a card cover while keeping the figure wrapper. The card option would then still consider that there was a cover image even though the image was gone, which could also lead to a traceback. Steps to reproduce: - Insert the `s_three_columns` snippet - Click on the image of one card - Either press "Enter", "Delete", "Backspace" - Hover the "Cover Image" options => The image is removed but the `<figure>` is still there, so the option is still considered active (leading to a traceback) task-6081728 Forward-Port-Of: odoo/odoo#280086
This fixes a display issue in the website builder where resize controls could be hidden behind the sidebar when editing animated page elements. Users can now clearly see and resize selected content, making page editing more reliable.
Original PR description
Steps to reproduce: - Drop a few snippets to make the page scrollable - At the bottom, drop the `s_three_columns` snippet - Click on the last Card - Add an animation "onScroll" (Effect - Slide, Intensity - 100) - Scroll top slightly to hide a part of the card behind the sidebar => The resize overlay is partially hidden The elements `.hb-row` have a z-index of 2, so they appear in front of the overlay which has a z-index of 1. It was decided to fully show the overlay to allow resizing. Keeping the overlay visible in front of the sidebar also allow the user to see where animated element is. task-6476269 Forward-Port-Of: odoo/odoo#282796
Invoice printing no longer fails when an Argentine company partner has a VAT number that is valid in another country but not as an Argentine CUIT. This prevents an error screen and allows users to continue printing invoices reliably.
Original PR description
When printing an invoice, a traceback will occur if the company's partner has an invalid CUIT. Steps to reproduce the error: - Install ``l10n_ar_edi`` module with demo data - Switch to ``(AR)…
When printing an invoice, a traceback will occur if the company's partner has an invalid CUIT. Steps to reproduce the error: - Install ``l10n_ar_edi`` module with demo data - Switch to ``(AR) Exento`` Company - Go to Invoicing > Configuration > Journals > Open ``Ventas Preimpreso`` journal > ARCA POS System: ``Electronic Invoice - Web Service`` > Save - Create a new invoice with ``ADHOC SA`` partner > Confirm the invoice - Open the ``(AR) Exento`` partner and set the VAT to ``BE0477472701`` - Open the Invoice > print Traceback: ```py ValueError: invalid literal for int() with base 10: 'BE0477472701' ``` After this [commit], companies outside the EU can use European VAT numbers. Consequently, an Argentine partner can have a CUIT number such as ``BE0477472701``, which is valid as a Belgian VAT number but not as a CUIT. When printing the invoice, the ``l10n_ar_vat`` field is computed from the partner's VAT and its value is passed to ``int()``, causing a traceback at the following line: https://github.com/odoo/enterprise/blob/d55486866d09f8aa87c2003dab722cfa323068b4/l10n_ar_edi/models/account_move.py#L138 [commit]: https://github.com/odoo/odoo/commit/a2afe3292e1cd0a4f339dc47707e469653d13ea0 Enterprise PR: https://github.com/odoo/enterprise/pull/127820 sentry-7666042143 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282454
This fix ensures costs linked to projects are correctly matched to the related sales order for reinvoicing, even when projects share the same analytic account or a bill line uses multiple analytic accounts. This helps prevent missed reinvoicing opportunities and keeps customer billing more accurate.
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#277110Delivery fee lines are now excluded from the Invoiced not Delivered report because they are not physical items to deliver. This prevents accounting teams from seeing incorrect outstanding delivery entries and improves the accuracy of revenue review data.
Original PR description
Issue: --- Delivery lines are included in `invoiced not delivered` report, which is wrong as delivery lines are not deliverable. Steps: 1- Create a SO with a good product and add a delivery line. Set the product line as delivered and create an invoice. 2- Open accounting, and from review tab, open `Invoiced not Delivered`. As you see, delivery lines are included in the report. Fix: --- On stable we could fix it inside `_get_accrual_domain` by checking if `delivery` is installed. On master we need to implement a solution to be able to differentiate the lines that won't be delivered. opw-6360894
The HTML editor now converts tables with merged rows or columns into regular tables when content is inserted or pasted. This prevents broken table behavior and keeps editing features working consistently for users.
Original PR description
Description of the issue this PR addresses: We don't support colspan/rowspan in the editor, so tables containing them can break other functionality that assumes a rectangular grid (equal cell count per row). This PR expand any rowspan/colspan into individual cells on insert. opw-6347233 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283840 Forward-Port-Of: odoo/odoo#281114
This fixes an issue where unsaved translation text could disappear when a user dragged the translation dialog. Users can now reposition the dialog without losing work they have just entered, improving reliability when editing multilingual content.
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
Fixed an issue where printing an Argentine electronic invoice could fail if the company's tax ID was incorrectly formatted. The change prevents the invoice print process from crashing, helping users continue their invoicing workflow more reliably.
Original PR description
When printing an invoice, a traceback will occur if the company's partner has an invalid CUIT. Steps to reproduce the error: - Install ``l10n_ar_edi`` module with demo data - Switch to ``(AR)…
When printing an invoice, a traceback will occur if the company's partner has an invalid CUIT. Steps to reproduce the error: - Install ``l10n_ar_edi`` module with demo data - Switch to ``(AR) Exento`` Company - Go to Invoicing > Configuration > Journals > Open ``Ventas Preimpreso`` journal > ARCA POS System: ``Electronic Invoice - Web Service`` > Save - Create a new invoice with ``ADHOC SA`` partner > Confirm the invoice - Open the ``(AR) Exento`` partner and set the VAT to ``BE0477472701`` - Open the Invoice > print Traceback: ```py ValueError: invalid literal for int() with base 10: 'BE0477472701' ``` The issue occurs because when the partner's identification type is CUIT, At [1] ``_run_check_identification()`` method does not include partners whose identification type has ``is_vat=True``. As a result, CUIT is not validated by ``_run_check_identification()`` method in ``l10n_ar`` module at [2]. So, partner's ``l10n_ar_vat`` field can be computed as ``BE0477472701``. Passing this value to ``int()`` raises the traceback during invoice printing at below line. https://github.com/odoo/enterprise/blob/d55486866d09f8aa87c2003dab722cfa323068b4/l10n_ar_edi/models/account_move.py#L138 [1]:https://github.com/odoo/odoo/blob/c2a39085ba0fbcf8a0e6a55228191e764499caea/addons/l10n_latam_base/models/res_partner.py#L24-L30 [2]:https://github.com/odoo/odoo/blob/c2a39085ba0fbcf8a0e6a55228191e764499caea/addons/l10n_ar/models/res_partner.py#L55-L65 Community PR: https://github.com/odoo/odoo/pull/282454 sentry-7666042143 Forward-Port-Of: odoo/enterprise#127820
Changing a project’s visibility no longer fails when the project folder contains document shortcuts. This keeps project access updates working as expected while still preventing users from changing shortcut access directly.
Original PR description
Changing a project's visibility fails when its documents folder contains a shortcut. The visibility change is never applied and the following error is raised: "You can not update the access of a…
Changing a project's visibility fails when its documents folder contains a shortcut. The visibility change is never applied and the following error is raised: "You can not update the access of a shortcut, update its target instead." ### Reproduction steps - Create a project and add a document to its folder. - Create another document outside the project's folder. - Create a shortcut to that document in the project's folder. - Change the project's visibility. ### Cause Changing a project's visibility updates the access rights of its folder and documents together. The shortcut access check is meant to reject operations performed only on shortcuts. However, reading `shortcut_document_id` on a recordset returns the shortcut targets found across that recordset. Therefore, the presence of a single shortcut makes the check reject the whole operation. This prevents regular documents and the project folder from having their access updated. ### Fix Only reject access updates when all records involved are shortcuts. This preserves the protection against changing shortcut access directly while allowing project access updates to include shortcuts alongside regular documents and folders. opw-6472637 Forward-Port-Of: odoo/enterprise#128756
Swedish ISO20022 batch payments now generate files that truly match the selected pain.001.001.09 format. This prevents compliant payment files from being rejected by banks when companies use the Swedish outgoing payment method.
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
Projects that contain document shortcuts can now have their visibility changed without triggering an error. This removes a frustrating blocker for teams managing project access, especially when project folders include many shortcuts.
Original PR description
A project with a shortcut cannot change its visibility. STR: - Create a project with and add a document to its folder - Create a document in another folder - Create a shortcut to that document in the project folder - Change the project's visibility Current behavior before PR: An error is raised and the project's visibility doesn't change: > You can not update the access of a shortcut, update its target instead. That blocks the project visibility change unless all the project folder shortcuts are moved away to somewhere else (pretty unconvenient if there are many of them) Desired behavior after PR is merged: The project's visibility changes successfully. https://www.loom.com/share/160cecf57fa440e5a57b09b74cab7c73 cc @moduon MT-15648 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This change restores the previous GIF resizing approach because the newer version caused slowdowns and memory errors when pages displayed multiple GIFs. It helps keep views such as Kanban pages responsive and reduces the risk of crashes or failed image loading.
Original PR description
Revert commit d9fae40571c4f10c17fe00efc087cb25b30b85ab as it's slow on odoo.com and the call to `frame.copy()` is raising a MemoryError when loading a KanbanView with multiple gifs. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284479
This update fixes an internal automated test for mail mentions so it waits for the right suggestion list instead of being interrupted by unrelated status updates. It helps keep Odoo's validation pipeline stable without changing the experience for end users.
Original PR description
Before this commit, the test "select @ mention from the suggestion list being filtered" could fail on runbot, on the check that follows the first "@": Failed to find 2 of ".o-mail-Composer-suggestion" (Timeout of 10 seconds). Found 0 instead. This happens because the test holds a render open on ImStatus, a component the member list renders as well as the composer. The composer tells the server that the user is typing, the bus sends the status back, and the member list re-renders its ImStatus with another class. The hold catches that render, the one that also brings the suggestions on screen. This commit gives the children of NavigableList an inNavigableList environment flag, and holds the render only on an ImStatus that has it. https://runbot.odoo.com/odoo/error/946282 Forward-Port-Of: odoo/odoo#284484
Sending or replying to chatter messages on tax returns will no longer incorrectly change their status to Paid. Payment finalization is now limited to the intended tax payment instructions flow, reducing the risk of inaccurate tax return tracking.
Original PR description
Before this fix: Replying to or sending a message from the chatter of a tax return could incorrectly change its state to Paid. This happened because action_send_mail() automatically called _action_finalize_payment() for account.return records. After this fix: Payment finalization only happens when the composer is opened from the tax payment instructions flow. Normal chatter messages and replies will no longer change the tax return state to Paid. task-6469536
Turkish e-invoices are now checked before sending to Nilvera to catch missing taxes on invoice lines, helping users avoid rejected submissions. The update also improves product-related warnings so note and section lines are not incorrectly flagged.
Original PR description
Nilvera does not accept invoices with lines that do not have taxes, so we added a valiation check for sending the invoice to warn the user. Additionally, we raise a warning when a line does not have a product and has an empty CTSP. However, if the line is a note or section, this warning should not be triggered. task-6404409 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
Deleting a project no longer incorrectly moves folders from archived projects to the trash. This protects documents linked to archived projects so teams do not lose access to files that still belong to existing projects.
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#127217This fixes a payroll attendance issue where early or batch-created attendance records could accidentally lose their link to the correct work entry. The cleanup now only reviews relevant overlapping entries, helping keep attendance and payroll data accurate.
Original PR description
When an early attendance starts on the previous UTC day, the cleanup uses full UTC days as boundaries. This can include an unrelated work entry and remove its attendance link. Use the generated work entries as cleanup boundaries so only entries that can overlap the new entries are considered. opw-6412221 Forward-Port-Of: odoo/enterprise#129059 Forward-Port-Of: odoo/enterprise#127040
Invoice sequence gap warnings now check each suffix-based sequence separately. This prevents invoices from being incorrectly highlighted as having numbering gaps when similarly numbered invoices with different suffixes exist, improving confidence in accounting sequence checks.
Original PR description
### Issue: When moves share the same `sequence_number` in a journal but have different suffixes (e.g. `INV/2026/00010` and `INV/2026/00010A`), the `made_sequence_gap` flag was incorrectly set ###…
### Issue: When moves share the same `sequence_number` in a journal but have different suffixes (e.g. `INV/2026/00010` and `INV/2026/00010A`), the `made_sequence_gap` flag was incorrectly set ### Cause: `_update_sequence_made_gap`, introduced in commit https://github.com/odoo/odoo/commit/17893089e8b21c0ecab5e61ed8e2c33f3731b3ac selects the previous and next moves ordered by `sequence_number` without filtering by suffix This causes two issues: - Moves from different suffix sequences are used as neighbors, leading to incorrect gap detection - Duplicate `sequence_number` values across suffixes are not accounted for, so only one move is considered per number ### Steps to reproduce: - Install `account` - Post 12 invoices to get a sequence up to `INV/2026/00012` - Reset `INV/2026/00012` to draft, rename it to `INV/2026/00010A` - Reset `INV/2026/00010A` to draft, rename it to `INV/2026/00009A` and confirm Before the fix: `INV/2026/00011` is red Expected: `INV/2026/00011` should not be red because `INV/2026/00010` exists - Delete `INV/2026/00009` Before the fix: `INV/2026/00010` is red Expected: `INV/2026/00010` should be red (gap in no-suffix sequence) - Reset `INV/2026/00009A` to draft and confirm it again Before the fix: `INV/2026/00010` is not red Expected: `INV/2026/00010` should still be red (different suffix) ### Notes: Suffix changes are treated as distinct sequences following the same gap rules as any other sequence This was agreed with R&D — the gap flag is meant to signal inconsistencies within a sequence, not across suffixes opw-6454823
This fix prevents an error when users create or edit partner-related tasks and change the project field. It keeps the workflow stable for teams using Timesheets, Sales, Projects, and Studio customizations.
Original PR description
Steps to reproduce: -------------------------------------- 1. Install `sale_timesheet` and studio modules 2. Go to any partner record: * Add a new page from the studio * In the created page add…
Steps to reproduce:
--------------------------------------
1. Install `sale_timesheet` and studio modules
2. Go to any partner record:
* Add a new page from the studio
* In the created page add `task_ids` field and close studio
3. Go to Created Page and click on Add a line for new task
4. In the dialog, change the Project field.
Observation:
--------------------------------------
Traceback occurs
```
TypeError: unhashable type: 'list' in _compute_last_sol_of_customer (sale_timesheet/models/project_task.py)
```
Issue:
--------------------------------------
When you're on a form view and change a field (like `project_id`), the web client sends an onchange RPC to the server. The server doesn't work with the real database records during this process, instead, it creates virtual (in-memory) snapshots of the form data using `new()`. These virtual records always have `NewIds`.
Then in `_get_last_sol_of_customer_domain()`
https://github.com/odoo/odoo/blob/ea58c5b1c3efb609799fba8fb0782af0d7a65bb8/addons/sale_timesheet/models/project_task.py#L105
And `DomainCondition.checked()` converts NewId values to `[]`, resulting domain as `('order_partner_id', 'in', [])` https://github.com/odoo/odoo/blob/ea58c5b1c3efb609799fba8fb0782af0d7a65bb8/odoo/orm/domains.py#L833-L836
The `[]` is a Python list, which is unhashable. When `tuple()` is called on the Domain, `DomainCondition.__iter__` yields the leaf as `(field, operator, value)` preserving the list value. The resulting outer tuple contains a list element, making the entire tuple unhashable and crashing
Using the Domain object directly as a dict key is also not viable, because `DomainCondition.__hash__` calls `hash(self.value)`, which likewise fails on list values.
Solution:
--------------------------------------
The fix uses `repr(domain)` as the cache key. `repr()` always returns a hashable string and produces consistent output for identical domains, preserving the deduplication behavior. The Domain object itself is still passed to `search()` unchanged.
opw-6297102This fixes an issue where some grouped data queries could return duplicated or incomplete results when the same grouping was requested more than once. The change prevents crashes in callers that rely on the expected result format and improves reliability of aggregated business data.
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-prThis update replaces an older loop style with the preferred clearer wording in core server-related code. It helps automated quality checks pass and keeps the codebase consistent, with no expected impact on users or business workflows.
Original PR description
Ruff checks on runbot flagged `while 1:` Preferred syntax is to use `while True` [UP048](https://docs.astral.sh/ruff/rules/while-one) runbot-945983 Forward-Port-Of: odoo/odoo#284490 Forward-Port-Of: odoo/odoo#283962
A small configuration error in Accounting report protections was fixed. The change ensures two important invoice-related reports are correctly protected from accidental deletion, helping preserve expected system behavior.
Original PR description
On `ir.actions.report` we want to block the unlinking of specific reports in odoo. However, when the list was created a comma was missed between `action_account_original_vendor_bill` and `account_invoice_without_payment` which means we were actually protecting against people unlinking `action_account_original_vendor_billaccount_invoice_without_payment`. Adding in that comma will allow these two records to be properly protected. task-none Forward-Port-Of: odoo/odoo#283323
The Brazilian AvaTax sales module now includes the dependency it needs to install reliably in special installation scenarios. This prevents setup failures caused by missing sales tax fields, helping businesses using Brazilian localization avoid blocked deployments.
Original PR description
When installing l10n_br_avatax_sale with --skip-auto-install, you'll get an error about the l10n_br fields listed in views/sale_order_views.xml, because these fields don't fully exist without sale_external_tax. This happens because they're defined on a mixin, which is an abstract model. Abstract models only add their fields to a model that actually lists them in `_inherit`. sale.order should list this mixin, but currently doesn't. Adding that dependency is an unstable fix, so it will be added in master (20.0, or 20.1) runbot-237866 Forward-Port-Of: odoo/enterprise#128142
This fix prevents duplicate emails from being sent when employees submit expenses across multiple companies. It helps keep expense communication cleaner and avoids confusion for approvers and employees.
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