Daily updates from Odoo
Monday, August 3, 2026
24 changes · saas-18.3
Enhancements to existing features
SEPA direct debit batch validation has been optimized to handle large payment batches more efficiently. Businesses processing hundreds or thousands of direct debit payments should see noticeably faster validation and notification steps, reducing delays and timeouts.
Original PR description
- Replace the `id:recordset` aggregation in `_get_expiry_date_per_mandate()` with `date:max` to compute the latest payment date directly in SQL. - Render `email_from` for all payments in batch and cache the computed authors by sender email to avoid repeated partner lookups during SDD pre-notification. This reduces ORM/cache overhead when validating large SEPA batches containing thousands of payments. Measured on a production-sized database: | metric | before | after | factor | |--------|-------:|------:|-------:| | `_get_expiry_date_per_mandate` (500 payments) | 564 ms | 111 ms | ~5x | | `_send_after_validation` notification (500 payments) | 92.9 s | 55.7 s | ~1.7x | | `_get_expiry_date_per_mandate` (1000 payments) | 890 ms | 178 ms | ~5x | | `_send_after_validation` notification (1000 payments) | timed out (>159 s) | 108.9 s | completed | OPW-6377340 Forward-Port-Of: odoo/enterprise#125439
Forward-Port-Of: odoo/odoo#278655
Original PR description
Forward-Port-Of: odoo/odoo#278655
Resolved issues and error corrections
Guatemalan electronic invoice PDFs now match the official XML by showing 'CF' whenever the XML uses it. Placeholder tax IDs are treated as missing, and invoice limits are checked in the company currency so legal thresholds are applied consistently across currencies.
Original PR description
with this commit:- - Display 'CF' in the invoice PDF whenever the generated XML uses CF. - Treat placeholder VAT values such as '/', 'NA', and 'na' as missing VAT. - Compare the invoice total using the company currency instead of the document currency when evaluating the 2,500 threshold, ensuring the legal limit is applied consistently regardless of the invoice currency task-6305333 Forward-Port-Of: odoo/enterprise#120985
Swiss payroll calculations now use the correct 0.05 rounding directly, avoiding tiny precision differences that could affect monthly salary comparisons and declarations. This makes payroll results more consistent and prevents unnecessary changes from appearing in Swissdec ELM reporting tests and outputs.
Original PR description
In multiple places within l10n_ch_hr_payroll_elm we use float_round to a precision of 0.01 but then manually round to 0.05 precision. 1. Open a python terminal 2. Enter 1000 % 0.05 >= 0.025 3. See this results to true, even though it shouldn't Fix this by using float_round with a precision of 0.05 instead. https://github.com/odoo/enterprise/blob/7f9cd01ff3dd470b06ae176982fd042243be8f3c/l10n_ch_hr_payroll_elm/models/hr_payslip.py#L102-L105 The change to `ema_declaration.json` is needed as previously it was expected that there was a small difference between salary over the months due to the odd rounding (a difference of like 0.000000000001). Adding the new rounding makes the values equal and test_ema_declaration_2023_01 would fail due to changeSalary no longer being in the computed dict. All the way to master! opw-6322937 Forward-Port-Of: odoo/enterprise#121401
Follow-up filters are now only shown to invoicing and accounting users who have access to the related follow-up status information. This prevents other users from triggering access errors when viewing or filtering customer follow-up data.
Original PR description
Description of the issue this commit addresses: Follow-up filters are visible to users without access to the restricted followup status field. Using these filters queries journal items and raises an access error. --- Desired behavior after this commit is merged: This commit limits the follow-up filters to invoicing and accounting users, matching the access groups of the followup status field. --- runbot-[161825](https://runbot.odoo.com/odoo/error/161825)
When updating mail notifications during `mail.mail._send()`, a `SerializationFailure` raised while flushing the notification recordset leaves the current transaction in an aborted state. As `_send()` continues handling the exception, accessing fields: - https://github.com/odoo/odoo/blob/127f1316540ec6cc6880ea3515e6923ba3903bc7/addons/mail/models/mail_mail.py#L816 So, any subsequent SQL query fails with `InFailedSqlTransaction`, masking the original concurrency error. Avoid acces
Original PR description
When updating mail notifications during `mail.mail._send()`, a `SerializationFailure` raised while flushing the notification recordset leaves the current transaction in an aborted state. As `_send()`…
When updating mail notifications during `mail.mail._send()`,
a `SerializationFailure` raised while flushing the notification recordset leaves the current transaction in an aborted state.
As `_send()` continues handling the exception, accessing fields:
- https://github.com/odoo/odoo/blob/127f1316540ec6cc6880ea3515e6923ba3903bc7/addons/mail/models/mail_mail.py#L816
So, any subsequent SQL query fails with
`InFailedSqlTransaction`, masking the original concurrency error.
Avoid accesing to `mail.message_id` with aborted cursor, preserving the original `SerializationFailure`.
A regression test is added to simulate a concurrency failure during
`flush_recordset()` and verify that the cursor is no longer used dirty
The logger for the unittest without the fix is the following:
```log
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "odoo/addons/mail/models/mail_mail.py", line 719, in _send
notifs.flush_recordset(['notification_status', 'failure_type', 'failure_reason'])
File "<string>", line 3, in flush_recordset
File "unittest/mock.py", line 1139, in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "unittest/mock.py", line 1143, in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "unittest/mock.py", line 1204, in _execute_mock_call
result = effect(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/addons/test_mail/tests/test_message_post_concurrent.py", line 93, in mocked_mail_notification_flush_recordset
return original_flush_recordset(self, *vals, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/models.py", line 6788, in flush_recordset
self._flush(fnames)
File "odoo/odoo/models.py", line 6852, in _flush
model.browse(some_ids)._write_multi(vals_list)
File "odoo/odoo/models.py", line 4938, in _write_multi
self.env.execute_query(SQL(
File "odoo/odoo/api.py", line 993, in execute_query
self.cr.execute(query)
File "odoo/odoo/sql_db.py", line 371, in execute
res = self._obj.execute(query, params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
psycopg2.errors.SerializationFailure: could not serialize access due to concurrent update
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "odoo/addons/test_mail/tests/test_message_post_concurrent.py", line 107, in test_mail_send_dirty_cursor
mails.send()
File "odoo/addons/mail/models/mail_mail.py", line 652, in send
self.browse(batch_ids)._send(
File "odoo/addons/mail/models/mail_mail.py", line 818, in _send
mail.id, mail.message_id)
^^^^^^^^^^^^^^^
File "odoo/odoo/fields.py", line 1309, in __get__
self.compute_value(recs)
File "odoo/odoo/fields.py", line 1491, in compute_value
records._compute_field_value(self)
File "odoo/odoo/models.py", line 5302, in _compute_field_value
fields.determine(field.compute, self)
File "odoo/odoo/fields.py", line 113, in determine
return needle(records, *args)
^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/fields.py", line 710, in _compute_related
record[self.name] = self._process_related(value[self.related_field.name], record.env)
~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/models.py", line 7083, in __getitem__
return self._fields[key].__get__(self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/fields.py", line 1272, in __get__
recs._fetch_field(self)
File "odoo/odoo/models.py", line 4120, in _fetch_field
self.fetch(fnames)
File "odoo/addons/mail/models/mail_message.py", line 756, in fetch
return super().fetch(field_names)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/models.py", line 4158, in fetch
fetched = self._fetch_query(query, fields_to_fetch)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/models.py", line 4245, in _fetch_query
rows = self.env.execute_query(query.select(*sql_terms))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "odoo/odoo/api.py", line 993, in execute_query
self.cr.execute(query)
File "odoo/odoo/sql_db.py", line 371, in execute
res = self._obj.execute(query, params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
psycopg2.errors.InFailedSqlTransaction: current transaction is aborted, commands ignored until end of transaction block
```
Real error in production:
```log
2023-04-15 10:43:14,978 106451 ERROR my_db odoo.sql_db: bad query: UPDATE "mail_notification" SET "failure_reason" = "__tmp"."failure_reason"::text, "failure_type" = "__tmp"."failure_type"::VARCHAR, "notification_status" = "__tmp"."notification_status"::VARCHAR FROM (VALUES (4426629, 'Error without exception. Probably due to concurrent access update of notification records. Please see with an administrator.', 'unknown', 'exception')) AS "__tmp"("id", "failure_reason", "failure_type", "notification_status") WHERE "mail_notification"."id" = "__tmp"."id" ERROR: could not serialize access due to concurrent update
```
```log
2023-04-14 10:43:14,978 106451 ERROR my_db odoo.sql_db: bad query: UPDATE "mail_mail" SET "failure_reason"='Error without exception. Probably due do sending an email without computed recipients.',"headers"='{''X-SMTPAPI'': ''{"ip_pool": "Transactional"}'', ''X-Odoo-Objects'': ''sale.order-1436960''}',"state"='exception',"write_uid"=1,"write_date"=(now() at time zone 'UTC') WHERE id IN (2548540)
ERROR: current transaction is aborted, commands ignored until end of transaction block
```
Description of the issue/feature this PR addresses:
Current behavior before PR:
Desired behavior after PR is merged:
# UPDATE 2026-07-22
The reviewer requested to remove the large docstring
For record, the docstring was
```python
"""Reproduces a concurrency scenario where `mail_mail._send()` fails with a PSQL SerializationFailure after
flushing `mail.notification` records. After such a failure, the cursor is left in an aborted
(`InFailedSqlTransaction`) state, so any further SQL access (e.g. reading `mail.message_id` like
https://github.com/odoo/odoo/blob/127f1316540ec6cc68/addons/mail/models/mail_mail.py#L816)
would raise a new error masking the original SerializationFailure.
Setup:
- Uses a separate `cursor()` to create and commit a message with its `mail.mail` and `mail.notification`
records, so they are visible to a second, concurrent transaction.
Concurrency simulation:
- `MailNotification.flush_recordset` is patched so that, right before the real flush runs, a second cursor
updates the same `mail.notification` records (`failure_reason`). This forces PSQL to raise a
SerializationFailure when the original transaction tries to flush those rows.
Assertions:
- `SerializationFailure` is raised confirming the concurrency conflict.
- `mail_mail._send()` logs the expected error message containing the mail `id` and `message-id`
Cleanup: created records are unlinked in `finally`
"""
```
# UPDATE 2026-07-23
The reviewer requested to remove the unittest
For record, the unittest was
```diff
diff --git a/addons/test_mail/tests/test_message_post.py b/addons/test_mail/tests/test_message_post.py
index 53dd5b9eec52..46a3958a5bff 100644
--- a/addons/test_mail/tests/test_message_post.py
+++ b/addons/test_mail/tests/test_message_post.py
@@ -7,17 +7,21 @@ from datetime import datetime, timedelta
from freezegun import freeze_time
from itertools import product
from markupsafe import escape, Markup
+from psycopg2.errorcodes import SERIALIZATION_FAILURE as SERIALIZATION_FAILURE_CODE
+from psycopg2.errors import SerializationFailure
from unittest.mock import patch
-from odoo import tools
+from odoo import SUPERUSER_ID, api, tools
from odoo.addons.base.tests.test_ir_cron import CronMixinCase
-from odoo.addons.mail.tests.common import mail_new_test_user, MailCommon
+from odoo.addons.mail.models.mail_notification import MailNotification
+from odoo.addons.mail.tests.common import mail_new_test_user, MailCommon, MockEmail
from odoo.addons.test_mail.data.test_mail_data import MAIL_TEMPLATE_PLAINTEXT
from odoo.addons.test_mail.models.test_mail_models import MailTestSimple
from odoo.addons.test_mail.tests.common import TestRecipients
from odoo.api import call_kw
from odoo.exceptions import AccessError
-from odoo.tests import tagged
+from odoo.modules.registry import Registry
+from odoo.tests import TransactionCase, get_db_name, tagged
from odoo.tools import mute_logger, formataddr
from odoo.tests.common import users
@@ -2244,3 +2248,49 @@ class TestMessagePostLang(MailCommon, TestRecipients):
self.assertIn('html lang="es_ES"', email['body'])
else:
self.assertIn('html lang="en_US"', email['body'])
+
+
+@tagged('database_breaking')
+class TestMessagePostConcurrent(MockEmail, TransactionCase):
+ """Mail concurrency edge cases that require real, separately committed transactions
+ instead of the usual rollback-based TransactionCase isolation.
+ """
+
+ def test_mail_send_dirty_cursor(self):
+ """Reproduces SerializationFailure `mail_mail._send()` fails,
+ the cursor is left in an aborted state, so any further SQL access would raise a new error
+ (e.g. reading `mail.message_id` like
+ https://github.com/odoo/odoo/blob/127f1316540ec6cc68/addons/mail/models/mail_mail.py#L816)
+ """
+ original_flush_recordset = MailNotification.flush_recordset
+
+ def mocked_mail_notification_flush_recordset(self, *args, **kwargs):
+ with Registry(get_db_name()).cursor() as cr:
+ cr.execute('UPDATE mail_notification SET failure_reason = %s WHERE id IN %s', ('Forced Concurrent Update', tuple(self.ids)))
+ return original_flush_recordset(self, *args, **kwargs)
+
+ recs2unlink = []
+ with Registry(get_db_name()).cursor() as cr:
+ env = api.Environment(cr, SUPERUSER_ID, {})
+ partner = env.ref('base.user_admin').partner_id
+ try:
+ message = partner.message_post(body='Hello', message_type='comment', partner_ids=[partner.id], mail_auto_delete=False, force_send=False)
+ notifs = env['mail.notification'].search([('notification_type', '=', 'email'), ('mail_mail_id', 'in', message.mail_ids.ids)])
+ self.assertTrue(notifs)
+ mails = message.mail_ids
+ recs2unlink.extend([notifs, mails, message])
+ cr.commit()
+
+ mails = self.env[mails._name].browse(mails.ids)
+ with (
+ mute_logger('odoo.sql_db'), self.assertRaises(SerializationFailure) as exc, self.mock_mail_gateway(),
+ patch(f'{MailNotification.__module__}.{MailNotification.__name__}.flush_recordset', autospec=True, side_effect=mocked_mail_notification_flush_recordset),
+ self.assertLogs('odoo.addons.mail.models.mail_mail', level='ERROR') as log_capture,
+ ):
+ mails.send()
+ finally:
+ for rec2unlink in recs2unlink:
+ env[rec2unlink._name].browse(rec2unlink.ids).unlink()
+
+ self.assertEqual(exc.exception.pgcode, SERIALIZATION_FAILURE_CODE)
+ self.assertIn(f'Exception while processing mail with ID {mails.id} and Msg-Id \'{mails.message_id}\'.', [record.message for record in log_capture.records])
```
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#274089**PROBLEM** Before this PR, there was no way to handle invoices sent to clients depending from a JST/LGU (local government unit). This PR add support for it. opw-6095581 Forward-Port-Of: odoo/odoo#260367
Original PR description
**PROBLEM** Before this PR, there was no way to handle invoices sent to clients depending from a JST/LGU (local government unit). This PR add support for it. opw-6095581 Forward-Port-Of: odoo/odoo#260367
Issue: The MyInvois payload computed `cbc:PrepaidAmount` as `amount_total - amount_residual`, treating every reconciled payment as a prepayment. A payment made on or after the invoice date is an ordinary settlement, not a deposit, but the code recognized it as one anyway. The values reported for `PrepaidAmount` were wrong regardless of the invoice date, and for a fully paid invoice this also collapsed `PayableAmount` to 0.00, which LHDN rejects. Root Cause: LHDN only considers a reconciled
Original PR description
Issue: The MyInvois payload computed `cbc:PrepaidAmount` as `amount_total - amount_residual`, treating every reconciled payment as a prepayment. A payment made on or after the invoice date is an…
Issue: The MyInvois payload computed `cbc:PrepaidAmount` as `amount_total - amount_residual`, treating every reconciled payment as a prepayment. A payment made on or after the invoice date is an ordinary settlement, not a deposit, but the code recognized it as one anyway. The values reported for `PrepaidAmount` were wrong regardless of the invoice date, and for a fully paid invoice this also collapsed `PayableAmount` to 0.00, which LHDN rejects. Root Cause: LHDN only considers a reconciled payment a genuine deposit if it was received before the invoice date. The code applied no date condition at all, so any payment reconciled against the invoice was added to `PrepaidAmount` and reduced `PayableAmount` accordingly. Fix: Only sum reconciled payment partials whose date is strictly earlier than the invoice date as prepaid, so regular payments are no longer misclassified as deposits. As a safety net, if the valid prepaid sum still covers the full invoice amount (e.g. a full advance payment), reset it to 0 so `PayableAmount` always reflects the full amount_total instead of being reported as 0. Also omit the `PrepaidPayment` node entirely when there is no genuine prepayment, rather than emitting it with a 0.00 amount. [Task-6404296](https://www.odoo.com/odoo/my-tasks/6404296) Forward-Port-Of: odoo/odoo#279035 Forward-Port-Of: odoo/odoo#278010
Use "VAT" instead of "TAX" for the default UK tax groups. Also set "Subtotal" as their preceding subtotal label so that it replaces "Untaxed Amount". task-[6413495](https://www.odoo.com/odoo/project/967/tasks/6413495) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#278351
Original PR description
Use "VAT" instead of "TAX" for the default UK tax groups. Also set "Subtotal" as their preceding subtotal label so that it replaces "Untaxed Amount". task-[6413495](https://www.odoo.com/odoo/project/967/tasks/6413495) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#278351
Issue: Outstanding credits/debits from a branch don't appear on the main company and inversely. Cause : From odoo/odoo#255875 outstanding credits/debits are limited by company to prevent different company issues on validation. However, this error is raised for `account.move` having different root companies. Which allow move from different branches of the same company. Steps to reproduce: - create a company and a branch - in the main company create a customer payment and valid it - in
Original PR description
Issue: Outstanding credits/debits from a branch don't appear on the main company and inversely. Cause : From odoo/odoo#255875 outstanding credits/debits are limited by company to prevent different company issues on validation. However, this error is raised for `account.move` having different root companies. Which allow move from different branches of the same company. Steps to reproduce: - create a company and a branch - in the main company create a customer payment and valid it - in the branch, create an invoice for the same customer and confirm it Current behavior: - the outstanding payment from the main company doesn't appear on the branch invoice, However, it's possible to reconcile it from the Journal entry view Expected behavior: - the outstanding payment from the main company appears on the branch invoice, opw-6140689 Forward-Port-Of: odoo/odoo#262260
The AEAT provides different endpoints depending on the type of digital certificate you use. A personal certificate or a seal certificate. The module used always the standard endpoint regardless of the certificate type. Causing authentication failures when a sello certificate was configured. This fix detects the certificate type by checking for the presence of a GIVEN_NAME attribute. task-6169935 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-p
Original PR description
The AEAT provides different endpoints depending on the type of digital certificate you use. A personal certificate or a seal certificate. The module used always the standard endpoint regardless of the certificate type. Causing authentication failures when a sello certificate was configured. This fix detects the certificate type by checking for the presence of a GIVEN_NAME attribute. task-6169935 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#274726 Forward-Port-Of: odoo/odoo#271080
Steps to reproduce: 1. In an `en_US` database, install the Arabic (`ar_001`) language and set it as the website's default language. 2. Add a `blog.post` dynamic snippet to a page and select it. 3. Open the snippet options. 4. Notice that the Filter dropdown is displayed in Arabic instead of English. The RPC fetching the available snippet filters targets the `website=True` `/website/snippet/options_filters` route. During the request initialization, website routes inherit the fro
Original PR description
Steps to reproduce: 1. In an `en_US` database, install the Arabic (`ar_001`) language and set it as the website's default language. 2. Add a `blog.post` dynamic snippet to a page and select it. 3. Open the snippet options. 4. Notice that the Filter dropdown is displayed in Arabic instead of English. The RPC fetching the available snippet filters targets the `website=True` `/website/snippet/options_filters` route. During the request initialization, website routes inherit the frontend request language (see: `frontend_pre_dispatch()`), so the ORM context lang is set to the website language. As a result, translated fields such as name are read in that language. Force `request.env.user.lang` in the context when fetching the filters since their names should be displayed in the editor's preferred language. task-5979540 Forward-Port-Of: odoo/odoo#275390
Before this commit: Deleting a record that uses a filterable selection field with `whitelist_fname` raises a traceback because the record field data becomes undefined during deletion. Steps to reproduce: 1. Install Belgium Accounting (l10n_be). 2. Create a contact and set a Peppol scheme and endpoint. 3. Delete the contact -> a traceback is raised. After this commit: The selection field safely handles undefined record field data during record deletion without causing an error. no-t
Original PR description
Before this commit: Deleting a record that uses a filterable selection field with `whitelist_fname` raises a traceback because the record field data becomes undefined during deletion. Steps to reproduce: 1. Install Belgium Accounting (l10n_be). 2. Create a contact and set a Peppol scheme and endpoint. 3. Delete the contact -> a traceback is raised. After this commit: The selection field safely handles undefined record field data during record deletion without causing an error. no-task Forward-Port-Of: odoo/odoo#278808
Purpose of this PR: - On double click, opening the toolbar is delayed by 300ms to prevent flickering before a potential triple click. - However, mouseup was re-enabling selection tracking (onSelectionChangeActive = true) before the 300ms delay finished. Because browser selectionchange events are dispatched asynchronously after mouseup, they triggered updateToolbar() immediately, bypassing the 300ms delay. - This fix re-enables selection tracking only after the 300ms debounced update actuall
Original PR description
Purpose of this PR: - On double click, opening the toolbar is delayed by 300ms to prevent flickering before a potential triple click. - However, mouseup was re-enabling selection tracking (onSelectionChangeActive = true) before the 300ms delay finished. Because browser selectionchange events are dispatched asynchronously after mouseup, they triggered updateToolbar() immediately, bypassing the 300ms delay. - This fix re-enables selection tracking only after the 300ms debounced update actually finishes. runbot-941543 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#278025
Since I cannot push to https://github.com/odoo-dev/odoo, this PR is a parallel one to [#279530](https://github.com/odoo/odoo/pull/279530). It shows how to fix the #279530 which has a merge conflict. Forward-Port-Of: [#266261](https://github.com/odoo/odoo/pull/266261) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Original PR description
Since I cannot push to https://github.com/odoo-dev/odoo, this PR is a parallel one to [#279530](https://github.com/odoo/odoo/pull/279530). It shows how to fix the #279530 which has a merge conflict. Forward-Port-Of: [#266261](https://github.com/odoo/odoo/pull/266261) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
**PROBLEM** When using fixed tax not affecting the base on line with discount, the xml generated is invalid and refused by peppol. **STEP TO REPRODUCE** 1. Install l10n_be. 2. Create an invoice with the following line: - qty: 2, price_unit: 100, discount: 10%, taxes: 21% + fixed tax 1€ - qty: -2, price_unit: 0, taxes: fixed tax 1€ 3. Generate an xml, and try validating it on peppol. 4. The validation fails with the error: [BR-27]-The Item net price (BT-146) shall NOT be negative.
Original PR description
**PROBLEM** When using fixed tax not affecting the base on line with discount, the xml generated is invalid and refused by peppol. **STEP TO REPRODUCE** 1. Install l10n_be. 2. Create an invoice with…
**PROBLEM** When using fixed tax not affecting the base on line with discount, the xml generated is invalid and refused by peppol. **STEP TO REPRODUCE** 1. Install l10n_be. 2. Create an invoice with the following line: - qty: 2, price_unit: 100, discount: 10%, taxes: 21% + fixed tax 1€ - qty: -2, price_unit: 0, taxes: fixed tax 1€ 3. Generate an xml, and try validating it on peppol. 4. The validation fails with the error: [BR-27]-The Item net price (BT-146) shall NOT be negative. **CAUSE** Fixed tax not affecting the base of other tax are dispatched into new base lines and then merged into one line per fixed tax. The new base lines they are dispatched to are created as a copy of the line they originated from. It means we copy the discount from the original lines. The fixed tax amount is the unit price of each new base lines. When reducing the base lines into one line, we take the unit prices of the line, and apply the discount to the unit price. But, since the unit price is the fixed tax amount, and fixed tax are not affected by discounts, we shouldn't apply discount. **PROBLEM 2** fixed division by 0 traceback when the aggregation of invoice lines is 0 **STEP TO REPRODUCE** 1. Install l10n_be. 2. Create the following invoice: - qty: 1, unit_price: 100, tax:0% + fixed tax 1€, set an analytic distribution account - qty: -1, unit_price: 50, tax:0% + fixed tax 1€, set the same analytic distribution account 3. Send the invoice to peppol. 4. A division by 0 should occur. opw-6388219
Issue ----- Branch companies can have quants of a product limited to their parent company, but they don't show in the per location report. Steps to reproduce ----- - Create a branch company - Create a WH for the branch - Create a tracked product - Company set to parent only - Switch to the branch company - Add a quant of the product in branch stock - Open Inventory > Reporting > Locations > The product is not shown although there is a quant in the branch Cause ----- The m
Original PR description
Issue ----- Branch companies can have quants of a product limited to their parent company, but they don't show in the per location report. Steps to reproduce ----- - Create a branch company - Create…
Issue ----- Branch companies can have quants of a product limited to their parent company, but they don't show in the per location report. Steps to reproduce ----- - Create a branch company - Create a WH for the branch - Create a tracked product - Company set to parent only - Switch to the branch company - Add a quant of the product in branch stock - Open Inventory > Reporting > Locations > The product is not shown although there is a quant in the branch Cause ----- The menu button triggers `action_view_quants` https://github.com/odoo/odoo/blob/03a3662212f094158f885ae6545009fd0a74d3cb/addons/stock/views/stock_quant_views.xml#L493-L495 https://github.com/odoo/odoo/blob/03a3662212f094158f885ae6545009fd0a74d3cb/addons/stock/models/stock_quant.py#L399-L402 The problem here comes from the fact that in `_get_quants_action`, we limit the products to those of only the active companies, instead of allowing to view those of parent companies aswell. https://github.com/odoo/odoo/blob/03a3662212f094158f885ae6545009fd0a74d3cb/addons/stock/models/stock_quant.py#L1330 Such a change works because the domain is specifically for the product's (`product_id.company_id`) and not the location's. ----- Ticket: opw-6131525 Forward-Port-Of: odoo/odoo#277531
The JsonFormatter has two bugs - the ignore list is not working as expected - in 18.0-18.4 the 'test' key is broken This commit add tests to ensure those behavior works as expected While on it, also adds a `additional_record_keys` parameter to allow to specifically add keys to the default list, without having to override the whole list, and add additional default keys (exc_info and test) The previous `ignored_record_keys` default value was possible to remove by calling `JSONFormatte
Original PR description
The JsonFormatter has two bugs - the ignore list is not working as expected - in 18.0-18.4 the 'test' key is broken This commit add tests to ensure those behavior works as expected While on it, also…
The JsonFormatter has two bugs - the ignore list is not working as expected - in 18.0-18.4 the 'test' key is broken This commit add tests to ensure those behavior works as expected While on it, also adds a `additional_record_keys` parameter to allow to specifically add keys to the default list, without having to override the whole list, and add additional default keys (exc_info and test) The previous `ignored_record_keys` default value was possible to remove by calling `JSONFormatter(ignore_record_keys=[])` The purpose was to be able to easily include all keys and ignore the default ingnore list, but this makes the additional blacklisting of a few keys more tedious, and the general usage and implementation more complex `JSONFormatter(ignore_record_keys=[*JSONFormatter.DEFAULT_IGNORED_RECORD_KEYS, 'other key'])` To simplify the logic, **this is not the case anymore**, so to include all keys something like this would be needed `JSONFormatter(additional_record_keys=JSONFormatter.DEFAULT_IGNORED_RECORD_KEYS)` Or an hardcoded list. Forward-Port-Of: odoo/odoo#279344 Forward-Port-Of: odoo/odoo#279049
### Steps to Reproduce: 1. Make sure that the Sale and Inventory modules are installed 2. Enable Multi-Step Routes and Packages in Inventory Configurations 3. Warehouse configuration > Outgoing shipments > Select Pick then Deliver (2 steps) 4. Routes > Deliver in 2 steps (pick + ship) > Pull From > Destination Location > Select WH/Output 5. Routes > Deliver in 2 steps (pick + ship) > Push To > Action > Change to Pull From 6. Operation Types > Delivery Orders > Packages > Enable Move Entire
Original PR description
### Steps to Reproduce: 1. Make sure that the Sale and Inventory modules are installed 2. Enable Multi-Step Routes and Packages in Inventory Configurations 3. Warehouse configuration > Outgoing…
### Steps to Reproduce: 1. Make sure that the Sale and Inventory modules are installed 2. Enable Multi-Step Routes and Packages in Inventory Configurations 3. Warehouse configuration > Outgoing shipments > Select Pick then Deliver (2 steps) 4. Routes > Deliver in 2 steps (pick + ship) > Pull From > Destination Location > Select WH/Output 5. Routes > Deliver in 2 steps (pick + ship) > Push To > Action > Change to Pull From 6. Operation Types > Delivery Orders > Packages > Enable Move Entire Packages 7. Go to any product, ex. Drawer > On Hand > Set original on hand qty to 16 and new lot to 50 8. Create a new SO and make 2 lines, with the same product, and change the second line's price to something else, ex. 80.0 9. Deliveries > WH/PICK/00001 > Set quantity to 4 > Put in Pack > Validate and Create Backorder 10. WH/PICK/00002 > Put in Pack > Validate 11. WH/OUT/00012 > Mark PACK0000001 Done > Save. Observe how the first line quantity is changed from 3 to 4 12. Mark PACK0000002 Done > Save > Validate > Observe how it's asking for a backorder even though we already packed all 5 items. ### Description of the issue/feature this PR addresses: Instead of using the `product_qty` from the stock move, use the quantity of the move line to correctly allocate the quantities in StockPackageLevel ### Current behavior before PR: In the Shop Floor when loading packages, marking the package level as done causes issues on the quantity processed on the corresponding move lines. On stock transfers, we currently allocate the Quantity Done to the wrong product line. The total quantity is correct, but the distribution across lines does not match the Demand values. This causes the transfer to remain stuck in Reserved, even though the shipment was already processed operationally. ### Desired behavior after PR is merged: The correct quantity from the move line is used and this resolves the issue with quantity distribution not matching move line quantities when using packages. opw-6040640 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#242487
Problem: When creating inline code from formatted text, the formatting is not preserved for the text that follows the inline code. Solution: Preserve the active text formatting (e.g., bold, italic, underline) when inserting inline code, ensuring subsequent text on the same line retains the previously applied styles. Steps to reproduce: - Go to To-Do → Create New. - Type some text in bold. - Insert an inline code block. - Continue typing after the inline code. - Observe that the text
Original PR description
Problem: When creating inline code from formatted text, the formatting is not preserved for the text that follows the inline code. Solution: Preserve the active text formatting (e.g., bold, italic, underline) when inserting inline code, ensuring subsequent text on the same line retains the previously applied styles. Steps to reproduce: - Go to To-Do → Create New. - Type some text in bold. - Insert an inline code block. - Continue typing after the inline code. - Observe that the text after the inline code is no longer bold. opw-6395163 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276943
The amount in words split the total with int(decimal * 100), which truncates. 3989.33 is held in binary as 3989.3299..., so the kuruş/cents came out one short (32 instead of 33). The written amount then disagreed with the numeric total on the same invoice and Nilvera rejects it. Round to the currency precision and round the subunit instead. Task-6383690 Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: ---
Original PR description
The amount in words split the total with int(decimal * 100), which truncates. 3989.33 is held in binary as 3989.3299..., so the kuruş/cents came out one short (32 instead of 33). The written amount then disagreed with the numeric total on the same invoice and Nilvera rejects it. Round to the currency precision and round the subunit instead. Task-6383690 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#277748 Forward-Port-Of: odoo/odoo#277180
Steps to Reproduce the Error (Odoo SaaS 19.2): 1. Install l10n_gcc_invoice localization & Accounting 2. Activate Arabic language (ar_001) and add Default Terms and Conditions in Settings > Configuration > Customer Invoices > Default Terms and Conditions 3. Create invoice with ar_001 partner 4. Confirm the invoice 5. Try to create credit note → Error: KeyError: 'en_US' Root Cause: The _load_narration_translation() workaround reads raw invoice_terms from DB and injects the entire JSONB di
Original PR description
Steps to Reproduce the Error (Odoo SaaS 19.2): 1. Install l10n_gcc_invoice localization & Accounting 2. Activate Arabic language (ar_001) and add Default Terms and Conditions in Settings >…
Steps to Reproduce the Error (Odoo SaaS 19.2):
1. Install l10n_gcc_invoice localization & Accounting
2. Activate Arabic language (ar_001) and add Default Terms and Conditions in Settings > Configuration > Customer Invoices > Default Terms and Conditions
3. Create invoice with ar_001 partner
4. Confirm the invoice
5. Try to create credit note → Error: KeyError: 'en_US'
Root Cause:
The _load_narration_translation() workaround reads raw invoice_terms from DB and injects the entire JSONB dict directly into cache, bypassing ORM field conversion. When Odoo 19.2's improved ORM conversion runs, it creates nested JSON in narration instead of a flat structure.
Timeline:
- bedf1cb66fbb: Workaround added to prevent T&C duplication in preview
- 75f050b9650d: Root cause fixed in report template (conditional display) → Made _load_narration_translation() redundant
- 4e4156536bc9: Odoo 19.2 improved ORM conversion → Now conflicts with the redundant workaround, causing nested JSON
How It Breaks:
1. Invoice creation: _load_narration_translation() injects raw dict into cache
2. ORM writes: nested JSON stored: {ar_001: {en_US: ., ar_001: Arabic}}
3. Credit note creation: copy_translations() expects flat structure → Crashes: KeyError: 'en_US'
Why It's Safe to Remove:
Report template already prevents T&C duplication (commit 75f050b9650d). Removing the workaround restores proper credit note creation without breaking T&C display.
Changes:
- Remove moves._load_narration_translation() in create()
- Remove out self.filtered('id')._load_narration_translation() in _compute_narration()
opw : 6284943
Forward-Port-Of: odoo/odoo#271037Miscellaneous changes
**Steps to reproduce:** - Install Survey app - Create a survey - Share as e-mail - Add some text in the body/subject - Modify recipients - The added text is automatically reset **Issue:** Behavior was previously introduced to match the content of the composer body/subject to the recipient language. If there was only one language among the recipients it automatically adapted the template and changed the rendered language (which also refreshed the content). This logic was trigge
Original PR description
**Steps to reproduce:** - Install Survey app - Create a survey - Share as e-mail - Add some text in the body/subject - Modify recipients - The added text is automatically reset **Issue:** Behavior…
**Steps to reproduce:** - Install Survey app - Create a survey - Share as e-mail - Add some text in the body/subject - Modify recipients - The added text is automatically reset **Issue:** Behavior was previously introduced to match the content of the composer body/subject to the recipient language. If there was only one language among the recipients it automatically adapted the template and changed the rendered language (which also refreshed the content). This logic was triggered by a depends on `partner_ids` and triggered the compute on every recipient changes which led to the subject/body reset. **Fix:** Revert commit: https://github.com/odoo/odoo/commit/b7bbb7b21f4848323666230b518cad9459726f67 in 18.0+ Also adapt commit: https://github.com/odoo/odoo/commit/c6f19e89cb6019e7dbaadbc7427fbb6ddd5661ed to avoid mixed language in resulting mail when the composer was modified We could also try to prevent the compute when the subject or body is already modified instead of removing its logic. opw-6020245 Forward-Port-Of: odoo/odoo#254090
backport: [19.0](https://github.com/odoo/odoo/pull/266411) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279961
Original PR description
backport: [19.0](https://github.com/odoo/odoo/pull/266411) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279961