Daily updates from Odoo
Wednesday, August 12, 2026
14 changes · 18.0
Enhancements to existing features
WIP --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Original PR description
WIP --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Resolved issues and error corrections
Fixed an issue where exporting the Deferred Revenue Report to XLSX could fail when the report included annotations. This helps accounting users reliably export annotated reports without encountering a server error.
Original PR description
**Steps to reproduce:** * Install the **Accounting** module. * Unhide the **Start Date** and **End Date** fields on invoice lines. * Create and post a customer invoice with deferred dates. * Go to…
**Steps to reproduce:** * Install the **Accounting** module. * Unhide the **Start Date** and **End Date** fields on invoice lines. * Create and post a customer invoice with deferred dates. * Go to **Accounting → Reports → Deferred Revenue Report**. * Add an annotation to a deferred revenue line by clicking the **annotate** from three dots next to the account. * Export the report in **XLSX** format. **Observed behavior:** * The export fails with a server error: `UnboundLocalError: cannot access local variable 'annotations_x_offset' where it is not associated with a value` **Cause:** * The variable `annotations_x_offset` is assigned inside the `for header_level_index, header_level in enumerate(options['column_headers'])` loop, which writes the "Annotations" column header for each header level. * The Deferred Revenue Report produces an empty `column_headers` list, so the loop body never executes and `annotations_x_offset` is never assigned. * When the code later tries to write annotation data for each report line, it references the unassigned variable, causing Python to raise `UnboundLocalError`. **Fix:** * Introduce a boolean flag `annotations_header_written = False` before the header loop to explicitly track whether the "Annotations" column header has already been written. * Inside the header loop, set `annotations_header_written = True` after writing the header. * After writing all individual column headers (where `x_offset` already points to the first free column after all data columns), add a fallback: if `report_annotations` is set but `annotations_header_written` is still `False`, assign `annotations_x_offset` from the current `x_offset` and write the "Annotations" header. opw-6354473
The selection options for l10n_id_coretax_add_info_07/08 and l10n_id_coretax_facility_info_07 were swapped: the "additional information" fields showed facility stamp text and vice versa. Also add the UoM codes and new kode faktur 07 additional info/facility stamp options published by DJP on 2026-01-26. task-6434742 Forward-Port-Of: odoo/odoo#281096
Original PR description
The selection options for l10n_id_coretax_add_info_07/08 and l10n_id_coretax_facility_info_07 were swapped: the "additional information" fields showed facility stamp text and vice versa. Also add the UoM codes and new kode faktur 07 additional info/facility stamp options published by DJP on 2026-01-26. task-6434742 Forward-Port-Of: odoo/odoo#281096
test fix task-6233756 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
Original PR description
test fix task-6233756 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
## Problem When rendering PDF invoices that contain **section** or **note** lines, the `l10n_gcc_invoice.arabic_english_invoice` QWeb template raises: ``` TypeError: argument of type 'bool' is not iterable ``` This happens because `account.move.line` records of type `line_section` or `line_note` have `name = False`. The template evaluates `arabic_name not in line.name` (and the same for `english_name`), which fails because Python cannot apply the `in` operator on a boolean value. ## Fix Add
Original PR description
## Problem When rendering PDF invoices that contain **section** or **note** lines, the `l10n_gcc_invoice.arabic_english_invoice` QWeb template raises: ``` TypeError: argument of type 'bool' is not…
## Problem When rendering PDF invoices that contain **section** or **note** lines, the `l10n_gcc_invoice.arabic_english_invoice` QWeb template raises: ``` TypeError: argument of type 'bool' is not iterable ``` This happens because `account.move.line` records of type `line_section` or `line_note` have `name = False`. The template evaluates `arabic_name not in line.name` (and the same for `english_name`), which fails because Python cannot apply the `in` operator on a boolean value. ## Fix Add a `line.name and` guard before each `not in` check: ```xml <!-- Before --> <span t-if="arabic_name not in line.name" .../> <span t-if="(english_name != arabic_name) and (english_name not in line.name)" .../> <!-- After --> <span t-if="line.name and arabic_name not in line.name" .../> <span t-if="line.name and (english_name != arabic_name) and (english_name not in line.name)" .../> ``` ## Steps to reproduce 1. Install `l10n_gcc_invoice` on an Odoo 16.0 instance. 2. Create a customer invoice and add a **Section** line. 3. Print/preview the invoice PDF. 4. Observe `Internal Server Error` / `TypeError: argument of type 'bool' is not iterable`. Forward-Port-Of: odoo/odoo#267447 Forward-Port-Of: odoo/odoo#267147
Issue: ```python In [14]: receiver._get_peppol_proxy_endpoint('/2/get_services') Out[14]: '/api/peppol//2/get_services' In [15]: receiver._get_peppol_proxy_endpoint('2/get_services') Out[15]: '/api/peppol/2/get_services' ``` this raises: ```bash [ERROR] odoo.addons.account_peppol_response.models.account_edi_proxy_user Auto registration of peppol services for module: account_peppol_response failed on the user: ***, with exception: Invalid signature for request. This might be due to
Original PR description
Issue:
```python
In [14]: receiver._get_peppol_proxy_endpoint('/2/get_services')
Out[14]: '/api/peppol//2/get_services'
In [15]: receiver._get_peppol_proxy_endpoint('2/get_services')
Out[15]: '/api/peppol/2/get_services'
```
this raises:
```bash
[ERROR] odoo.addons.account_peppol_response.models.account_edi_proxy_user
Auto registration of peppol services for module: account_peppol_response failed on the user: ***, with exception: Invalid signature for request. This might be due to another connection to odoo Access Point server. It can occur if you have duplicated your database
```
OPW-6431279
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-prSteps to Reproduce: - Go to Settings → Technical → Sequences & Identifiers → Sequences. - Open the sale.order sequence and note the current Next Number. - Go to Sales → Quotations → Import (via the list view import action). - Upload a .xlsx or .csv file with quotation/order data. - Click Test (without clicking Import). - Go back to the sequence configuration and check Next Number again. - Repeat click Test again two or three times. - Next number is increasing. Issue: Each click of
Original PR description
Steps to Reproduce: - Go to Settings → Technical → Sequences & Identifiers → Sequences. - Open the sale.order sequence and note the current Next Number. - Go to Sales → Quotations → Import (via the…
Steps to Reproduce: - Go to Settings → Technical → Sequences & Identifiers → Sequences. - Open the sale.order sequence and note the current Next Number. - Go to Sales → Quotations → Import (via the list view import action). - Upload a .xlsx or .csv file with quotation/order data. - Click Test (without clicking Import). - Go back to the sequence configuration and check Next Number again. - Repeat click Test again two or three times. - Next number is increasing. Issue: Each click of Test advances the sale.order sequence's Next Number, even though no record is actually created or kept. After a few Test clicks, the sequence has skipped several numbers before a single real order has been imported — leaving permanent gaps in order numbering (e.g. SO0010, SO0011 skipped, next real order becomes SO0012). Cause: Test runs the import inside a database savepoint that is rolled back afterward (dry run). That create() call on sale.order still triggers ir.sequence.next_by_code(), which for standard-implementation sequences calls PostgreSQL's nextval(). nextval() is explicitly non-transactional in PostgreSQL — it is never undone by a rollback, even inside a savepoint — so the number is permanently consumed regardless of how many times Test is clicked or whether Import is ever pressed. FIX: Add a dry_run context flag, set when the import wizard is in Test mode, and check it in ir.sequence._next_do() to preview the next value instead of drawing a real one. https://github.com/odoo/odoo/issues/241837
When an incoming email has a `.eml` file attached, the body of the attached email is used as the body of the message. Records created from the alias (Helpdesk tickets, leads, tasks, ...) end up with the wrong content in their description. ### Steps to reproduce 1. Send an email with body "Some nice content" and a `.eml` file attached to a Helpdesk alias. 2. Open the new ticket: the description shows the content of the `.eml`, not "Some nice content". ### Cause `MailThread._message_p
Original PR description
When an incoming email has a `.eml` file attached, the body of the attached email is used as the body of the message. Records created from the alias (Helpdesk tickets, leads, tasks, ...) end up with…
When an incoming email has a `.eml` file attached, the body of the attached email is used as the body of the message. Records created from the alias (Helpdesk tickets, leads, tasks, ...) end up with the wrong content in their description. ### Steps to reproduce 1. Send an email with body "Some nice content" and a `.eml` file attached to a Helpdesk alias. 2. Open the new ticket: the description shows the content of the `.eml`, not "Some nice content". ### Cause `MailThread._message_parse_extract_payload` goes through every part of the email with `email.Message.walk()` and decides if each part is body text or an attachment. The `.eml` file arrives as a part of type `message/rfc822`. The method sees the filename and adds it to the attachments. But a `message/rfc822` part is itself a full email, so `walk()` keeps going and also returns the parts inside it. These inner parts have no filename, so the method treats them as body parts of the outer message and end up replacing the real body with the content of the attached email. The inner text/plain part is also kept as an extra anonymous "attachment" file alongside the real `.eml`. Conceptually, an attached email is opaque content, just like an attached PDF or ZIP. It is not a sub-tree of the outer email's parts. The parser should treat it the same way it treats any other attachment: read its bytes, store them, and not look inside. ### Fix Replace `walk()` with a small iterator that walks multipart containers but stops at `message/rfc822` boundaries. The `.eml` is still captured as an attachment, but its inner parts are no longer visited, so the body is no longer overwritten and no anonymous "attachment" file is produced. opw-6089248
Before this commit: ----------- - When a product attribute had only one available value, it was not automatically selected for display types other than multi. After this commit: ------------ - Automatically select the attribute value when an attribute has a single available value and its display type is not multi, allowing the product to be added without any additional user interaction. Task-6327371
Original PR description
Before this commit: ----------- - When a product attribute had only one available value, it was not automatically selected for display types other than multi. After this commit: ------------ - Automatically select the attribute value when an attribute has a single available value and its display type is not multi, allowing the product to be added without any additional user interaction. Task-6327371
#### Description of the issue this PR addresses: - When reducing the number of columns or removing a column layout, empty columns were previously unwrapped like any other column. - As a result, columns containing only placeholder paragraphs contributed empty paragraphs to the resulting content, even though they did not contain any meaningful user content. #### Desired behavior after PR is merged: - Fully empty columns are discarded when they are removed. - Non-empty columns continue to be
Original PR description
#### Description of the issue this PR addresses: - When reducing the number of columns or removing a column layout, empty columns were previously unwrapped like any other column. - As a result, columns containing only placeholder paragraphs contributed empty paragraphs to the resulting content, even though they did not contain any meaningful user content. #### Desired behavior after PR is merged: - Fully empty columns are discarded when they are removed. - Non-empty columns continue to be merged as-is, preserving their content. - A single empty paragraph is still kept when all columns are empty to ensure the editor remains editable. - Rename `Remove columns` to `Remove column layout` and update its description to `Convert columns to regular content` to better reflect the operation. task-6296536 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Before this commit, deleting a record leaves its id in the relations of a record deleted before it in the same update, and reading one of those relations hands out an entry for a record that is gone. This happens because an update takes a deleted record out of the relations that hold it, but forgets it as soon as it is deleted. However, deleting a record is what queues the deletion of the records it holds: `channelMembers` carries `onDelete: (r) => r.delete()`, so the members of a thread are
Original PR description
Before this commit, deleting a record leaves its id in the relations of a record deleted before it in the same update, and reading one of those relations hands out an entry for a record that is gone. This happens because an update takes a deleted record out of the relations that hold it, but forgets it as soon as it is deleted. However, deleting a record is what queues the deletion of the records it holds: `channelMembers` carries `onDelete: (r) => r.delete()`, so the members of a thread are deleted once the thread is already forgotten, and their ids stay in its `onlineMembers`. This commit fixes the issue by keeping the records deleted by an update known until it ends.
Automatically update the ruff config to the latest version generated on runbot
Original PR description
Automatically update the ruff config to the latest version generated on runbot
# Introduction This PR fixes broken mention links linked to the fact that we replace strings without paying attention to the fact that some strings may contain others that we want to replace later. This affects both id's and names of records. See commit messages for more details. # How to reproduce - Create Contact A and then Contact B and either : - Contact B's id need to contain Contact A's id (e.g. Contact B id = 12; Contact A id = 1) - Contact B's name need to contain Contact
Original PR description
# Introduction This PR fixes broken mention links linked to the fact that we replace strings without paying attention to the fact that some strings may contain others that we want to replace later.…
# Introduction
This PR fixes broken mention links linked to the fact that we replace strings
without paying attention to the fact that some strings may contain others
that we want to replace later. This affects both id's and names of records.
See commit messages for more details.
# How to reproduce
- Create Contact A and then Contact B and either :
- Contact B's id need to contain Contact A's id (e.g. Contact B id = 12; Contact A id = 1)
- Contact B's name need to contain Contact A's name (e.g. Contact B name = ABC; Contact A name = AB)
- In a chatter create a message mentionning first Contact B and then Contact A
> Depending on the version, you might need to reload the page here
- Edit the message and save
# The issue
We see a broken mention in the chatter
# Cause
When saving an edited message, we give the raw body of the message (without the mention links) and the mentionend partners to `generateMentionsLinks` : https://github.com/odoo/odoo/blob/f9f605b1783d252d5e005bec50a2a72dd4ae0e13/addons/mail/static/src/utils/common/format.js#L152
This method's purpose is to replace the text links ("@Contact A") with actual html links. It does so by enumerating each partner given as an argument and replace the text mention with a placeholder :
https://github.com/odoo/odoo/blob/f9f605b1783d252d5e005bec50a2a72dd4ae0e13/addons/mail/static/src/utils/common/format.js#L158
It will then replace the placeholders with actual links : https://github.com/odoo/odoo/blob/f9f605b1783d252d5e005bec50a2a72dd4ae0e13/addons/mail/static/src/utils/common/format.js#L208-L218
The issue is that in both of those steps, we can try to replace a string that is contained
in another string we want to replace.
For exemple :
"string123 some text string12"
If we try to replace "string12" first, then we will select the wrong string :
"[string12]3 some text string12".
opw-6313748Miscellaneous changes
Allow resetting sent moves to draft. Ensures a rectificative flow exists or is created. Task: 6273211
Original PR description
Allow resetting sent moves to draft. Ensures a rectificative flow exists or is created. Task: 6273211