Saturday, August 22, 2026
71 changes · master
Resolved issues and error corrections
This fix prevents approved employee leave records from having their work entry type recalculated later. Only draft leave requests are updated, helping preserve payroll-related information once leave has been validated.
Original PR description
We should recompute the work entry type of only draft leaves. 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#283512
Starting a new chat now notifies only active users who can access the conversation. This prevents failed chat creation or broadcast errors caused by archived users, making messaging more reliable for active employees.
Original PR description
Before this commit, starting a chat with a partner that has an archived user broadcast the new channel to that user too. This happens because _get_or_create_chat searches the partners with active_test=False, only to check that the given ids exist, and a recordset union keeps the environment of its left operand. The flag therefore reaches user_ids, which stops filtering on active. Archived users only reach the broadcast since "[REF] mail, im_livechat: use user in channel._broadcast", as the main_user_id it replaced is always active. The problem is that _broadcast passes each user to with_user, so the whole channel payload is computed with the rights of a user the caller never asked for, and lands on a bus channel no session can subscribe to. This commit fixes the issue by keeping active_test=False on the search alone, and by asking for active users at the broadcast. task-6483179 Forward-Port-Of: odoo/odoo#283593
Fixed an issue where scheduling a meeting from an activity could open the calendar without the expected linked record details. This ensures users see the correct default information when moving from activities to calendar events, reducing confusion and manual re-entry.
Original PR description
Prior to this commit, scheduling a meeting from the activity schedule wizard would fail to open the calendar with the correct default values (like the linked res_model). This occurred because a recent change introduced a context-cleaning step during activity creation to prevent any pollution for unassigned role activities. While this is a good defensive guard, this permanently stripped context from the returned recordset, causing downstream actions to lose context keys like `default_res_model`. This commit restores the original context on the returned activity recordset, ensuring subsequent actions receive the correct default parameters. Task-6482365 Forward-Port-Of: odoo/odoo#283586
Fixed an issue where entering a time on mobile could close the time selector after the first digit and shift focus behind the bottom sheet. This makes scheduling and planning date-time entry more reliable on touch devices.
Original PR description
Steps to reproduce ================== - Use a mobile viewport - Go to planning - Click on an empty cell - Click on the date - On the bottomsheet, click on the time at the bottom - Try to type 12:34…
Steps to reproduce ================== - Use a mobile viewport - Go to planning - Click on an empty cell - Click on the date - On the bottomsheet, click on the time at the bottom - Try to type 12:34 => Only 1 is entered and then the start_datetime field is focused behind the bottom sheet Cause of the issue ================== The <input type="time"/> listen to the onchange event. We listen to rawPickerProps changes using a reactive call. shouldFocus is then set to true, and the focus is done after the next render. The onchange event is called at a different time depending on the platform. On IOS and Firefox desktop: after changing hours or minutes On Android: once the apply button is clicked On Chrome desktop: After entering a single char Solution ======== The bottomsheet is only displayed when env.isSmall && hasTouch(). It doesn't make sense to focus the input, since we don't handle the keyboard in that case. opw-6386252 Forward-Port-Of: odoo/odoo#283782 Forward-Port-Of: odoo/odoo#276617
Odoo now identifies the right company or contact when multiple records use the same email address but have different display names. This prevents emails and chatter messages from showing the wrong sender, helping businesses avoid confusion in multi-company setups.
Original PR description
### Issue: When multiple partners share the same email address, `_mail_find_partner_from_emails` may resolve to the wrong partner when the input is a formatted email like "`Name <email>`" This…
### Issue:
When multiple partners share the same email address, `_mail_find_partner_from_emails` may resolve to the wrong partner when the input is a formatted email like "`Name <email>`"
This affects use cases like email templates using `{{object.company_id.email_formatted}}` as sender, where the wrong company partner could be selected
### Cause:
The lookup in `done_partners` only matched on `email_normalized`, which cannot distinguish partners sharing the same email but with different names
The `email_formatted` field carries both name and email, allowing an exact match when the input is a formatted email
### Steps to reproduce:
- Install `account`
- Create an Email Template (Applies to: account.move, From: {{object.company_id.email_formatted}})
- Create a second company B with the same email as the default (e.g. info@yourcompany.com)
- In Settings (logged in as company B), set a Fiscal Position (e.g. US Taxable)
- Create an Invoice on company B
- In the chatter, click Send message, click the expand arrows button, use the three dots menu to select the template
- Send and check the Sender in the chatter
Before the fix, the sender resolves to the default company even though the invoice belongs to company B
opw-6260992
Forward-Port-Of: odoo/odoo#283875
Forward-Port-Of: odoo/odoo#269509Refunds in Point of Sale now use the same fiscal position as the order being refunded, even when that order had no special fiscal position. This prevents refunds from silently using the default preset's taxes, helping businesses avoid incorrect tax calculations and later reconciliation surprises.
Original PR description
Steps to reproduce: - Enable presets and set a preset carrying a tax-replacing fiscal position (e.g. "Takeout") as the default preset of the POS config - Take an order with another preset that has no…
Steps to reproduce: - Enable presets and set a preset carrying a tax-replacing fiscal position (e.g. "Takeout") as the default preset of the POS config - Take an order with another preset that has no fiscal position (e.g. "Dine In") - Refund that order from the ticket screen Issue: The refund is taxed with the fiscal position of the default preset instead of the one of the refunded order. The ticket screen displays the refund lines with the taxes of the original order, but the refund that is actually created maps them through the wrong fiscal position. With tax-included prices the totals still match on screen, so the operator only sees the discrepancy after going back. Cause: The destination order of a refund is an empty order, created with the default preset and therefore with that preset's fiscal position. In TicketScreen.onDoRefund, the fiscal position of the refunded order was only copied onto it when the refunded order had one, so an order taken without a fiscal position kept the default preset's one. When an already existing empty order is reused as destination, whatever fiscal position was last set on it survives for the same reason. Fix: Always assign the fiscal position of the refunded order to the destination order, an empty one included, so a refund is taxed exactly like the order it refunds instead of silently switching. The preset itself is left untouched: it drives the ordering workflow (timing slot, customer identification) which must not be imposed on a refund. opw-6442664 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282560 Forward-Port-Of: odoo/odoo#280672
Forms now keep the desktop-style layout on tablet-sized screens instead of switching too early to the mobile view. This provides a more consistent and usable experience for users working on tablets or medium-sized devices.
Original PR description
Change the breakpoints to keep the desktop configuration on tablet resolutions instead of switching to the mobile display. task-6379696 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#283818 Forward-Port-Of: odoo/odoo#283765
Sales automatic invoicing now creates each invoice for the actual latest payment amount, rather than using the total paid so far. This prevents over- or under-invoicing when customers pay a sales order in multiple installments, improving billing accuracy and customer trust.
Original PR description
Steps to produce: --- - Install the `Sales` module. - In Settings, enable `Automatic Invoice`. - Also enable the Demo payment provider. - Create a sale order with a total of `800` and confirm it. -…
Steps to produce: --- - Install the `Sales` module. - In Settings, enable `Automatic Invoice`. - Also enable the Demo payment provider. - Create a sale order with a total of `800` and confirm it. - Generate a payment link for `200` from the gear icon and pay it. - Generate a second payment link for `300` and pay it. - Generate a final payment link for the remaining `300` and pay it. Issue: --- - After the first payment (200), an `invoice of 200` is created. Correct. - After the second payment (300), an` invoice of 500` is created instead of 300. - After the third payment (300), an `invoice of 100` is created instead of 300. Root cause: --- - The down payment invoice uses `order.amount_paid`, the cumulative sum of all transactions on the order, instead of the amount of the latest payment. This causes invoices to be sized off the running total instead of the individual payment delta. Fix: --- - Compute the invoice amount as `order.amount_paid - order.amount_invoiced` (the unpaid) instead of passing the cumulative `amount_paid` directly. opw-6324036 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283492 Forward-Port-Of: odoo/odoo#273099
This fixes an inefficiency that could slow down opening Sign templates for users with access to their own templates. The system now loads related template item data in a batch instead of one item at a time, reducing unnecessary database work and improving responsiveness.
Original PR description
Steps to reproduce: - with a user with "Sign / User: Own Templates" access rights - go to Sign / Templates - click on a template to open it => reading `sign.item.role.item_ids.template_id` triggers the computation of the related field `sign.item.template_id`, whose inverse `sign.template.sign_item_ids` carries a domain. When applying the domain, the sign.item's fields need to be fetched but they are fetched with one query per sign.item instead of a single batched one. task-6478942 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283864 Forward-Port-Of: odoo/odoo#282994
Imported UBL invoices with document-level discounts or charges now correctly associate their tax amounts with the relevant tax totals. This prevents incorrect tax adjustments during import, helping ensure accounting data is accurate for affected invoices.
Original PR description
When importing UBL invoices that contain document-level allowances or charges with percentage taxes, the tax values were not linked to their corresponding `TaxSubtotal` group (`related_taxes_values`). As a result, the tax correction step (`_import_ubl_invoice_fix_taxes_amounts`) was unaware of document-level taxes, which caused wrong tax corrections. opw-6388544 Forward-Port-Of: odoo/odoo#279350
Fixed an issue where clicking the cashier status icon in certain Belgian Blackbox Point of Sale setups could trigger an error. This improves reliability for cashiers when employee login is disabled.
Original PR description
Steps: ----------- - Install pos_blackbox_be. - Configure a PoS with Blackbox Belgium enabled and `Log in with Employees` disabled. - Open a PoS session and click exactly on the session status circle on the cashier icon. Issue: ----------- - A traceback is raised with the following error: `this.cashierSelector is not a function`. Cause: ----------- - Installing pos_blackbox_be makes the cashier icon appear clickable by adding the `pe-auto` class to the cashier icon's session status circle, even when `Log in with Employees` is disabled. In this configuration, the cashier selector is unavailable, causing the click handler to fail. Fix: ----------- - Add a dedicated onClick handler to the CashierName button. - Return early when `module_pos_hr` is not enabled before calling `selectCashier`, ensuring that `selectCashier` is called only when the `module_pos_hr` configuration is enabled. Task-6369404 Forward-Port-Of: odoo/odoo#282656
The Accounting journal entry screen now shows clear labels on the reversal smart button. Users can more easily tell whether they are viewing original entries with reversals or the reversal entry itself, reducing confusion during accounting reviews.
Original PR description
Issue: - The reversal smart button was displayed without a label for journal entries. Fix: - Show 'Reversal Entries' for original journal entries and 'Journal Entry' for their reversal entries. Impact: - The reversal smart button now displays the correct label for journal entries and their reversals. task-[6472384](https://www.odoo.com/odoo/project/967/tasks/6472384) Forward-Port-Of: odoo/odoo#283756
Fixed an issue that could crash the chat interface when an operator closed multiple live chat conversations at once. This improves reliability for support teams handling several conversations and prevents disruptions in the chat hub.
Original PR description
Before this commit, clicking "Close all conversations" from the chat hub as an operator with several live chat windows open (fresh database with im_livechat demo data, logged in as admin) sometimes…
Before this commit, clicking "Close all conversations" from the chat hub as an operator with several live chat windows open (fresh database with im_livechat demo data, logged in as admin) sometimes crashed:
TypeError: Cannot read properties of undefined (reading
'findIndex')
at Proxy.close (mail/static/src/core/common/chat_window_model)
or with "RangeError: Maximum call stack size exceeded" in the record machinery. The overflow interrupts that machinery anywhere, hence the varying crash.
This happens because closing all windows leaves every live chat channel at once, and the resulting bus payloads reach the store out of their transaction order. Resolving an out-of-order many command derives a REPLACE from the field's history, and applying it updates the inverse field of each removed record. The problem is that these inverse updates are resolved against the same out-of-order revision:
- on the many side, they derive another REPLACE on the field they come from, re-entering the resolution before the field is updated, until the stack overflows;
- when their revision is older than the field's last replace, they are dropped, leaving the two sides of the relation out of sync.
Reminder that inverse updates go through `updateFields()` since "[FIX] mail: no infinite loop on discuss page load", so that the version history records them.
This commit fixes the issue by always applying client-generated updates (inverse echoes, computes, direct field writes): they reflect state the client already changed elsewhere, so only server data is subject to version resolution. They are still recorded in the history.
task-6450249
Forward-Port-Of: odoo/odoo#283204This fix prevents a secondary database error from hiding the real issue when email notifications are updated at the same time by concurrent processes. It helps email sending failures be handled and diagnosed correctly, improving reliability in the Mail app under load.
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#279897
Forward-Port-Of: odoo/odoo#274089This fixes an issue in the HTML editor where a color set on an outer table could incorrectly overwrite the colors of tables nested inside it. Users editing rich content with nested tables will see more accurate formatting and fewer unexpected color changes.
Original PR description
Problem: When a `table` with a `color`/`backgroundColor` contains a nested `table`, `distributeTableColorsToAllCells` propagates the outer table's color to every `td` in the subtree, including cells…
Problem:
When a `table` with a `color`/`backgroundColor` contains a nested `table`, `distributeTableColorsToAllCells` propagates the outer table's color to every `td` in the subtree, including cells belonging to the inner table. The inner table's own color is then discarded since its `td`s already have a value.
Cause:
`table.querySelectorAll("td")` returns every `td` in the entire subtree, not just the table's own direct cells.
Solution:
Scope the selected `td`s to `td.closest("table") === table`, so a table's color is only distributed to its own cells.
Steps to reproduce:
1. Add a `background-color` to an outer `table`.
2. Nest a `table` with a different `background-color` inside one of its cells.
3. Load/normalize the content in the editor.
4. Observe both tables' cells carry the outer table's color.
opw-6438972
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#283373
Forward-Port-Of: odoo/odoo#281413This fix updates an internal image test to work reliably with the Pillow version included in Ubuntu Jammy. It helps keep automated checks stable across supported environments without changing user-facing behavior.
Original PR description
`Image.Palette.ADAPTIVE` is not available in the Pillow version provided by Ubuntu Jammy, causing the animated GIF test to fail. Use `Image.ADAPTIVE` instead, which is compatible with both older and newer Pillow versions. 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#282388 Forward-Port-Of: odoo/odoo#282223
Odoo now shows the specific error details returned by Serbia's eFaktura service when an invoice submission fails. This helps users understand why an invoice was rejected and take the right corrective action without relying on generic technical error messages.
Original PR description
**Steps to reproduce:** - Install the Serbian EDI module `l10n_rs_edi`. - Configure eFaktura credentials on the company. - Create and confirm a Serbian customer invoice. - Send the invoice to…
**Steps to reproduce:**
- Install the Serbian EDI module `l10n_rs_edi`.
- Configure eFaktura credentials on the company.
- Create and confirm a Serbian customer invoice.
- Send the invoice to eFaktura.
**Observed Behavior:**
When the eFaktura API returns an HTTP error, Odoo only displays the generic exception generated by `requests`, for example an HTTP 400/500 error.
The actual error information returned by eFaktura in the response body is not shown to the user, making it difficult to understand why the invoice was rejected.
**Cause:**
`_l10n_rs_edi_send` catches `HTTPError`, `Timeout`, and `ConnectionError`, but the error message is built only from the Python exception.
For HTTP errors, the eFaktura API may return a response containing more precise information such as:
```json
{
ErrorCode: ...,
Message: ...
}
```
This response was not being used when displaying the error in Odoo.
**Fix:**
When an HTTP response is available and contains an eFaktura error payload, use the returned `ErrorCode` and `Message` as the error displayed on the invoice. Fallback to the existing connection/HTTP exception message when no usable API response is available.
opw - 6453653
Forward-Port-Of: odoo/odoo#281490Searching messages in Discuss now handles search text with repeated spaces correctly. This prevents an error that could interrupt users when looking through conversations and keeps message highlighting working as expected.
Original PR description
**Steps to reproduce:**
- Go to Discuss app
- Open a conversation
- Click on the Search Messages button
- Enter a word, then a lot of spaces
- `RangeError: Maximum call stack size exceeded`
**Issue:**
During highlighting, if the search term contains multiple spaces, `searchTerm.split(" ")` produces empty terms `""`. Then the empty regex will match on every character, creating a lot of highlight `<span>` elements and eventually causing the error on `element.replaceChildren(...newNode);`.
**Fix:**
Filter out empty terms before processing.
opw-6446173
Forward-Port-Of: odoo/odoo#282059Regular sales users can now see the three-dot menu on product cards in the Product Catalog. This restores access to actions they were already allowed to use elsewhere, reducing unnecessary administrator dependency.
Original PR description
**Steps to Reproduce:** 1. Give the logged in user "Sales / User: Own Documents Only" access rights 2. Open the Product Catalog (from a Sales Order line) 3. The three-dot menu on a product card is not visible when you hover over it 4. Change user rights with "Sales Administrator" access rights, the three-dot menu appears as expected **Issue:** The three-dot menu on the Product Catalog kanban card is restricted to the Sales Administrator group, even though the actions it exposes (edit product, availability, etc) are already accessible to regular Sales users through other menus/views. **Why this happens:** The view `product.view.kanban.catalog.inherit.sale` sets the `groups` attribute to `sales_team.group_sale_manager`, restricting the menu behind Administrator rights instead of the base Sales access group opw-6416629 Forward-Port-Of: odoo/odoo#279400
Intercompany deliveries can now automatically unpack products after shipment, preventing package records from carrying over incorrectly between companies. This keeps inventory balances clearer and avoids confusing leftover stock artifacts in intercompany locations.
Original PR description
Issue: Compare to lot and serial number package are not multi company. It means that the package don't pass from a company to the other. So when a company deliver to another. The delivery will create a quant with the package. However the receipt in the other company will create a new quant without package (or a new package). It means that the quants are never reconcile and it could become difficult to understand what remains in intercompany location and what are artifact from past movements. In order to fix it, we introduce a new system parameter to directly unpack after the delivery. This way the receipt is always without source package and will automatically decrease the quant. opw-6376983 Forward-Port-Of: odoo/odoo#282448 Forward-Port-Of: odoo/odoo#277133
Vendor bills created from incoming Peppol/UBL email files now include the PDF embedded inside the XML. This prevents missing invoice documents when suppliers send electronic invoices through an email alias, improving document completeness for accounting teams.
Original PR description
When receiving a Peppol/UBL XML file containing an embedded PDF via an email alias, the PDF is not extracted and attached to the resulting vendor bill. Steps to reproduce: - Set up a BE Company - Configure an incoming mail server - Set up an email alias for the Vendor Bill journal - Receive a Peppol XML with embedded PDF via alias - Check the created Bill Issue: PDF has not been extracted from the xml This occurs because the received xml is set as main attachment for the record and in this case we skip extraction opw-6075250 Forward-Port-Of: odoo/odoo#278806 Forward-Port-Of: odoo/odoo#262047
Razorpay payment captures and refunds now use the original transaction reference when communicating with the provider. This helps ensure follow-up payment actions are matched to the correct Razorpay transaction, reducing failed or incorrect capture/refund operations.
Original PR description
Forward-Port-Of: odoo/odoo#283432 Forward-Port-Of: odoo/odoo#282554
The Point of Sale combo setup now correctly keeps separate choices for the same product when customers select different side options. This prevents extra duplicate order lines, making restaurant orders clearer and reducing cashier corrections.
Original PR description
Steps: --- - Open the Burger combo choice. - Set the maximum quantity to 2. - Open the restaurant. - Add a cheese burger with Belgian fresh homemade fries. - Add another cheese burger with sweet potato fries. - Add Coca-Cola. - Click Apply. Issue: --- - A new cheese burger line is created even though a cheese burger line already exists. Cause: --- - When the same product has different configurations but belongs to the same `combo_item`, the last configuration overrides the previous one due to using the same key. Fix: --- - Differentiate configurations by appending `lineUuid` to the configuration key. - Ensure each product configuration is handled independently when computing included and extra combo items. task-5480195 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282988 Forward-Port-Of: odoo/odoo#243954
French e-Invoicing eligibility now checks a customer's SIREN or SIRET instead of relying only on a VAT number. This prevents French business customers without a VAT number from being incorrectly treated as consumers, so the correct invoice sending option remains available.
Original PR description
**Steps to reproduce:** - Install module `l10n_fr_pdp` and configure French e-Invoicing. - Create a customer has a valid SIREN/SIRET (company_registry) but no VAT number. - Create an invoice for the customer and confirm the invoice. - Check the available sending methods. **Observed Behavior:** The French E-Invoicing option is disabled because the customer is identified as a B2C partner when no VAT number is set. **Cause**: The B2C detection relies on the partner's VAT number instead of its SIREN/SIRET. As a result, French companies without a VAT number but with a valid SIREN are classified as B2C. **Fix**: Determine whether a partner is B2C based on the presence of a valid SIREN/SIRET (derived from `company_registry`) instead of the VAT number. This correctly identifies French business partners that are eligible for French e-Invoicing even when they do not have a VAT number configured. opw-6357756 Forward-Port-Of: odoo/odoo#283418 Forward-Port-Of: odoo/odoo#278060
When users choose the withholding-only option in the payment wizard, Odoo now shows only the appropriate miscellaneous journals. This prevents confusion from an automatic journal change that users could not later adjust correctly.
Original PR description
Previously, when the user selected the 'withhold only' option in the payment wizard, the journal was automatically changed to a miscellaneous journal. However, when the user tried to change the journal, no miscellaneous journals were available. With this commit, only miscellaneous journals are available when 'withhold only' is selected. task-6461688 Forward-Port-Of: odoo/odoo#281697
Fixes an issue in the Italian localization where debit carryover from a prior VAT closing could appear in the January monthly VAT report section reserved for credit carryover. This helps companies produce more accurate VAT reports and avoid misclassification in Italian tax reporting.
Original PR description
With a l10n_it company: - Create an invoice for december, create the tax return closing entry for this period. On the monthly VAT Report of january the carryover is declared in section VP9 which is supposed to store credit carry over not debit. opw-6354509 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#279471
Sales orders now calculate delivery status based only on products that are actually deliverable, instead of including services or delivery fees. This prevents orders from being shown as partially delivered when all physical items that need delivery have already been shipped.
Original PR description
Delivery status considered every order line, including service, delivery and etc. When we ship products without stock, we deliver only consumable products, leaving the order wrongly partially delivered when there are other types of products included. Filter to consu lines when computing the order's delivery status. Forward-Port-Of: odoo/odoo#283206
Exchanged subcontracted products are now correctly received into warehouse stock instead of remaining in the subcontractor location. Purchase orders also show the correct received quantity after an exchange, improving inventory and procurement accuracy.
Original PR description
Steps to reproduce ------------------ 1. Configure a product with a subcontracted BoM and a subcontractor. 2. Create a purchase order of 10 units for that product and confirm it. 3. Receive the 10…
Steps to reproduce ------------------ 1. Configure a product with a subcontracted BoM and a subcontractor. 2. Create a purchase order of 10 units for that product and confirm it. 3. Receive the 10 units. 4. On the receipt, use "Return for Exchange" on 3 units and validate both the return and the exchange receipt. Issue ----- After the exchange, the 3 units stay in the subcontracting location instead of reaching `WH/Stock`, and the received quantity on the purchase order line stays at 7 instead of 10. `mrp_subcontracting` overrides `_prepare_move_default_values` to force the move `location_dest_id` to the subcontractor location for every `is_subcontract` move: https://github.com/odoo/odoo/blob/d9c06a66356dd9d5a50821b8cde6194967353c18/addons/mrp_subcontracting/wizard/stock_picking_return.py#L20-L25 That is correct for the return, but the same override also runs for the exchange re-receipt, an `incoming` picking whose destination should be the stock location from `return_type.default_location_dest_id`: https://github.com/odoo/odoo/blob/d9c06a66356dd9d5a50821b8cde6194967353c18/addons/stock/wizard/stock_picking_return.py#L137-L153 The exchange move then goes from the subcontracting location back to itself, so validating it nets zero and `WH/Stock` never receives the units. Skipping the override when `new_picking.picking_type_id.code` is `incoming` lets the exchange land in stock. The received quantity must also count that receipt. `_should_count_for_quantity_received` only counts `supplier` or `transit` sources: https://github.com/odoo/odoo/blob/d9c06a66356dd9d5a50821b8cde6194967353c18/addons/stock/models/stock_move.py#L330-L331 so the exchange, sourced from the internal subcontracting location, is skipped while the return still subtracts its quantity. Counting subcontracting-sourced moves: https://github.com/odoo/odoo/blob/d9c06a66356dd9d5a50821b8cde6194967353c18/addons/mrp_subcontracting/models/stock_move.py#L312-L314 restores `qty_received` to 10. opw-6410978 Forward-Port-Of: odoo/odoo#282666 Forward-Port-Of: odoo/odoo#279431
Accrued expense entries can now be created for purchase orders whose quantity was changed to zero after receipt. This prevents an error dialog and lets accounting users continue their accrual workflow normally.
Original PR description
### Steps to Reproduce: 1. Have a product where Track Inventory is enabled and the product category is FIFO and Perpetual 2. Create a PO for the product 3. Validate the receipt 4. Update the quantity…
### Steps to Reproduce: 1. Have a product where Track Inventory is enabled and the product category is FIFO and Perpetual 2. Create a PO for the product 3. Validate the receipt 4. Update the quantity on the PO to 0 5. Create Accrued Expense Entry > Traceback ### Description of the issue/feature this PR addresses: **Issue:** Currently when generating an Accrued Expense Entry for a PO where quantity on the line is updated to 0, the system crashes with an RPC error. This happens because reducing the line quantity to 0 sets the overall order amount to 0.0. Then. when the accrued orders wizard tries to calculate line-item ratios, it triggers a `ZeroDivisionError`. **Solution:** We can add a zero-check fallback condition when computing the line ratio inside `_compute_move_vals` in the `AccountAccruedOrdersWizard` class. The ratio calculation now defaults to 0.0 if the order total is zero, preventing division by zero. ### Current behavior before PR: Triggering the Accrued Expense Entry wizard on a PO with a changed quantity of 0.0 causes a `ZeroDivisionError` server error. The user receives an RPC error dialog and cannot proceed with creating the journal entry. ### Desired behavior after PR: The wizard should be able to process Purchase Orders with a line quantity of 0 without throwing an RPC error. The system should now cleanly generate the accrual entry based on received quantities. opw-6459403 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281808
Portal and live chat users can now mention someone in a message without the mention being lost. This ensures the mentioned person is properly notified, improving communication reliability in conversations.
Original PR description
Before this commit, a portal or livechat visitor mentioning someone in a message did not notify them: the mention was silently dropped. This happens because the client cleans up empty recipient fields by checking `postData[field].length`, and `partner_ids_mention_token` is an object, so its `length` is always undefined. The tokens were dropped as empty, and the server then filtered out every partner the sender cannot read. This commit fixes the issue by checking the number of keys instead. Forward-Port-Of: odoo/odoo#282782
Fixed an issue that could prevent users from opening the calendar in month view when their linked employee record belonged to another company. The calendar now handles missing schedule information gracefully, avoiding an error and keeping the user experience uninterrupted.
Original PR description
Currently, an error occurs when a user opens the calendar. Steps to Reproduce: - Install the `hr_calendar` module. - Go to `Employees` and create an `employee`. - Under the `Settings tab`, set the…
Currently, an error occurs when a user opens the calendar. Steps to Reproduce: - Install the `hr_calendar` module. - Go to `Employees` and create an `employee`. - Under the `Settings tab`, set the employee's `user` to `Administrator`. - Create a `new company` and switch to it. - Open the `Calendar` and set the `scale` to `Month`. `TypeError: reduce() of empty iterable with no initial value` When the user opens the calendar, it fetches the unusual days for the selected attendees. By default, the current user's partner is set as an attendee [1]. It then computes the schedule for the attendee [2], which returns an empty dictionary [3] because the linked employee belongs to a different company than the current company, as restricted by the domain [4]. This empty dictionary is then passed to reduce() to intersect the schedules, which raises an error because the iterable is empty. This commit ensures that when no schedule is found for the attendees, an empty set is returned. [1]: https://github.com/odoo/odoo/blob/658018684d781fef8bf77a77f1e050d1eb16937c/addons/hr_calendar/models/calendar_event.py#L38-L40 [2]: https://github.com/odoo/odoo/blob/658018684d781fef8bf77a77f1e050d1eb16937c/addons/hr_calendar/models/res_partner.py#L123-L130 [3]- https://github.com/odoo/odoo/blob/658018684d781fef8bf77a77f1e050d1eb16937c/addons/hr_calendar/models/res_partner.py#L40-L42 [4]- https://github.com/odoo/odoo/blob/658018684d781fef8bf77a77f1e050d1eb16937c/addons/hr_calendar/models/res_partner.py#L19-L25 sentry-7672490836 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282866
Fixes an issue where validating inventory receipts could fail when automatic reception report printing was enabled. Transfers and manufacturing-related validations can now complete normally, and printed reports contain the correct records.
Original PR description
Issue before this commit: ======================== Currently, when the reception report option is enabled for the operation type under Print on Validation, validating a transfer with reserved…
Issue before this commit: ======================== Currently, when the reception report option is enabled for the operation type under Print on Validation, validating a transfer with reserved quantities for the next transfer automatically tries to print the reception report. However, the print fails with `External ID not found in the system: stock.stock_reception_report_action`, preventing the transfer from being validated. Steps to Reproduce: ========================= - Install the stock module. - In Inventory → Configuration → Operation Types → Receipts, enable Reception Report under Print on Validation. - Create a delivery and receipt for Product A, both with a quantity of 4. - Open the Allocation smart button on the receipt and reserve the incoming quantity for the created delivery. - Validate the receipt. Observation: The system tries to print the reception report, but validation fails with `External ID not found in the system: stock.stock_reception_report_action`. Cause of the issue: ========================= In this [PR](https://github.com/odoo/odoo/pull/264299/changes#diff-79ba983a362d3fed28458daad5f2902e914fd731308157aa73f350f1597265a6L30-L35), the reception report action was removed, and the reception report is now printed through the Picking Operations report using `do_print_picking()`. However, the auto-print methods in both Stock and MRP still referenced the old reception report action, causing the `External ID not found` error when the reception report auto-print is enabled After This Commit: ========================= - The obsolete reception report reference is replaced with the appropriate reports in Stock and MRP, preventing the traceback and allowing users to complete validation when the reception report is enabled. - The unnecessary context override in `_get_autoprint_done_report_actions()` was removed. It was replacing the context returned by report_action() with the `unnecessary default_production_ids`, which removed the `active_ids` generated by report_action(). These IDs are required by the report renderer to identify the production records, and removing them caused a blank PDF report. Forward-Port-Of: odoo/odoo#283194
Electronic invoice XML now uses the reference from the invoiced child contact when one is set, instead of incorrectly taking the parent company reference. This helps ensure Peppol invoices carry the right buyer identifier and reduces misrouting or processing issues for customers with multiple contacts.
Original PR description
**Steps to reproduce:** * Set up a French company and configure Peppol E-invoicing. * Install `account_edi_ubl_cii` module. * Create a company partner (customer) and set a **Reference** value on the…
**Steps to reproduce:**
* Set up a French company and configure Peppol E-invoicing.
* Install `account_edi_ubl_cii` module.
* Create a company partner (customer) and set a **Reference** value on the company contact under
**Customer** -> **Settings** -> **Sales and Purchase**.
* Create a child contact under that company and set a different Reference value.
* Create an invoice using the child contact as the invoice partner and confirm the invoice.
* Send it via Peppol.
**Observed Behaviour:**
* The BuyerReference in the generated XML contains the reference of the parent
(commercial partner) Instead of the child contact used on the invoice.
**Cause:**
* The buyer reference was taken from the commercial partner instead of the
invoice partner.
**Fix:**
* Update the condition to use the invoice partner's reference when available;
Otherwise, fall back on the commercial partner's reference.
opw - 6330649
Forward-Port-Of: odoo/odoo#283324
Forward-Port-Of: odoo/odoo#273700DIN5008 invoice PDFs sent by post now place the recipient address correctly in the required window. This prevents postal delivery failures through Pingen while keeping the normal DIN5008 layout unchanged for non-postal documents.
Original PR description
**Steps to reproduce:** - Install l10n_din5008 and accountant. - Enable Snailmail from Accounting → Settings. - Create a German customer (Fiscal Country: Germany). - Create and post a customer…
**Steps to reproduce:** - Install l10n_din5008 and accountant. - Enable Snailmail from Accounting → Settings. - Create a German customer (Fiscal Country: Germany). - Create and post a customer invoice using the DIN5008 report layout. - Select Send by Post. - Enable Developer Mode and navigate to `Settings → Technical → Email → Snailmail Letters`. - Open the generated letter and send it. **Current behavior:** The letter fails to be sent to Pingen with the following error: An error occurred when sending the document by post. Error: ` The attachment of the letter could not be sent. Please check its content and contact the support if the problem persists.` **Cause:** For Snailmail documents, Pingen validates that the recipient address is located within the DIN5008 address window. The current l10n_din5008 report renders additional document information instead of the address in the address area, preventing the compliance validation to fail. **Fix:** When rendering the report for Snailmail, ensure that only the recipient address is displayed in the DIN5008 address window while suppressing the additional information that would otherwise occupy this area. This preserves the standard DIN5008 layout for regular reports while generating a Snailmail-compliant PDF that passes Pingen’s validation. **Reference:** [Pignen Recipient Address Validation Rule](https://help.pingen.com/en/fix-and-enhance-letters/issue-with-recipient-address#040201) Ticket [link](https://www.odoo.com/odoo/project.task/6387869) opw-6387869 Forward-Port-Of: odoo/odoo#280320
Store pickup locations are no longer shown as selectable delivery addresses when customers review or edit their checkout contact details. This prevents confusion and keeps the address list limited to customer-created delivery addresses.
Original PR description
Steps to produce: --- - Install `website_sale_collect` module. - Create and publish a product. - Add it to the cart and proceed to checkout. - Fill in the address and confirm. - Select a `pick-up in…
Steps to produce: --- - Install `website_sale_collect` module. - Create and publish a product. - Add it to the cart and proceed to checkout. - Fill in the address and confirm. - Select a `pick-up in store` delivery method. - Click the edit icon on the contact details. - Confirm without making any changes. Issue: --- - The pick-up point address appears as a selectable delivery address in the contact details list, which it should not. Root cause: --- - When a pick-up point is selected, `set_pickup_location` calls `_address_from_json` ([1]), which creates a child `res.partner` record with `type='delivery'` and sets `pickup_delivery_method_id` to identify it as a pick-up point address. Later, when the user returns to the address page, `_prepare_address_data` calls `_get_delivery_address_domain` ([2]) from `portal`. This method returns all child partners with `type='delivery'` without distinguishing between user-created delivery addresses and the auto-generated pick-up point addresses As a result, the pick-up point address incorrectly appears in the checkout address list. Solution: --- - As specified in [task], partners created through this flow should be archived. However, in the referenced [commit], the `active=False` flag was removed when creating the partner, causing newly created partners to remain active. Override `_get_delivery_address_domain` to exclude pick-up point addresses. Since auto-generated pick-up point addresses always have `pickup_delivery_method_id` set, they are filtered out from the checkout address list, while manually created delivery addresses remain unaffected. [1]https://github.com/odoo/odoo/blob/fb6298e50a7c8ded2800254e8715336eeb37deb5/addons/website_sale_stock/models/res_partner.py#L16-L72 [2]https://github.com/odoo/odoo/blob/fb6298e50a7c8ded2800254e8715336eeb37deb5/addons/portal/models/res_partner.py#L51-L55 [task]: https://www.odoo.com/odoo/project/49/tasks/3645144 [commit]: https://github.com/odoo/odoo/commit/fb74a371407ee19c6b1a3ab9f5a7b314978cb5cb opw-6356778 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283314 Forward-Port-Of: odoo/odoo#276148
The SMS Marketing screen no longer shows the Insert Field option when a message cannot be edited. This prevents users from triggering an error while viewing campaigns that are already sent or being sent.
Original PR description
Steps to reproduce ---------------------------------------- 1. Install the SMS Marketing module (mass_mailing_sms). 2. Open any SMS Marketing record in the "Sent" or "Sending" stage. 3. Click on the…
Steps to reproduce ---------------------------------------- 1. Install the SMS Marketing module (mass_mailing_sms). 2. Open any SMS Marketing record in the "Sent" or "Sending" stage. 3. Click on the "Insert Field" button. Observation ---------------------------------------- Traceback Occurs: ``` TypeError: Cannot read properties of null (reading 'getRootNode') ``` Issue ---------------------------------------- The SMS widget displays the "Insert Field" button even when the SMS message field is readonly. The button relies on the textarea reference to open the dynamic fields popover, but the textarea is only rendered in editable mode. The readonly behavior of the text field can be seen here: https://github.com/odoo/odoo/blob/ccce9fcc79edcfb1f310b49a16de8235d987b74b/addons/web/static/src/views/fields/text/text_field.xml#L5-L7 However, the SMS widget still renders the "Insert Field" button without checking whether the message field is readonly: https://github.com/odoo/odoo/blob/ccce9fcc79edcfb1f310b49a16de8235d987b/addons/sms/static/src/components/sms_widget/fields_sms_widget.xml#L6 As a result, clicking the button in readonly mode tries to access an unavailable textarea reference to open the dynamic fields popover, causing a traceback. Solution ---------------------------------------- Hide the "Insert Field" button when the SMS message field is readonly, preventing the dynamic fields popover from being opened when the textarea reference is unavailable. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283446 Forward-Port-Of: odoo/odoo#282845
The Point of Sale feedback screen now adjusts better to Android phones and tablet displays. This prevents messages and confirmation visuals from appearing too small or overflowing, giving customers and staff a clearer checkout experience across devices.
Original PR description
In this commit: - The feedback screen was not scaling properly on Android devices and tablet displays, causing content to appear too small or overflow. - Fixed by making the checkmark and text sizes responsive using units so the layout adapts correctly across different screen sizes. Task: 6420543 Forward-Port-Of: odoo/odoo#283181 Forward-Port-Of: odoo/odoo#279328
Clicking a table of contents entry now scrolls the page so the selected heading is easier to see, rather than leaving it barely visible at the edge of the screen. This makes navigation within HTML content feel more reliable and helps users quickly find the section they selected.
Original PR description
When clicking on a title in the TOC, we auto-scroll to that section of the HTML, allowing users to read that part. Since [1], scrollIntoView is replaced to consider top-aligned sticky elements. As a result, instead of scrolling to make it comfortable to read the section, it stops as soon as the title is visible. Unless you are really attentive at the bottom of the screen, it can look like the scrolling did not work. This commit computes the appropriate offset to make the TOC heading more visible after scrolling. [1]: https://github.com/odoo/odoo/commit/f5cf8565e7d09edd3a29fd95537381fb70d75785 Task-6394193 Forward-Port-Of: odoo/odoo#278304
Posted customer invoices now keep their original delivery date even if later deliveries are added to the same sales order with earlier completion dates. This prevents already-finalized invoices from changing silently and improves reliability for invoicing and audit records.
Original PR description
Steps to Reproduce: 1. Confirm a Sales Order with one line -> creates delivery P1. Validate P1 with date_done = Day_A. 2. Create an invoice from the SO and post it -> invoice.delivery_date = Day_A.…
Steps to Reproduce: 1. Confirm a Sales Order with one line -> creates delivery P1. Validate P1 with date_done = Day_A. 2. Create an invoice from the SO and post it -> invoice.delivery_date = Day_A. 3. Add a new line to the same SO -> creates delivery P2. 4. Validate P2 with date_done = Day_B, where Day_B is earlier than Day_A. Issue: The already-posted invoice's `delivery_date` silently changes from Day_A to Day_B after step 4, even though nobody edited the invoice. This only happens when a delivery validated after posting has an earlier `date_done` than what was already used. Root Cause: `account.move.delivery_date (sale_stock)` is computed in `_compute_delivery_date()`, which depends on `sale.order.effective_date.effective_date` is itself computed as the earliest `date_done` among all done, facing deliveries on the order. Neither compute method checks whether the invoice is posted, so validating P2 triggers a chain reaction: the delivery is saved -> the sale order recalculates -> the invoice recalculates -> delivery_date gets overwritten on an already-posted invoice. `sale_stock` also marks `delivery_date` as protected, but this protection only works when the invoice itself is saved (write/create). Here, the change starts from saving the delivery (stock.picking), which never goes through the invoice's save method, so the protection never kicks in. `delivery_date` is also not on the list of fields Odoo normally blocks from editing after posting. Fix: `_compute_delivery_date()` now splits invoices into posted and non-posted before running. Non-posted invoices work exactly as before. Posted invoices are skipped from the sync and simply keep their current value instead of taking the newly calculated one. `sale.order.effective_date` itself is untouched only its effect on an already-posted invoice is blocked. Result: Once an invoice is posted, its `delivery_date` now stays fixed no matter what happens with later deliveries on the same sale order. `effective_date` keeps updating normally either way, confirming the fix only affects the invoice. Verified with both a script and a manual UI test. opw-6409171 Forward-Port-Of: odoo/odoo#283069 Forward-Port-Of: odoo/odoo#280978
Fixes a calendar display issue where weekday labels could repeat incorrectly for users in time zones with daylight saving changes at midnight. This ensures day, week, and month calendar views show the correct sequence of weekdays, reducing confusion when scheduling around those dates.
Original PR description
Current behaviour: In the Calendar view (day/week/month scale), when the user's timezone observes a DST transition that starts exactly at local midnight (e.g. Africa/Cairo, since 2023), the day…
Current behaviour: In the Calendar view (day/week/month scale), when the user's timezone observes a DST transition that starts exactly at local midnight (e.g. Africa/Cairo, since 2023), the day column right after the transition gets the wrong weekday name, duplicating the previous day's name. For ex. it renders "... THU THU FRI ..." instead of "... THU FRI SAT ...", for the week surrounding April 30th 2027. To fix this we add 1 hour to the Date before reading its weekday/day from it, mirroring the workaround FullCalendar itself adopted for this same bug. It has no effect on any ordinary day (adding 1h to a correct local midnight stays within the same calendar day), and it cannot overshoot into the next day since no real-world DST gap exceeds that margin. Note: This is a known bug (https://github.com/fullcalendar/fullcalendar/issues/7633), fixed in FullCalendar v6.1.17, a major version ahead of the v4.4.0, so the fix can't be applied directly without a full library upgrade. opw-6370140 Forward-Port-Of: odoo/odoo#280253 Forward-Port-Of: odoo/odoo#279343
The profile viewer now displays the profile name even when only one profile is open. This makes it easier for users working across multiple browser tabs to identify the right profile at a glance.
Original PR description
The name of the profile is only displayed when the viewer has multiple profiles open. It's not displayed when opening a single profile. If you have many of them open in different browser tabs, the name would help knowing which is which. ## Before <img width="527" height="77" alt="image" src="https://github.com/user-attachments/assets/26706115-dc99-40af-82b1-29e017051403" /> ## After <img width="527" height="77" alt="image" src="https://github.com/user-attachments/assets/46fd85d9-3558-4b64-8fdf-52bf372f4c65" /> --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283180
Printer pairing now correctly checks the printer status after an internal method name changed. This helps IoT printer setup work reliably and removes duplicate checks that were no longer needed.
Original PR description
In odoo/odoo#244530, the method `print_status` in the linux printer driver was renamed to `status`, however it was not changed in the connection manager. This commit fixes the issue, and also removes redundant checks for the printer type (as these are already performed in the `status` method). --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283521
Manufacturing orders no longer show unnecessary consumption warnings for service or combo components. This prevents confusing alerts when validating production orders that include non-physical items, since those items are not consumed like goods.
Original PR description
Issue: Consumption Warnings were appearing for services in manufacturing orders, but services are not consumed therefore the warning makes no sense. Steps to reproduce: Create a bill of materials with at least 1 service type component line Create a manufacturing order for that product Validate the manufacturing order Why: Consumption warning was appearing because we have no amount of quantity of products of type service / combo. Since these types are not supposed to have a quantity in the same way goods do, this check does not make sense for products other than goods and therefore we should remove the check for products other than `'consu'` opw-6420932 Forward-Port-Of: odoo/odoo#278745
This fixes an internal database connection cleanup issue when a separate read-only replica database is configured. It helps prevent replica connections from being left open, improving reliability and resource management without changing user-facing workflows.
Original PR description
close_db matched readonly connections against the primary DSN. When db_replica_* differs, those connections were left open. 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#282671
The website analytics page now correctly shows Plausible Analytics data when opened, instead of incorrectly appearing disconnected. This restores access to website reporting in Odoo, including the free Plausible Analytics integration available on Odoo SaaS.
Original PR description
**Description** The website analytics page always appears as if the website is not connected to Plausible Analytics, even when the connection is actually in place. This means that the user can't read…
**Description** The website analytics page always appears as if the website is not connected to Plausible Analytics, even when the connection is actually in place. This means that the user can't read analytics directly from Odoo, and that the free Plausible Analytics offered is Odoo SaaS is not usable. **How to reproduce** Set up Plausible Analytics (it is active by default on SaaS) and visit `/odoo/website-analytics`, or go to "Reporting" -> "Analytics". **Origin** 1. `WebsiteDashboard` component fetches analytics data via an RPC call to `fetch_dashboard_data`. For how the component is built, the first call misses a `website_id` argument. 2. `fetch_dashboard_data` defaults to `self.env.website` when called without `website_id` argument. After [1], `self.env.website` is empty on `jsonrpc` routes, because this protocol should be URL-agnostic. As result, the `WebsiteDashboard` appears empty when first loaded, and data is restored when a website button is clicked on the top right. **Fix** 1. The argument `website_id` in `fetch_dashboard_data` is made optional. 2. `base.default_website` is used instead of `self.env.website`. `base.default_website` is guaranteed to exist and to point to the first website in the website sequence. [1]: https://github.com/odoo/odoo/commit/5eba3e99ebd4f928fc5de620a38ab127e95be890 task-6402001 Forward-Port-Of: odoo/odoo#282003
This fixes crashes and inconsistent selections in the timesheet assistant when saving or updating suggestions. Helpdesk and timesheet users should see a more reliable experience when working with timesheets from the assistant.
Original PR description
The `helpdesk_timesheet` override of `_getLocalConfigValsOnTake` called `this._is_record()`, a method that does not exist. This PR makes it call `_getResId` instead Task-6385031 Forward-Port-Of: odoo/enterprise#128675 Forward-Port-Of: odoo/enterprise#124075
Spreadsheet-related automated tests were adjusted to match recent icon changes. This helps keep quality checks reliable after the spreadsheet update, with no expected impact on daily users.
Original PR description
See https://github.com/odoo/odoo/pull/283915
German SEPA credit transfer files now exclude an unsupported company identifier when using the older German XML format. This keeps exported payment files compliant with bank requirements and helps prevent payment batches from being rejected.
Original PR description
### Issue before this commit: When generating a SEPA Credit Transfer batch using the German XML format (pain.001.001.03), the <LEI> tag is erroneously included in the exported file if an LEI is…
### Issue before this commit: When generating a SEPA Credit Transfer batch using the German XML format (pain.001.001.03), the <LEI> tag is erroneously included in the exported file if an LEI is configured on the company. This invalidates the XML, causing banks to reject the file. ### Steps to reproduce the issue: 1. Download Accounting and l10n_de 2. Go to Settins > Vendor Payments > SEPA Credit Transfer / ISO20022 and set Name Identification as 529900T8BM49AURSDO55 and Issuer as LEIMAN 3. Go to companies and set 529900T8BM49AURSDO55 as LEI in the DE company 4. Go to Accounting dashboard and click the 3 dots of the bank group, go to Configuration and set the Account Number and be sure in the Outgoing Payments tab XML Format is German 5. Create a new German company from Contacts with: 1. Country as Germany 2. VAT 3. Account Number in the Bank Accounts by adding one line: 1. example Account Number: DE65100500007201811026 2. example Bank: BNP Paribas 3. activate the Send Money button 7. Then go to Vendor > Payments and create a new one with Payment Method as SEPA Credit Transfer for the German company created 8. Go back and select the new payment from the list and click create batch and print it 9. In the XML of pain.001.001.03.(DE) file, the LEI tag should not be included. ### Cause of the issue: The XML generation logic does not filter out the <LEI> element for older schema versions like pain.001.001.03, which do not support this tag. ### Reason to introduce the fix: To ensure strict schema compliance and prevent bank rejections. The <LEI> element is now properly omitted from pain.001.001.03 files and restricted only to newer formats (e.g., pain.001.001.09) where it is valid. opw-6428150 Forward-Port-Of: odoo/enterprise#128709 Forward-Port-Of: odoo/enterprise#127758
Colombian contacts using the NIT identification type are now only classified as companies when a VAT number is provided. This prevents individual child contacts from being incorrectly created as companies, improving contact accuracy for Colombian records.
Original PR description
Before this change: When creating a child contact under a Colombian company from another company context, the identification type defaults to NIT. The system evaluated the child contact as a company immediately, regardless of whether a VAT number was entered, preventing proper individual contact creation. To reproduce: 1. Create a new contact and set the country to Colombia. 2. Set the identification type to NIT. 3. Leave the VAT field empty. 4. Observe that the "Is a Company" checkbox becomes checked automatically. After this change: The company computation logic explicitly verifies that a valid VAT number is present before evaluating NIT contacts as companies, allowing individual child contacts to retain their correct entity status. opw-6468848 Forward-Port-Of: odoo/enterprise#128656
This fix ensures that Peruvian electronic invoices with untaxed lines show the correct validation error during batch sending instead of causing a generic failure. It prevents one problematic invoice from blocking the background process from handling other invoices, improving reliability for SUNAT submissions.
Original PR description
In l10n_pe_edi, invoices containing lines without tax can't be submitted to SUNAT.
When sending a single invoice, an error message is displayed. However, sending multiple invoices processes them in the background by a cron job. In this case, EDI document creation fails without error handling, raising a generic parsing error and blocking the cron from processing other invoices.
Steps to reproduce:
1. Create and post two invoices with no tax on some lines.
2. From the list view, select both invoices and click "Send" and mark "SUNAT".
3. An exception is raised: `ValueError: XMLSyntaxError("Start tag expected, '<' not found, line 1, column 1")`.
opw-6390480
Forward-Port-Of: odoo/enterprise#127990
Forward-Port-Of: odoo/enterprise#125143This fixes a test instability in Peru electronic invoicing caused by small spelling differences from different library versions. It helps keep automated validation reliable across supported Python environments without changing business behavior.
Original PR description
### Issue: `test_invoice_down_payment_with_withholding_tax` fails on RunBot when using `num2words==0.5.10` (Python < 3.12) The expected XML contains `DIECISÉIS` but older versions of `num2words` generate `DIECISEIS` without the accent ### Cause: The accent on `DIECISÉIS` was added in `num2words` PR #443, between versions `0.5.10` and `0.5.13` RunBot uses different versions depending on the Python version: `num2words==0.5.10` for Python < 3.12 (Jammy / Bookworm) `num2words==0.5.13` for Python >= 3.12 ### Steps to reproduce: - Run the test with `num2words==0.5.10` Before the fix, the test fails on the `cbc:Note` comparison runbot-945461 Forward-Port-Of: odoo/enterprise#127356
Colombian point-of-sale orders that include combo products now send compliant electronic invoice data to DIAN. This prevents card-paid combo sales from being rejected because zero-priced combo parent lines were included in the report.
Original PR description
Issue: When ordering through POS combo items won't be accepted by DIAN. Steps to reproduce: Set company to Colombia and activate the DIAN module. Simulate a sell of an combo item with POS. Pay with card. Error will ensue. Cause: The XML sent to DIAN is not accepted because one of the items has 0 price (the combo item). Solution: Not sending lines that are combo items. opw-6232599 Forward-Port-Of: odoo/enterprise#123928 Forward-Port-Of: odoo/enterprise#119652
This fix ensures that a customer manually assigned to a planning or field service shift is kept when the shift is started, signed in, or completed. It prevents the shift customer from being unintentionally replaced or removed based on the linked sales order, improving reliability for scheduling and field service workflows.
Original PR description
Before this commit, when `sale_planning` module is installed after `planning_field_service` and the user sets a customer onto a shift, the customer could be removed when the user signs in or complete the shift. This issue is because `sale_planning` module defined `partner_id` field as a related field `related="sale_order_id.partner"` and `planning_field_service` module stores the field and so the field will always follows the partner set on the SO linked even if the user sets a customer on the shift. This commit removes the related attribute to replace it by a compute and a search method to have the exact same behavior but the search method will be short-circuited if the partner_id field is stored. task-5264800 Forward-Port-Of: odoo/enterprise#128417 Forward-Port-Of: odoo/enterprise#122034
Users working in French and other translated languages can now download the General Ledger report as a CSV without the export failing. The fix ensures translations are handled through the active export process, preventing the error that stopped the file from being generated.
Original PR description
Currently an exception is generated when the user tries to export (downlaod) a `CSV` file of the `General Ledger` report as the below step: - Install the `accountant` module with demo data - Enable…
Currently an exception is generated when the user tries to export (downlaod) a `CSV` file of the `General Ledger` report as the below step: - Install the `accountant` module with demo data - Enable and change to the `French` language - Go to `Comptabilité` > `Analyse` > `General Ledger` - Click the cog icon in the menu > Click on `CSV` - An error occurs in the log, and nothing is downloaded Error: `TypeError: 'NoneType' object is not subscriptable` This issue occurs after the recent refactoring changes in [1]. When `_generate_csv_lazy_export` is called, it uses the `_()` method for translation, which accesses `self.env`. However, at this point, the cursor is already closed. As a result, when the code at [2] is reached from `ormcache`, it raises the above error because `model.env.transaction.ormcaches__` is `None` (code ref [3]). This commit fixes the above issue by performing the translation using the existing `handler` variable, which contains an environment with the new cursor (see code ref [4]). [1]: https://github.com/odoo/odoo/commit/13c3adf3a8b5ba6325190d6b9aea45fb8a6a8b2f [2]: https://github.com/odoo/odoo/blob/5ef7829895b2e05650da394c1e35dfdc3a23c066/odoo/orm/cache.py#L111 [3]: https://github.com/odoo/odoo/blob/5ef7829895b2e05650da394c1e35dfdc3a23c066/odoo/orm/environments.py#L1012 [4]: https://github.com/odoo/enterprise/blob/912a47b8f0828ef8316b7e4ecdabf8a2f305b313/account_reports/models/account_general_ledger.py#L524 Sentry-7608119520 Forward-Port-Of: odoo/enterprise#126778
When payroll teams include additional unpaid payslips in a SEPA payment file, those payslips are now correctly marked as paid after confirming the payment. This prevents paid employees' payslips from incorrectly remaining in a validated but unpaid status.
Original PR description
Steps to reproduce: - Open the payment report wizard on a payslip or a pay run - Tick "Include Unpaid" and keep the extra payslips selected - Generate the SEPA file, then click "Mark as Paid" Issue: the extra payslips listed in the file stay in state "validated". Cause: mark_as_paid() paid payslip_ids, while the file is built from unpaid_payslips. Fix: pay the payslips that are actually listed in the file. Task 6428919 Forward-Port-Of: odoo/enterprise#127889
The timesheet timer no longer disappears when the server reaches UTC midnight while it is still the previous day for the user. This helps employees in non-UTC time zones keep accurate running timers until their own local midnight.
Original PR description
**Problem:** The running timesheet timer in the systray stops when the Odoo server clock passes UTC midnight, even though it is still the same day in the user's own timezone. **Steps to reproduce:**…
**Problem:** The running timesheet timer in the systray stops when the Odoo server clock passes UTC midnight, even though it is still the same day in the user's own timezone. **Steps to reproduce:** 1. Set the user's timezone to one behind UTC (e.g. America/Guadeloupe, UTC-4). 2. Start a timesheet timer while it is before local midnight but after the server has passed UTC midnight (e.g. 20:00 local = 00:00 UTC). 3. Look at the running timer in the systray. **Current behavior:** At server (UTC) midnight the running timer disappears and its ongoing count is lost. **Expected behavior:** The timer keeps running until the user's own local midnight, regardless of the server timezone. **Cause of the issue:** The systray controller derives its reference day from `date.today()`, which returns the server's (UTC) local date. The running timer's timesheet is created dated the user's local day (`hr_timesheet` uses `fields.Date.context_today`). Once the server crosses UTC midnight, `date.today()` advances to the next day while the user's local day has not, so `timesheet_systray_user_data` (searching `date == today`) and `get_timer_start_time` (searching `date` within today's bounds) no longer match the running timesheet, and the systray reports no running timer. **Fix:** Deriving the reference day from `fields.Date.context_today` aligns the systray's notion of "today" with the timezone the timesheet was recorded in, so the timer is tied to the user's local day rather than the server's. This keeps recording and retrieval consistent, since the timesheet is already dated with the user-local day on creation. opw-6343442 Forward-Port-Of: odoo/enterprise#125635
Philippine payroll now uses the proper salary bases when calculating mandatory SSS and Pag-IBIG monthly contributions. This helps ensure employee deductions and employer payroll obligations are computed accurately, with tests updated to confirm the corrected behavior.
Original PR description
. SSS Mandatory contribution is categories['TAX_CASH_EARNINGS'] . Pag-IBIG Contribution is categories['PH_BASIC'] + categories['ECOLA'] . Update the corresponding tests task-6431960 Forward-Port-Of: odoo/enterprise#126527
Adds automated coverage to ensure Italian VAT report carryover values are handled correctly. This helps prevent regressions in tax reporting for Italian localization users.
Original PR description
Add test for https://github.com/odoo/odoo/pull/279471 opw-6354509 Forward-Port-Of: odoo/enterprise#127429
Fixes an issue in Accounting where resetting a bank statement line to draft could trigger an error instead of completing normally. This helps accounting users correct or reopen bank transactions without being blocked by a system failure.
Original PR description
Currently, an error occurs when resetting a **bank** statement line to draft. **Steps to Reproduce:** - Install the `Accounting` module. - Go to `Accounting Dashboard` and click the `three-dot` on…
Currently, an error occurs when resetting a **bank** statement line to draft. **Steps to Reproduce:** - Install the `Accounting` module. - Go to `Accounting Dashboard` and click the `three-dot` on bank journal. - Open `Transactions`. - Create a new `statement line`. - Select the `statement line`, click the `gear action`, and click `Reset to Draft`. `AttributeError: 'bool' object has no attribute 'setdefault'` After the [recent commit], when resetting the statement line to draft, the server action run [1] and the linked move is going reset to draft, and the method returns the result [2]. After the mentioned commit, the method returns True [3]. When the result from [4] is passed to clean_action, it raises an error [5]. This commit ensures that it returns None after resetting the statement line linked to the invoice to draft, as it previously returned None and same as like [6]. [recent commit]: https://github.com/odoo/odoo/commit/712718d9df0fd5044ac57fdd9ec58e64bece36c0 [1]- https://github.com/odoo/enterprise/blob/7ca28a1c079d22b60e3756ca9b4f404771f214e8/account_accountant/views/bank_rec_widget_views.xml#L541-L551 [2]- https://github.com/odoo/enterprise/blob/eae51e3ca155ca29e1de54f4bd223e7540b2aa7f/account_accountant/models/account_bank_statement.py#L112-L114 [3]: https://github.com/odoo/odoo/blob/b3444e4bd421a30229b0dc7c8d5eb6c78b8b51ed/addons/account/models/account_move.py#L6236-L6251 [4]: https://github.com/odoo/odoo/blob/a6f99706c6a62fc65666a0ff5e58fa465b41a6fb/addons/web/controllers/action.py#L53-L59 [5]: https://github.com/odoo/odoo/blob/b3444e4bd421a30229b0dc7c8d5eb6c78b8b51ed/addons/web/controllers/utils.py#L24 [6]: https://github.com/odoo/odoo/blob/d7df2e8acff9eb7066993fa6a0b0c6d7c85baabc/addons/account/models/account_payment.py#L1206-L1208 sentry-7354160052 Forward-Port-Of: odoo/enterprise#127189
Restaurant preparation display cards now show the assigned course name, such as Main Course, instead of only a course number when course allocation is enabled. This makes kitchen order information clearer and helps staff identify preparation stages more easily.
Original PR description
Before this commit: ====================== Prep cards showed the course index, like T1 - C2, even when course allocation was enabled. After this commit: ==================== Prep cards show the allocated course name, like T1 - Main Course, when course allocation is enabled. Task-6421309 Forward-Port-Of: odoo/enterprise#125744
The General Ledger now avoids showing misleading foreign currency amounts when companies with different currencies share the same chart of accounts. Initial balances will no longer combine amounts from different currencies, helping finance teams trust multi-company reports.
Original PR description
**Steps to reproduce:** * Install the **Accounting** module. * Create two companies with different currencies (e.g. **USD** and **CAD**) sharing the same **Chart of Accounts**. * Open a shared…
**Steps to reproduce:** * Install the **Accounting** module. * Create two companies with different currencies (e.g. **USD** and **CAD**) sharing the same **Chart of Accounts**. * Open a shared account and: * Add both companies in the **Company** field. * Under the **Mappings** tab, configure a mapping for each company. * In each company, create and post a journal entry on the same shared account (for example, a receivable account) using the company's own currency. * Set the journal entry dates to the **current month**. * Open **Accounting → Reporting → General Ledger**. * Change the reporting period to the **following month** so the posted entries are shown as the **Initial Balance**. * Open the report separately for each company. **Observed behavior:** * From the **CAD company**, the Initial Balance displays **USD 2,000** instead of the expected **USD 1,000**. * From the **USD company**, the **Currency** column on the Initial Balance is **blank**. **Cause:** * The SQL query for the `id_with_accumulated_balance` groupby used `SUM(amount_currency)` and `MIN(currency_id)` to aggregate all pre-period lines into a single Initial Balance row. * In a multi-company shared Chart of Accounts, lines from different companies (each with their own currency) were collapsed into the same group, causing `SUM(amount_currency)` to add amounts across currencies and `MIN(currency_id)` to return an arbitrary currency ID. * Additionally, the Python accumulation loop incorrectly performed **integer addition** on `currency_id` (a foreign key), further corrupting the displayed currency. **Fix:** * Replace `SUM(amount_currency)` and `MIN(currency_id)` with `CASE` expressions `MIN = MAX` is a uniformity check that works for **any number of currencies**: if every row in the group shares the same currency the condition is true and the correct sum is returned; if even one row differs the condition is false and both fields return `NULL`. The original three-column `GROUP BY (id, date, account_id)` is preserved. * The Initial Balance row now correctly shows a **blank** currency column, consistent with the Odoo 18 behavior, instead of an incorrect aggregated foreign currency amount. opw-6375310 Forward-Port-Of: odoo/enterprise#124823
Odoo now avoids syncing orders for TikTok shops that have not completed authorization. This prevents scheduled order imports from failing when a shop connection is still pending, keeping TikTok sales synchronization stable for authorized shops.
Original PR description
Currently, an error occurs when orders are being fetched from shops with pending authorization. Steps to replicate: - Install `sale_tiktok`. - Open Sales > Configuration > Shops (Under the title…
Currently, an error occurs when orders are being fetched from shops with pending authorization.
Steps to replicate:
- Install `sale_tiktok`.
- Open Sales > Configuration > Shops (Under the title tiktok shops).
- Click `Connect New Shop` > Give values for `App key, App secret, Service ID`.
- Click `Connect Shop & Authorize` and then Return back to Odoo.
- Run the Scheduled Action `TikTok Shop: sync orders`.
Error:
```
File '/home/odoo/src/enterprise/saas-19.4/sale_tiktok/utils.py', line 171, in make_tiktok_api_request
if now > shop.access_token_expire_datetime - timedelta(minutes=5):
TypeError: unsupported operand type(s) for -: 'bool' and 'datetime.timedelta'
ValueError: TypeError('unsupported operand type(s) for -: 'bool' and 'datetime.timedelta'') while evaluating
'model._sync_orders()'
```
Cause:
- Since the shop has not yet been authorized with TikTok, the `access_token_expire_datetime` field is not set. This field is only populated after the shop is successfully authorized (see [this]).
- Later, when the `TikTok Shop: sync orders` cron runs, the flow reaches [here], where we checks whether the access token is expired and needs to be refreshed. At this point, `access_token_expire_datetime` is still False because the shop has not been authorized yet.
Solution:
- The orders should only be fetched from those shops that are authorized with TikTok.
- Used the `access_token` field to determine whether a shop is authorized, as it is only populated after the authorization flow is successfully completed.
[this]: https://github.com/odoo/enterprise/blob/593409ca160863d3e985f3cf5e2aadda1d450b41/sale_tiktok/controllers/onboarding.py#L52-L54
[here]: https://github.com/odoo/enterprise/blob/593409ca160863d3e985f3cf5e2aadda1d450b41/sale_tiktok/utils.py#L171
sentry-7631179329
Forward-Port-Of: odoo/enterprise#127148Payroll processing now ignores time off requests that were refused after being marked for review on a future payslip. This prevents HR teams from seeing unnecessary pay run errors or review items for absences that are no longer valid.
Original PR description
Have a payslip created and validated in a period. Have a unprocessed payrun in the same period. Create a time off that is automatically validated, it will have a Payslip State set to 'To defer to next payslip' as it is required to be deferred. The HR team decide to refuse the time off for any reason, before processing the pay run. Now, while processing the pay run, the refused time off 'to defer' will trigger an error and show in the 'time off to review'. This is not correct, as the refused time off should not have any effect on a payrun. This fix backports another fix done in 19.5 and add a test to assert the fix. Backport of https://github.com/odoo/enterprise/commit/a5e78deee10a772b628dd60a84dfe2df9cd52cd2 Task 6484459 Forward-Port-Of: odoo/enterprise#128543
Australian payroll batches now handle employees with no leave allocations correctly. This prevents batch payslip creation from failing when employees with and without unused leave are processed together.
Original PR description
Creating a batch of payslips mixing employees with and without leave allocations raises a KeyError: ``` File "l10n_au_hr_payroll/models/hr_payslip.py", line 869, in _add_unused_leaves_to_payslip…
Creating a batch of payslips mixing employees with and without leave allocations raises a KeyError:
```
File "l10n_au_hr_payroll/models/hr_payslip.py", line 869, in _add_unused_leaves_to_payslip
annual_gross = leaves_totals[payslip.id]['annual'] * daily_wage
~~~~~~~~~~~~~^^^^^^^^^^^^
KeyError: 22
```
Current Issue:
`_l10n_au_get_unused_leave_by_type` only materialises leaves_by_date[payslip.id] inside the allocation loop, so a payslip whose employee has no matching allocation never gets a key. `_l10n_au_get_unused_leave_totals` then rebuilt a plain dict out of those entries and only fell back to a defaultdict when leaves_by_date was completely empty. A mixed batch is not empty, so the plain dict was returned and `_add_unused_leaves_to_payslip` raised on the payslips that were missing from it.
This never showed up in the UI, **where payslips are created one at a time**: a single slip either has an allocation, or produces an empty mapping that hits the fallback.
Approach:
Build the totals on a defaultdict and update it instead of returning a plain dict, so any payslip without allocation resolves to 0 rather than being absent. This also drops the need for the empty special case, and keeps the mapping consistent with the defaultdict returned by `_l10n_au_get_unused_leave_by_type`, which `_l10n_au_get_leaves_for_withhold` indexes the same way.
task-6465229
Forward-Port-Of: odoo/enterprise#127623The salary calculator now keeps simulations separate from an employee's existing draft payslip. This prevents fields from being cleared and avoids misleading missing-field errors when payroll teams test salary scenarios.
Original PR description
Steps:- 1. Navigate to Payroll->Employees menu->Salary Calculator 2. Select Employee who already have a draft payslip. 3. You will see all the fields will get emptied and give "Missing required fields". Root cause:- Opening the salary simulator temporarily writes the simulated values onto the employee's record. If that employee already had a draft payslip, this write also refreshed that payslip behind the scenes, even though the payslip had nothing to do with the simulation. Fix:- Mark the simulation clearly as a simulation so it no longer refreshes the employee's existing payslip. task-6392171 Forward-Port-Of: odoo/enterprise#128403 Forward-Port-Of: odoo/enterprise#127223
This fixes an issue where users could not search for Twitter/X accounts by name when adding mentions to a social post. The mention search now uses the correct user lookup behavior, making it easier to compose posts accurately.
Original PR description
Bug === We cannot search user by name when mentioning in a post for Twitter. In cdc2bd4bff93e5081858c4f7e2e08061131f6e2d we changed the endpoint to search users, but in f77c3a673129aeafbd32ded95cd207dd878901e4 we used the method like if it was the old code). Task-6425391 Forward-Port-Of: odoo/enterprise#125803
Opening spreadsheet version history now uses the correct type of database access when contributor details need to be updated. This avoids an unnecessary retry behind the scenes, making the action more reliable and efficient for users.
Original PR description
The get_spreadsheet_history method is marked as readonly, causing RPC requests to use a read-only transaction. However, retrieving the metadata of a document spreadsheet updates its spreadsheet contributors. Opening the version history consequently attempts an UPDATE in a read-only transaction and forces the request to be retried with a read-write cursor. Remove the readonly decorator so the request uses a read-write cursor directly. Task-6176364 Forward-Port-Of: odoo/enterprise#126626
This fixes issues in the Social app where liking a tweet could crash, Facebook likes did not refresh correctly, edited comments did not show immediately, and duplicate images could appear in post views. Users get a smoother, more reliable experience when managing social media interactions from Odoo.
Original PR description
Bugs ==== When liking a Tweet in the feed view, a traceback is raised. Since https://github.com/odoo/odoo/commit/c0c82927f2e7 the likes does not update in the comments modal, `record` is now a snapshot of the kanban record (and so the template is not reactive). When editing a comment, we need to close / re-open the modal to see the change. This is because we try to change the prop from inside the component. Since https://github.com/odoo/enterprise/commit/89d87b7177e20ba1d3daee791d5ff55af0fafaf0 we have many records per media, and so the duplication check should have been updated. Task-6425391 Forward-Port-Of: odoo/enterprise#125639
Accounting users without Point of Sale access can now export Spanish VAT record books that include POS transactions. The report safely reads the required POS information in the background, preventing access errors and allowing finance teams to complete tax reporting.
Original PR description
Steps to reproduce:
- With an ES Company
- Open a POS session, add product with tax and pay
- As a user with only accounting access
- Go to Accouting > Reporting > Tax report
- Select Generic Tax report
- Print "VAT record Books"
Issue:
An AccessError will raise
```
Access Error
You are not allowed to access 'Point of Sale Session' (pos.session) records.
This operation is allowed for the following groups:
- Point of Sale/User
Contact your administrator to request access if necessary.
```
Analysis:
Vat Record Books handler for POS needs to read pos.session and pos.order records. Currently, the action is performed with the rights of the user running the report, so accounting-only user face an error.
As POS records are only read internally to build the report, we add sudo call to get the data.
opw-5862529
Forward-Port-Of: odoo/enterprise#126590
Forward-Port-Of: odoo/enterprise#125980This fix prevents branch reconciliation settings from being incorrectly copied when matching internal transfers between a branch and its parent company. Users can now reconcile these transfers without company inconsistency errors, reducing manual corrections in accounting workflows.
Original PR description
When reconciling an internal transfer between a branch and its parent company, an User Error is raised if the branch transaction has been processed via reconciliation model. Steps to reproduce: -…
When reconciling an internal transfer between a branch and its parent company, an User Error is raised if the branch transaction has been processed via reconciliation model. Steps to reproduce: - Have a company with branch both selected - On the branch, create a reconciliation model "Internal transfer" that assigns the whole balance to the liquidity transfer account - Have a Bank journal on the company and a Bank journal on the branch - On the branch bank journal, create a -100 transaction 'testb' and reconcile it using the branch internal transfer model - On the company bank journal, creata a 100 transaction, open the reconciliation widget and select the branch transaction to match it Issue: The reconciliation is refused with a company inconsistency error ``` Uh-oh! You’ve got some company inconsistencies here: - “BNK1/2026/00011 test” belongs to company “YourCompany” while “Reconciliation Model” (reconcile_model_id: 'Internal Transfer branch') belongs to another company. To avoid a mess, no company crossover is allowed! ``` However, if user manually assign the transfer account to the branch transaction, the reconciliation proceed as expected Analysis: When reconciling, we build the counterpart journal item by cloning the values of the matched move line, copying also the reconcile model. That field is company dependent and flagged copy=False, so it should not be propagated. opw-6365856 Forward-Port-Of: odoo/enterprise#127517
German Datev ATCH exports now work when an invoice's main attachment was added through a log note. This prevents accounting users from being blocked by an access error when exporting supporting invoice attachments.
Original PR description
### Issue: When an invoice has an image as its main attachment added via a log note, any non-admin user who did not create the attachment gets an `AccessError` when exporting the Datev ATCH zip ###…
### Issue: When an invoice has an image as its main attachment added via a log note, any non-admin user who did not create the attachment gets an `AccessError` when exporting the Datev ATCH zip ### Cause: Since commit `e7c93e5a6f`, attachments uploaded via certain flows can be "orphaned" — their `res_model` is set to `False` and `res_id` to `0` via `_fix_attachments_on_record_from_files_data` This allows the attachment to appear in the chatter without being linked to the move's attachment list However, `_message_set_main_attachment_id` can still set such an orphaned attachment as `message_main_attachment_id` When a user without system rights tries to read it, the ORM access check uses `res_model=False` and `res_id=0`, which does not match the move the user has access to, raising an `AccessError` ### Steps to reproduce: - Install `l10n_de_reports` and switch to the DE company - Create and confirm an Invoice (any lines, any customer) - Add a Log Note with an image - Set Demo user's Accounting rights to `Invoicing & Banks` - Log in as Demo - Open the General Ledger - In the cog menu, choose `Datev ATCH (zip)` Before the fix, an `AccessError` is raised opw-6397804 Forward-Port-Of: odoo/enterprise#128484 Forward-Port-Of: odoo/enterprise#127787