Thursday, September 24, 2026
22 changes · master
Resolved issues and error corrections
Cancelling accounting entries that were still in draft now deletes them instead of creating reversal entries. This avoids recording financial amounts that were never actually posted, especially when periods have already been locked.
Original PR description
_unlink_or_reverse relies on _can_be_unlinked, which only looks at the hash and the lock dates. A draft entry dated in a locked period thus lands in the reversal branch: it is copied, the copy is posted after the lock date and the draft stays behind. The reversal books amounts that were never posted in the first place. Lock dates protect what has been booked. An entry that was never posted can be deleted whatever its date. Seen with hr_payroll_account when a payslip is cancelled after the accountant locked the period without posting the salary entry. Any caller of _unlink_or_reverse (assets, loans, deferred entries) behaves the same. Forward-Port-Of: odoo/odoo#290210 Forward-Port-Of: odoo/odoo#289980
When a Belgian partner's VAT number is changed, the related BCE/KBO identifier is now updated automatically instead of keeping the old value. This keeps business identity data consistent and helps dependent services, such as electronic invoicing endpoints, use the correct information.
Original PR description
Steps to reproduce: * Install **Accounting** module. * Create a Belgian partner. * Assign a VAT number (e.g. BE0477472701). * The BCE/KBO field in Multi ID is correctly populated. * Modify the VAT…
Steps to reproduce:
* Install **Accounting** module.
* Create a Belgian partner.
* Assign a VAT number (e.g. BE0477472701).
* The BCE/KBO field in Multi ID is correctly populated.
* Modify the VAT number.
Observed behavior:
* The BCE/KBO (BE_EN) in Multi ID keeps the old value.
Cause:
* `_deduce_additional_identifiers_from_vat` skipped any identifier key that already existed in `additional_identifiers`: new_identifiers = {k: v for k, v in deduced.items() if k not in identifiers}
* On the first VAT save, BE_EN is written correctly.
* On subsequent VAT changes, BE_EN already exists, so the condition short-circuits and the old value is preserved.
Fix:
* Replace the key-presence guard with a value-equality check so that deduced identifiers are always overwritten when their value would change: updated = {k: v for k, v in deduced.items() if identifiers.get(k) != v}
* Since BE_EN is fully derived from the VAT (it is the VAT with the country prefix stripped), it must always mirror the current VAT.
* The Peppol endpoint is already declared as depending on `additional_identifiers`, so it recomputes automatically once BE_EN is corrected — no further change needed there.
opw-6545770
Forward-Port-Of: odoo/odoo#290348
Forward-Port-Of: odoo/odoo#288057Users will no longer receive both an in-app browser alert and a native push notification for the same mention when their Odoo tab is open but not focused. This reduces notification noise and keeps alerts consistent with the user's notification preferences.
Original PR description
Steps to reproduce: - Grant browser notification permission (subscribes to web push). - Set user A's `notification_type` to `inbox` in preferences and reload. - Keep A's tab open but unfocused. - As user B, post an @-mention on a non-channel chatter record targeting A. A receives two alerts: the JS one from the `mail.message/inbox` bus event and the native notification from web push. `OutOfFocusService.notify()` gated JS skip on `message.thread.model`, but the server never sends `mail.thread`, so chatter fell through. Replace it with a `message_type` + `!isSelfAuthored` filter mirroring the server side `_notify_get_recipients_for_extra_notifications`. Also swap the `isInbox` SW handshake workaround added by [1] when the user is busy. [1]: https://github.com/odoo/odoo/pull/232431 Forward-Port-Of: odoo/odoo#290410 Forward-Port-Of: odoo/odoo#282981
This fix ensures amounts in currencies without decimal places, such as Japanese yen, display the full number instead of dropping meaningful zeroes. It prevents confusing monetary summaries like 100 appearing as 1, improving accuracy in financial information shown to users.
Original PR description
Description of the issue/feature this PR addresses: In Odoo 19.0, format_amount(..., trailing_zeroes=False) removes significant integer zeroes when the currency has no decimal places, such as JPY.…
Description of the issue/feature this PR addresses:
In Odoo 19.0, format_amount(..., trailing_zeroes=False) removes
significant integer zeroes when the currency has no decimal places,
such as JPY. Project update monetary summaries use this formatter
with trailing_zeroes disabled.
Steps to reproduce in an Odoo shell with the default JPY configuration:
```python
from odoo.tools.misc import format_amount
currency = env.ref('base.JPY')
format_amount(env, 100, currency, trailing_zeroes=False)
format_amount(env, 0, currency, trailing_zeroes=False)
```
Current behavior before PR:
The numeric part of 100 becomes 1, and the numeric part of 0
disappears. Grouped amounts can also lose digits and leave an
incomplete thousands group.
Desired behavior after PR is merged:
Preserve all integer digits for currencies without decimal places.
Only strip trailing zeroes when a fractional part is present.
Regression coverage includes zero, positive, negative and grouped
amounts, both currency symbol positions, and languages with distinct
or identical decimal and thousands separators.
Validation:
- Before the fix: the new regression test fails in 20 subcases.
- After the fix: all 9 tests in TestFormatAmountFunction and
TestFormatLangDate pass on an isolated PostgreSQL 16 database.
- git diff --check passes.
Test selection:
--test-tags=/base:TestFormatAmountFunction,/base:TestFormatLangDate
This PR also includes my signed Individual Contributor License
Agreement in doc/cla/individual/ysnkucuker.md.
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#290374Hungarian electronic invoice XML sent to NAV no longer includes cash rounding as a separate invoice line. This keeps reported invoices aligned with Hungarian legal guidance and prevents rounding differences from being treated like taxable goods or services.
Original PR description
Global cash rounding can be applied to customer invoices. Before this commit, the rounding would be included in the XML file sent to NAV. It would be included as a new invoice line (same as the products lines) and the ATK tax is applied on it. As stated in the legal Hungarian Documentation, an invoice line should always relate to the supply of a good or the service provided. In this case, a cash rounding (which is not a financial advantage or disadvantage) will be considered by the law as a settlement difference, that is not part of the invoice. So, this commit removes cash rounding lines from the NAV XML. Moreover, it uses base_lines for the amounts computation instead of line_ids. task-6527383 Forward-Port-Of: odoo/odoo#290375 Forward-Port-Of: odoo/odoo#286258
This fixes reconciliation cases where no exchange difference is needed by ensuring the company currency is used. It helps prevent small remaining balances from being left in company currency after reconciling foreign-currency entries.
Original PR description
When doing reconciliation with no exchange difference, the currency used should always be the company currency. Using foreign currency in that case could leave residual amounts in company currency. task-6582723 Forward-Port-Of: odoo/odoo#289237
The Source PO button now appears only on the subcontractor resupply receipt where it is relevant. This prevents unrelated purchase receipts from showing misleading links, helping users identify the correct purchase order in subcontracting workflows.
Original PR description
After a recent change, the smart button for Source POs uses the reference_ids on a stock.picking. This altered the logic to fetch all orders from any picking that has the same reference. However,…
After a recent change, the smart button for Source POs uses the reference_ids on a stock.picking. This altered the logic to fetch all orders from any picking that has the same reference. However, `subcontracting_source_purchase_count` should specifically relate to the resupply of subcontractor picking to their subcontracted purchase order. **Steps to Reproduce:** 1. Enable multi-step routes and subcontracting 2. Unarchive the MTO route 3. Create 4 products Finished Product, A, B, C, and set MTO on both A and B 4. Put 10 units of C in stock 5. Create a BoM for Finished Product using A and B as components 6. Create a subcontracted BoM for B using C as a component 7. Set the purchase vendor for product A as the vendor 8. Set the purchase vendor for product B as the subcontractor 9. Create and confirm a manufacturing order for Finished Product 10. Confirm the POs The two POs are: 1. Purchase Product A from its vendor 2. Subcontract Product B from its subcontractor and use Product C as a component **Current Functionality:** - Product A's receipt has a **Source PO** smart button pointing to the subcontracting PO for Product B (BUG) - Product B's receipt has a **Source PO** smart button pointing to its own PO (BUG) - Product C's picking has a **Source PO** smart button pointing to its own PO **New functionality:** - Product A's receipt has no **Source PO** smart button - Product B's receipt has no **Source PO** smart button - Product C's receipt has a **Source PO** smart button pointing to its own PO opw-6420168 Forward-Port-Of: odoo/odoo#289710 Forward-Port-Of: odoo/odoo#278908
This fixes a mobile display issue in Point of Sale where order search filter options, such as Date or Customer, were hidden behind the order list. Mobile users can now choose the correct search field reliably, making it easier to find past orders on small screens.
Original PR description
Steps to reproduce: - Open a PoS session on a small screen (mobile app or mobile browser) - Register at least one order, so the order list is not empty - Go to the Orders screen and type a term in…
Steps to reproduce: - Open a PoS session on a small screen (mobile app or mobile browser) - Register at least one order, so the order list is not empty - Go to the Orders screen and type a term in the search bar Issue: On a desktop the search bar drops down the list of fields to search on (Reference, Receipt Number, Invoice Number, Date, Customer). On a small screen that list never shows up, so the search silently falls back to the first field and there is no way to search by Date or by Customer. Cause: The list is rendered, but painted behind the order list. Under the media-breakpoint-down(sm) block of ticket_screen.scss the order list becomes `position: sticky; z-index: 1`, so a sibling rule raised `.search .fields` to `z-index: 2` to keep the dropdown on top. The `z-1` utility class put on that dropdown in 07f743843830 compiles to `z-index: 1 !important` and overrides the rule. Both elements end up at `z-index: 1` in the same stacking context, and the order list wins the paint order because it comes later in the DOM. Fix: Drop the `z-1` utility and declare `z-index: 2` on `.fields` in the search bar's own stylesheet, which makes the small-screen override in ticket_screen.scss redundant. The stacking of the dropdown now lives in a single place, next to the rest of its styling, so a utility class added to that element cannot silently disable it again. A tour clicks its target element directly and so cannot see a purely visual overlap, which is why the existing MobileTestUi runs of TicketScreen.search() never caught this. The added assertion checks that a suggestion is the topmost element at its own center. opw-6540466 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#289082 Forward-Port-Of: odoo/odoo#286983
Company-paid expenses created from a project now apply the project analytic allocation only to the actual expense line, not to balancing lines. This prevents project expense reporting from incorrectly cancelling itself out and gives businesses more accurate project cost visibility.
Original PR description
### Current behavior: Creating a company-paid expense from the Project overview posts a journal entry with the project analytic on both the expense and outstanding lines, so the analytic balance nets to zero ### Expected behavior: Analytic distribution should only be on the P&L (expense) line ### Steps to reproduce: 1. Open a project overview and create a company-paid expense 2. Submit, approve, and post it 3. Open the journal entry: analytic is on debit and credit lines ### Cause of the issue: `project_id` stays in the context after `clean_context` during `_create_company_paid_moves`. With `sale_project`, AML analytic compute then applies the project distribution to outstanding/tax lines as well ### Fix: Removed `project_id` from the context when creating company-paid moves opw-6368848 Forward-Port-Of: odoo/odoo#287299 Forward-Port-Of: odoo/odoo#280256
This fixes an issue where product images could disappear when importing or creating several variants of the same product at once. Businesses can now rely on bulk product imports to keep images on the product and its variants, reducing manual cleanup after catalog updates.
Original PR description
Steps to reproduce: - Import a product CSV with one row per attribute value ("Product Values" column) and an image URL on every row, or create several variants of the same template in one `create()`…
Steps to reproduce:
- Import a product CSV with one row per attribute value ("Product Values" column) and an image URL on every row, or create several variants of the same template in one `create()` call with an image.
- Open the product: neither the template nor the variants have an image. Products without attribute values imported in the same file are fine.
The import creates all variants of a template in a single batch. The inverse of `image_1920` is applied record by record: the first variant finds no image on the template and writes its image there. Writing `image_1920` on the template invalidates the image cache of every product.product, including the sibling variants being created, whose `image_1920` is protected during the inverse and therefore reads back as empty. The next variant then takes the "clear the image from the template" branch and writes `False` on the template, undoing the first write. Nothing is stored.
Read the value to inverse for every record before writing anything, so the invalidation triggered by the template write cannot change what the following records write.
opw-6545651
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#290171
Forward-Port-Of: odoo/odoo#287419Shipment cancellations now send each delivery carrier only the shipments that belong to it. This prevents duplicate cancellation attempts and reduces the risk of carrier API errors or cancelling the wrong shipment.
Original PR description
When cancelling shipments for multiple pickings with different delivery carriers, passing the entire recordset (self) instead of the individual picking to the carrier's cancel_shipment method causes each carrier to receive tracking references that do not belong to it. This results in API errors or silent wrong cancellations at the carrier level, and each shipment being cancelled N times instead of once, where N is the total number of pickings being processed. Forward-Port-Of: odoo/odoo#289809
Fixes an issue where printing labels from the stock allocation report could produce too few labels after partial allocations. The label count now uses the most up-to-date allocation data, helping warehouse teams print the correct number of product labels.
Original PR description
Issue ===== Sometime, when printing product's labels from the allocation report, some labels are missing, the print file doesn't have the right amount. How to reproduce ================ 1. Create a…
Issue
=====
Sometime, when printing product's labels from the allocation report, some labels are missing, the print file doesn't have the right amount.
How to reproduce
================
1. Create a stored product;
2. For this product, create and confirm 3 deliveries (or sale orders):
- 1 for 4 units;
- 1 for 5 units;
- 1 for 6 units;
3. Create and confirm a receipt for 10 units;
4. From this receipt, click on "Allocation" to open the allocation report;
5. In this order, assign:
- 4 units;
- 5 units;
- 1 / 6 units; (Note that 4 + 5 + 1 = 10 units.)
6. Click on "Print Labels" => Only 9 labels are printed.
Cause of the issue & solution
=============================
In the `AllocationReport` `onClickPrintLabels` method, we compute the amount of label to print but this amount was computed based on not correctly updated values.
This commit changes that to base this compute on `productLinesById` which is correctly up to date.
Also, this commit fixes a minor issue by replacing `is_waiting` by `waiting` (when checking move's state) since `waiting` is the right string.
Issue reported during the v 20.0 logistic testing day by VBOL.
Forward-Port-Of: odoo/odoo#289676Time off requests for fully flexible employees now show the correct number of days when the request spans multiple days. This prevents multi-day leave, such as Monday to Friday, from being incorrectly displayed and processed as only one day, while still accounting for public holidays and half-day boundaries.
Original PR description
Steps to reproduce: ------------------------------------ 1. Install Time off module 2. Create New Fully Flexible Employee 3. Click on the Time off smart button 4. Create time off for multiple days…
Steps to reproduce: ------------------------------------ 1. Install Time off module 2. Create New Fully Flexible Employee 3. Click on the Time off smart button 4. Create time off for multiple days (eg. Mon - Friday) Observation: ------------------------------------ Number of days still shows 1 Days. Issue: ------------------------------------ Issue occurs because `work_time_per_day_mapped` returns one interval per day for standard and flexible schedules in multi-day time off requests, so the interval count correctly matches the number of leave days. https://github.com/odoo/odoo/blob/d73e5662a0af7c549008661f743ba5d51f765339/addons/hr_holidays/models/hr_leave.py#L460-L461 However, for fully flexible schedules, it returns a single interval containing the total hours across all days, causing the leave duration to always be computed as 1 day regardless of the actual number of days requested. Solution: ------------------------------------ For fully flexible employees, count the actual calendar days and subtract public holidays when applicable. opw-6060552 Forward-Port-Of: odoo/odoo#255826
This fix improves how Odoo handles searches that exclude certain values, especially when the search follows linked records. Users should see more complete and accurate results when filtering records across sales, inventory, HR, mail, website, and related areas.
Original PR description
Most of the time, when implementing a search method that resolves a
relation, the search method should not support negative operators.
Instead let the ORM inverse correctly the domain.
A domain `[('a.b', op, val)]` when op is negative, we should searh `b`
with the positive operator and negate the domain to have a complete
result.
https://github.com/odoo/enterprise/pull/132619
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#289955Discuss calls now switch the main view to a newly joined participant when the user was previously alone in an automatic-layout call. This makes one-to-one calls feel more natural while still showing clear feedback when the user is speaking.
Original PR description
Before this commit, when starting a discuss call and being alone in the call with "auto" call layout, the spotlight stayed on self user rather than move to the new participant. This happens because…
Before this commit, when starting a discuss call and being alone in the call with "auto" call layout, the spotlight stayed on self user rather than move to the new participant. This happens because the spotlight in "Auto" mode relies on speaker, and when no one speaks this defaults to oldest participant which is self. However when in this scenario, the most important is seeing the other person as soon as they join. This commit fixes the issue by having spotlight show the other participant. Note that with this change affects the showing that self is talking and other people can hear us, as the lack of showing of the self card means we don't see the talking bars on ouselves. This commit solves this problem by mixing the mic "^" quick settings button with the self talking bar, similarly to the Call Menu. That way, when in a call in the meeting view, this is made very clear when we are self talking at this is properly registered in the call. <img width="1220" height="996" alt="Screenshot 2026-09-23 at 17 55 46" src="https://github.com/user-attachments/assets/a9c1df9a-0f7c-4bac-83e9-f0b5a6219d3d" /> Forward-Port-Of: odoo/odoo#290334
This fix improves how action buttons are arranged in the Point of Sale product screen modal, especially on tablets and screens with unusual proportions. It prevents buttons such as Cancel from appearing awkwardly on their own row or overflowing, making the interface more reliable and easier to use in both portrait and landscape views.
Original PR description
This PR fixes the issue of the Cancel button floating on the last row when the buttons wrap and other overflowing issues. Before this PR, we were targetting the screen's orientation and max-height,…
This PR fixes the issue of the Cancel button floating on the last row when the buttons wrap and other overflowing issues. Before this PR, we were targetting the screen's orientation and max-height, which worked in general but still let a few layout issues through. On tablets the buttons are large and squarish for better touch usability (which has the double function of leaving plenty of space for translations), this makes fitting them within the modal container without overflowing a bit more complex. Instead, we target ranges of the aspect-ratio of the screen and adjust the buttons squarish aspect-ratio and the number of grid columns accordingly. By controlling the grid's columns we're able to tell the last button (the Cancel button) to stretch to full width when needed as well as having a more balanced layout in both landscape and portrait views. task-6235164 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#265711
This fixes how journal item reconciliation behaves when an account is not set up for payment reconciliation. The system now uses the company currency and avoids creating unnecessary exchange difference entries, helping keep accounting records accurate.
Original PR description
When reconciling journal items, if one of them is using an account with no payment reconciliation, then the currency of the reconciliation should be the company currency regardless of which currency was used on the items. Also, in that case, no exchange difference entry should be created. task-6582723 Forward-Port-Of: odoo/enterprise#132199
The timesheet timer now prefills with the most recently visited task instead of being blocked by an untouched draft. This prevents employees from accidentally logging time on the wrong task and also supports non-billable tasks when timesheets are allowed.
Original PR description
1. Open a task and open the timer from the systray -> it is prefilled with that task 2. Close the systray, open another task and open the timer again -> it is still prefilled with the first task 3. Open a task, open the timer and click "Create" -> the empty form taking over is prefilled with the task that was just timesheeted Closing the systray saves the form as a draft even when nothing was typed in it, and a draft takes precedence over the task last visited. With this PR, a form is only kept as a draft once the user has edited it, and creating a timesheet consumes the prefill so that the form taking over starts empty. Reopening the systray still prefills the task last visited. Task-6580476 Forward-Port-Of: odoo/enterprise#132176
This fix ensures Colombian electronic invoice XML notes contain only the invoice Terms and Conditions, not the journal technical control key. This keeps DIAN submissions aligned with expected business content and avoids exposing an internal configuration value in customer-facing invoice data.
Original PR description
**Steps to reproduce:** - Install the `l10n_co_dian` module and switch to the CO Company. - Disable `Test environment` and enable `DIAN Demo Mode` in the invoicing settings. - Set a `Technical…
**Steps to reproduce:** - Install the `l10n_co_dian` module and switch to the CO Company. - Disable `Test environment` and enable `DIAN Demo Mode` in the invoicing settings. - Set a `Technical control key` on the `Customer Invoices` journal. - Create and confirm an invoice with Terms and Conditions. - Send the invoice to `DIAN`. - Open the generated XML file and observe the `cbc:Note` tag. **Observation:** The `Note` tag contains the `technical control key`. **Expected behavior:** The `Note` tag should only contain the Terms and Conditions value from the invoice. (Confirm with PO [1]) **Root Cause:** At [2] and [3], the code includes the `technical key` in the `Note` tag. [1]: https://www.odoo.com/mail/message/1164671931 [2]: https://github.com/odoo/enterprise/blob/a5f4bde1aa33ea7b796d7ee3f9d713a6c8ab8348/l10n_co_dian/models/account_edi_xml_ubl_dian.py#L664 [3]: https://github.com/odoo/enterprise/blob/a5f4bde1aa33ea7b796d7ee3f9d713a6c8ab8348/l10n_co_dian/models/account_edi_xml_ubl_dian.py#L1600-L1603 opw-6513843 Forward-Port-Of: odoo/enterprise#132831 Forward-Port-Of: odoo/enterprise#131495
Appointment calendar views now display all appointment bookings, even when the current user is not directly part of them. This restores the previous behavior and prevents teams from seeing empty or incomplete calendars after the multi-calendar update.
Original PR description
With the new multi-calendar feature, calendar views in the appointment app also used the calendar filter to show events. This proved to be a regression compared to the old view where we could see other appointments, even if the user wasn't part of it. This commit brings back this behavior by displaying all appointments. Task-6591658 Forward-Port-Of: odoo/enterprise#132742
This fix improves searches that exclude certain records, so teams get more accurate results when filtering out items in Helpdesk, Planning, Sales-related planning, skills reporting, and Kenyan e-invoicing product data. It helps prevent misleading lists or reports caused by negative search conditions not being applied correctly.
Original PR description
https://github.com/odoo/odoo/pull/289955 Forward-Port-Of: odoo/enterprise#132619
This update fixes several Shop Floor interface inconsistencies and adds easier access to barcode reference sheets. It helps manufacturing teams work more smoothly by making the work order screen clearer and related barcode guidance easier to find.
Original PR description
Fix some Shopfloor UI and had link to barcodes demo and action sheet. Task-6574839 Forward-Port-Of: odoo/enterprise#132566