Daily updates from Odoo
Thursday, August 6, 2026
244 changes
24 changes
Enhancements to existing features
The signing workflow now avoids loading unnecessary role data when preparing a signing template. This reduces background work and can make the Sign app more responsive, especially for databases with many signing roles.
Original PR description
This `search_read` is done - without `domain` which means it fetches the entire table - without `fields` which means it fetches all fields And the business code only actually needs the id of the very first record which is returned. See `SignTemplateIframe`'s constuctor: `this.props.signRoles[0].id;` AFAICS, that's the only place where it's used. This will be better refactored in master. Forward-Port-Of: odoo/enterprise#126835
Comply with new provincial fiscal transparency regulations (CABA, Entre Ríos, Chubut, and other provinces still not defined) that mandate detailing Perception IIBB taxes on B2C invoices (Factura/Invoice B): - Some jurisdictions mandate a new description for IIBB perception taxes on the invoice, - We need to put the IIBB perception taxes in the special "Fiscal Transparency Regime" table, and exclude them from the standard tax table, task-6367087 Forward-Port-Of: odoo/odoo#278729 Forwar
Original PR description
Comply with new provincial fiscal transparency regulations (CABA, Entre Ríos, Chubut, and other provinces still not defined) that mandate detailing Perception IIBB taxes on B2C invoices (Factura/Invoice B): - Some jurisdictions mandate a new description for IIBB perception taxes on the invoice, - We need to put the IIBB perception taxes in the special "Fiscal Transparency Regime" table, and exclude them from the standard tax table, task-6367087 Forward-Port-Of: odoo/odoo#278729 Forward-Port-Of: odoo/odoo#277673
The decorator `no_retry` can be used on any objects and just sets a flag. When checking if we should retry a test, let's also check the value on the class. This was broken by 2423b460526e9e7dd9ea8a7b3f7e40c93ab708f5. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280641
Original PR description
The decorator `no_retry` can be used on any objects and just sets a flag. When checking if we should retry a test, let's also check the value on the class. This was broken by 2423b460526e9e7dd9ea8a7b3f7e40c93ab708f5. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280641
Resolved issues and error corrections
Dropdown fields in accounting reports now show the text cursor in the expected right-aligned position. This removes a small visual inconsistency and makes report filters feel more polished and easier to use.
Original PR description
Dropdown inputs inside of an account report show the cursor in the center of the input field. The cursor has been changed to be right-aligned. task-6247454 Forward-Port-Of: odoo/enterprise#120728
This fix prevents the AI chat from failing when used in areas such as Shopfloor where view options are not available. Users can keep the AI chat open while navigating and sending messages without encountering this error.
Original PR description
### Issue When AI chat is used in views where `config.viewSwitcherEntries` is not initialized, sending a message raises a `TypeError` because the code attempts to call `.map()` on an undefined value.…
### Issue
When AI chat is used in views where `config.viewSwitcherEntries` is not initialized, sending a message raises a `TypeError` because the code attempts to call `.map()` on an undefined value.
### Steps to Reproduce
[Video](https://drive.google.com/file/d/1i7kWDnmv4mebGtf1to12BNHCG5omBTXl/view?usp=sharing)
1. Click the **Ask AI** button.
2. Open the AI chat.
3. Keep it open and navigate to the **Shopfloor** app.
4. Send a message in the AI chat.
### Error
```text
TypeError: Cannot read properties of undefined (reading 'map')
at WithSearch.getCurrentViewInfo
```
### Fix
Safely handle cases where `config.viewSwitcherEntries` is undefined by using optional chaining and falling back to an empty array.
**Before**
```js
result.available_view_types = config.viewSwitcherEntries.map((v) => v.type);
```
**After**
```js
result.available_view_types =
config.viewSwitcherEntries?.map((v) => v.type) || [];
```
opw-6414684
Forward-Port-Of: odoo/enterprise#126738
Forward-Port-Of: odoo/enterprise#125821The Malaysian Statement of Accounts option now appears only for companies based in Malaysia. This prevents users in other countries from seeing or running a country-specific report that does not apply to their company.
Original PR description
### Current behavior: After installing `l10n_my_reports`, the Malaysian's Statement of Account button appears on Aged Receivable for every company, and the partner Action "Print Statement of Account" can be run from non-MY companies ### Expected behavior: To avoid user confusion, it is advised to restrict its visibility so that it is only accessible to Malaysia-specific companies ### Steps to reproduce: 1. Install `l10n_my_reports` 2. Switch to a non-Malaysian company 3. Open Invoicing > Reporting > Aged Receivable 4. Observe the "Statement of Account" button on partner lines ### Cause of the issue: Missing checks for 'MY' company country code in UI and print report action ### Fix: - show the Aged Receivable SoA button only when `company_country_code === 'MY'` - guard `action_print_report_statement_account` for non-MY companies opw-6340854 Forward-Port-Of: odoo/enterprise#126597 Forward-Port-Of: odoo/enterprise#126172
The Barcode app welcome screen now correctly shows package scanning instructions when package handling is enabled. This helps warehouse users quickly find transfers by scanning packages without missing guidance on the landing page.
Original PR description
When packages are enabled, the barcode scanner welcome screen does not display the instructions for scanning a package. ## Steps to reproduce - Enable `Packages` in Inventory settings. - Open the Barcode application. - Observe the instructions listed on the welcome/landing page. - Notice that the instruction `Scan a package to find a transfer` is missing, even though packages are enabled. ## Issue The MainMenu component has a getter barcodeHomeHelper that checks this.packageEnabled to construct the barcode scanner helper bullet points. However, during setup, the configuration value was incorrectly assigned to this.packagesEnabled. ## Fix Correct the variable name typo in the main menu setup so that the package related instructions are correctly displayed when packaging is enabled. [^1] [^1]:  Forward-Port-Of: odoo/enterprise#124658
The payroll system now prevents refund payslips from being recalculated when users click compute, preserving their existing refund details. Users can still clear and reset the payslip lines when needed, reducing the risk of accidental changes to refund payroll records.
Original PR description
When a payslip is a refund payslip, we don't want to compute it if we click on "compute". The lines can still be reset when clicking on "reset" Forward-Port-Of: odoo/enterprise#126845
Appointment cancellation emails are now sent in the language of the customer who booked the appointment, matching the behavior of confirmation emails. This avoids confusing customers with cancellation notices in the staff member's language while keeping the old behavior for non-appointment events.
Original PR description
**Problem:** When an appointment is cancelled, the cancellation email is always sent in the organizer's (staff member's) language, ignoring the booking customer's language. The booking confirmation,…
**Problem:**
When an appointment is cancelled, the cancellation email is always sent in the organizer's (staff member's) language, ignoring the booking customer's language. The booking confirmation, by contrast, is correctly localized.
**Steps to reproduce:**
1. Set a contact's language to a non-default one (e.g. Romanian).
2. Book an appointment for that contact (they are the booker/attendee).
3. Cancel the appointment.
4. The customer received the confirmation in Romanian but the cancellation email arrives in English.
**Current behavior:**
The cancellation email is rendered in the organizer's language.
**Expected behavior:**
The cancellation email is rendered in the booking customer's language, like the confirmation/invitation email.
**Cause of the issue:**
The cancellation uses `appointment_canceled_mail_template`, whose `lang` is `{{ object.partner_id.lang }}`. On `calendar.event`, `partner_id` is `related='user_id.partner_id'`, i.e. the organizer, not the customer. The template is posted once per event (via `_track_template`), so its single rendering language applies to every recipient, including attendees whose own language differs. The confirmation email is unaffected because it is the per-attendee `attendee_invitation_mail_template` (model `calendar.attendee`), rendered once per attendee in that attendee's language.
**Fix:**
Deriving the language from `appointment_booker_id` makes the cancellation consistent with the other appointment mails, which are meant for the person who booked the meeting. It falls back to `partner_id` when there is no booker (e.g. an event not created through the appointment flow), preserving the previous behavior in that case.
opw-6323179
Forward-Port-Of: odoo/enterprise#125927
Forward-Port-Of: odoo/enterprise#124116Portal users editing Knowledge articles can now view and edit existing voice transcription text without seeing voice recording controls. This keeps recording features limited to internal users while preserving access to article content.
Original PR description
Portal users can edit Knowledge articles but should not have access to the voice recording feature, which is reserved for internal users. The readonly component is already used in read-only views, so reusing it in the editor for portal users is safe, it still renders the existing transcription content correctly without exposing any recording controls. Portal users can still edit the text content inside the component thanks to the editable descendants concept, which allows the editor to manage specific editable areas even within a readonly component. Task-6320461 Forward-Port-Of: odoo/enterprise#126606
Australian payroll data updates now include salary rule category information. This prevents scheduled payroll updates from failing when new payroll rule categories are introduced, helping payroll maintenance run reliably.
Original PR description
ir_cron_update_payroll_data updates the payroll data including rules but fails if a new rule category is introduced. This commit adds the hr_salary_rule_category_data file to the list of data files to update. task-6351929 Forward-Port-Of: odoo/enterprise#122392
Uploading a document from a contact now places it in the intended default workspace instead of reusing the last folder selected in Documents. This prevents files from being misfiled, such as ending up in Finance when they should go to My Drive or the contact-related document area.
Original PR description
Steps to reproduce ================== 1. Open Documents. 2. Select Finance. 3. Return to the home page and open the Contacts app. 4. Open any contact. 5. Click the Documents stat button. 6. Upload a document. Issue ===== The document is uploaded to the Finance folder instead of My Drive. Reason ====== When uploading a document using the upload button, we use `currentFolderAccessToken` to determine the destination folder. When opening the Documents view from a contact, `searchpanel_default_folder_id` is set to `False` so that documents are uploaded to the `All` workspace. However, when the search model is loaded, we do not reset `currentFolderAccessToken` when `folder_id` is `False`, causing the previously selected folder (Finance) to be reused. Task-6352242 Forward-Port-Of: odoo/enterprise#123285
Timesheet assistant rules were updated so suggestions no longer use empty captured text that could appear as confusing phrases such as "Discussing with undefined". This improves the clarity and reliability of automatically generated timesheet suggestions for users.
Original PR description
Several aw.rule regexes use (.*) for the capture groups feeding the suggestion name/description, allowing an empty match and producing incorrect suggestions (e.g. "Discussing with undefined") Task-6377168 Forward-Port-Of: odoo/enterprise#126751 Forward-Port-Of: odoo/enterprise#125771
Installing the website subscription feature no longer fails if the default monthly subscription plan was previously deleted. The setup now skips that optional plan link when it is unavailable, allowing businesses to complete installation without manual recovery steps.
Original PR description
Currently, an error occurs when user tries to install `website_sale_subscription` after deleting the monthly subscription plan. Steps to replicate: - Install `sale_subscription`. - Open Subscription…
Currently, an error occurs when user tries to install `website_sale_subscription` after deleting the monthly subscription plan.
Steps to replicate:
- Install `sale_subscription`.
- Open Subscription > Configuration > Recurring Plans.
- Delete the plan named `monthly`.
- Install `website_sale_subscription`.
Error:
```
ValueError: External ID not found in the system: sale_subscription.subscription_plan_month
odoo.tools.convert.ParseError: while parsing /home/odoo/odoo19/enterprise/website_sale_subscription/data/donation_data.xml:16, somewhere inside
<record id="product_recurring_donation_pricing_monthly" model="product.pricelist.item">
<field name="plan_id" ref="sale_subscription.subscription_plan_month"/>
<field name="product_tmpl_id" ref="product_recurring_donation"/>
<field name="fixed_price">1.0</field>
<field name="pricelist_id" eval="False"/>
</record>
```
Cause:
- Error is raised when assigning the `Monthly` subscription plan in the `website_sale_subscription` master data because the user has deleted the `Monthly` subscription plan.
Solution:
- As the `plan_id` is not a required field, we can skip assigning the `plan_id` if it the record is not found.
sentry-7441124574The cash basis report test now uses the company’s configured outstanding receipts account instead of assuming a fixed account code. This prevents false test failures when account codes differ across database setups, improving release reliability without changing user-facing accounting behavior.
Original PR description
Description of the issue this commit addresses: Commit 63f5646cfd75 made the tests use the default outstanding account but hard-coded code 101403. In an all-module database, generated account codes depend on existing accounts, so Outstanding Receipts may use code 101404 and make otherwise correct report assertions fail. --- Desired behavior after this commit is merged: This commit derives the expected report line name from the configured outstanding receipts account, making the assertions independent of its generated code. --- runbot-[231581](https://runbot.odoo.com/odoo/error/231581) Forward-Port-Of: odoo/enterprise#126743 Forward-Port-Of: odoo/enterprise#125652
This fixes missing styling for spreadsheet side panels in the backend. Users should see these panels with the intended layout and appearance after a previous cleanup accidentally removed the shared styling from the right place.
Original PR description
During the reorganisation of the css in #114180, the generic sidepanel.css file was removed from every assets. It belongs to the bundle assets at is impacts sidepanels that are only available in the backend (i.e. not in public spreadsheets) Task-6448906
This fix makes a product merge test consistently choose the intended main product before merging. It prevents intermittent automated build failures caused by unpredictable record ordering, improving release validation reliability.
Original PR description
Version: - saas-19.4 Steps to reproduce: - Run the test_merge_success_single_variant test case multiple times with different products created each time. Issue: The master record is selected based on its creation date, but that ordering is not always reliable. As a result, the wrong product may be chosen as the master,causing the incorrect product to be archived and the runbot test to fail. Fix: Before merging, we now manually set product 1 as the master. This makes the test predictable and stops the failures. Build error - 941476
Payslip reports now correctly treat notes that contain only empty formatting as blank. This prevents unnecessary empty note sections from appearing on payroll documents, keeping payslips and related reports cleaner for employees and payroll teams.
Original PR description
The condition was only checking if the note field was truthy. Actually, this can be a problem if the field is with only empty tags. Instead, we should use is_html_empty. Forward-Port-Of: odoo/enterprise#127008
Before this commit, test_chatbot_stop_when_agent_joins failed at random in nightly builds: FAILED: [8/13] Tour chatbot_stop_when_agent_joins_tour -> Step .o-livechat-root:shadow button:contains(Try again). Element (.o-livechat-root:shadow button:contains(Try again)) has not been found. TIMEOUT step failed to complete within 10000 ms. This happens because the test monkeypatches the chatbot_trigger_step route to make the email step fail once, while the routing map bak
Original PR description
Before this commit, test_chatbot_stop_when_agent_joins failed at random in nightly builds: FAILED: [8/13] Tour chatbot_stop_when_agent_joins_tour -> Step .o-livechat-root:shadow button:contains(Try…
Before this commit, test_chatbot_stop_when_agent_joins failed at random in nightly builds:
FAILED: [8/13] Tour chatbot_stop_when_agent_joins_tour -> Step
.o-livechat-root:shadow button:contains(Try again).
Element (.o-livechat-root:shadow button:contains(Try again)) has
not been found.
TIMEOUT step failed to complete within 10000 ms.
This happens because the test monkeypatches the chatbot_trigger_step route to make the email step fail once, while the routing map bakes the controller endpoints in when it is built and keeps them in the "routing" ormcache. Every later request reuses that map, so when it was already built with the original method the patch is ignored: the step never fails, the chatbot posts its last step and the retry button the tour clicks never appears.
This commit fixes the issue by invalidating the "routing" ormcache inside the patch, as done for meeting_view_tour, so the request reaches the patched route.
https://runbot.odoo.com/odoo/error/945351Followup of odoo/odoo@41fe2ebdb9cc. Before this commit, when trying to submit a track proposal with a speaker image, the request failed with: ``` TypeError: event.track.image: use BinaryValue instead of bytes ``` --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#274366
Original PR description
Followup of odoo/odoo@41fe2ebdb9cc. Before this commit, when trying to submit a track proposal with a speaker image, the request failed with: ``` TypeError: event.track.image: use BinaryValue instead of bytes ``` --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#274366
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#280396
Original PR description
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#280396
From versions 18.3 to 19.3, company_id and siret refer to the same field. This caused an issue when connecting to the PDP using siret or siren could overwrite company_id. task-6327304 Forward-Port-Of: odoo/odoo#277446 Forward-Port-Of: odoo/odoo#275276
Original PR description
From versions 18.3 to 19.3, company_id and siret refer to the same field. This caused an issue when connecting to the PDP using siret or siren could overwrite company_id. task-6327304 Forward-Port-Of: odoo/odoo#277446 Forward-Port-Of: odoo/odoo#275276
Steps to reproduce: 1) Install hr_holidays & hr_attendance. 2) Enable "Display Extra Hours" & "Absence Management" in the Attendance app settings. 3) Make a new employee, in the form view click the "+" button to make a new hr version record for this employee. 4) Set the date to be the first of the last month. 5) In the Attendance app -> New -> Select the new employee. 6) Select the check in and out dates to be from last month, edit the times such that the time worked is between 7 and 8 h
Original PR description
Steps to reproduce: 1) Install hr_holidays & hr_attendance. 2) Enable "Display Extra Hours" & "Absence Management" in the Attendance app settings. 3) Make a new employee, in the form view click the…
Steps to reproduce: 1) Install hr_holidays & hr_attendance. 2) Enable "Display Extra Hours" & "Absence Management" in the Attendance app settings. 3) Make a new employee, in the form view click the "+" button to make a new hr version record for this employee. 4) Set the date to be the first of the last month. 5) In the Attendance app -> New -> Select the new employee. 6) Select the check in and out dates to be from last month, edit the times such that the time worked is between 7 and 8 hours (ex 9:00am to 4:55pm). 7) In the Attendance app -> Reporting -> Attendances -> the test employee should have a negative value for "Worked Extra Hours" 8) Create a new time off type, enable "Deduct Extra Hours" & disable "Requires Allocation" use hours as the Unit of measure. 9) Open the time off smart button menu from the test employee's form view. Issue) The value seen in the report from step 7 is not the same as what the user sees in the dashboard. Notes) This issue can occur when an employee scheduled to work for 8 hours a day only clocks in for 7:55 hours leading to a negative extra time. The back-end has the correct value stored and sends it to the browser. The issue occurs because the JavaScript function that converts the decimal number of hours into a string (9.5 -> "9:30") does not work with negative input. This PR resolves that issue. opw-6417543 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280491
Before this commit: Deleting a record that uses a filterable selection field with `whitelist_fname` raises a traceback because the record field data becomes undefined during deletion. Steps to reproduce: 1. Install Belgium Accounting (l10n_be). 2. Create a contact and set a Peppol scheme and endpoint. 3. Delete the contact -> a traceback is raised. After this commit: The selection field safely handles undefined record field data during record deletion without causing an error. no-t
Original PR description
Before this commit: Deleting a record that uses a filterable selection field with `whitelist_fname` raises a traceback because the record field data becomes undefined during deletion. Steps to reproduce: 1. Install Belgium Accounting (l10n_be). 2. Create a contact and set a Peppol scheme and endpoint. 3. Delete the contact -> a traceback is raised. After this commit: The selection field safely handles undefined record field data during record deletion without causing an error. no-task Forward-Port-Of: odoo/odoo#280153 Forward-Port-Of: odoo/odoo#278808
20 changes
Enhancements to existing features
Motivation ---------- Each database served keeps a full registry in a process-wide LRU. The LRU is bounded by a count, so on a server hosting thousands of databases the number of retained registries follows traffic rather than memory pressure. Their combined footprint can push a worker past its virtual-memory soft limit, at which point it is killed and restarted. On a server with ~2500 databases, the soft limit is reached at ~180 resident databases while the LRU could still hold ~210, so HT
Original PR description
Motivation ---------- Each database served keeps a full registry in a process-wide LRU. The LRU is bounded by a count, so on a server hosting thousands of databases the number of retained registries…
Motivation ---------- Each database served keeps a full registry in a process-wide LRU. The LRU is bounded by a count, so on a server hosting thousands of databases the number of retained registries follows traffic rather than memory pressure. Their combined footprint can push a worker past its virtual-memory soft limit, at which point it is killed and restarted. On a server with ~2500 databases, the soft limit is reached at ~180 resident databases while the LRU could still hold ~210, so HTTP workers were being recycled under normal load. Tracking usage -------------- Every request for a registry goes through the single lookup in the registry constructor, which stamps it with a monotonic timestamp; the stamp is also set when a registry is first built. Collecting idle registries -------------------------- A collection pass drops every registry whose last use is older than the configured idle timeout. It runs at the end of registry loading, so it fires periodically as databases come and go. Registries that are still loading are skipped, so a concurrent build is never collected. Dropping a registry only detaches it from the LRU: a request still holding a reference keeps working, and the next lookup rebuilds it. The timeout is read from ODOO_REGISTRY_MAX_IDLE_TIMEOUT, in seconds; a value of zero, the default, disables the mechanism so behaviour is unchanged unless it is opted into. Results ------- With a five-minute timeout on the same ~2500-database server, the HTTP workers settle at around 40 resident registries instead of saturating memory on the long run. The gevent worker, which sees every web client reconnect at startup and briefly fills the LRU with ~150 databases, releases most of them on the first pass, reclaiming the memory. On a real-life SaaS server with 64GB of RAM, that frees up to ~10GB which were previously taken by unused registries in the LRU. It comes at the expense of extra registry recomputes, but on the other hand workers do not reach their virtual memory limit anymore. closes odoo/odoo#276581 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#278236
Comply with new provincial fiscal transparency regulations (CABA, Entre Ríos, Chubut, and other provinces still not defined) that mandate detailing Perception IIBB taxes on B2C invoices (Factura/Invoice B): - Some jurisdictions mandate a new description for IIBB perception taxes on the invoice, - We need to put the IIBB perception taxes in the special "Fiscal Transparency Regime" table, and exclude them from the standard tax table, task-6367087 Forward-Port-Of: odoo/odoo#278729 Forwar
Original PR description
Comply with new provincial fiscal transparency regulations (CABA, Entre Ríos, Chubut, and other provinces still not defined) that mandate detailing Perception IIBB taxes on B2C invoices (Factura/Invoice B): - Some jurisdictions mandate a new description for IIBB perception taxes on the invoice, - We need to put the IIBB perception taxes in the special "Fiscal Transparency Regime" table, and exclude them from the standard tax table, task-6367087 Forward-Port-Of: odoo/odoo#278729 Forward-Port-Of: odoo/odoo#277673
The decorator `no_retry` can be used on any objects and just sets a flag. When checking if we should retry a test, let's also check the value on the class. This was broken by 2423b460526e9e7dd9ea8a7b3f7e40c93ab708f5. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280641
Original PR description
The decorator `no_retry` can be used on any objects and just sets a flag. When checking if we should retry a test, let's also check the value on the class. This was broken by 2423b460526e9e7dd9ea8a7b3f7e40c93ab708f5. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280641
Resolved issues and error corrections
The Barcode app welcome screen now correctly shows the instruction to scan a package when package tracking is enabled. This helps warehouse users find transfers by package without missing expected guidance on the landing page.
Original PR description
When packages are enabled, the barcode scanner welcome screen does not display the instructions for scanning a package. ## Steps to reproduce - Enable `Packages` in Inventory settings. - Open the Barcode application. - Observe the instructions listed on the welcome/landing page. - Notice that the instruction `Scan a package to find a transfer` is missing, even though packages are enabled. ## Issue The MainMenu component has a getter barcodeHomeHelper that checks this.packageEnabled to construct the barcode scanner helper bullet points. However, during setup, the configuration value was incorrectly assigned to this.packagesEnabled. ## Fix Correct the variable name typo in the main menu setup so that the package related instructions are correctly displayed when packaging is enabled. [^1] [^1]:  Forward-Port-Of: odoo/enterprise#124658
This fixes a payroll ADP test setup issue that could fail when optional attendance-related apps were not installed. The change keeps the test compatible across different installation setups, improving reliability of automated validation without changing user-facing payroll behavior.
Original PR description
Steps to reproduce the bug: - Install l10n_us_hr_payroll_adp without hr_attendance/hr_holidays_attendance - Run the test suite (runbot build 941182) - TestL10nUsHrPayrollADPExport.setUpClass fails…
Steps to reproduce the bug: - Install l10n_us_hr_payroll_adp without hr_attendance/hr_holidays_attendance - Run the test suite (runbot build 941182) - TestL10nUsHrPayrollADPExport.setUpClass fails Problem: setUpClass raised `ValueError: Invalid field 'overtime_deductible' in 'hr.work.entry.type'` when writing on the overtime work entry type. The field overtime_deductible is only defined by hr_holidays_attendance https://github.com/odoo/odoo/blob/saas-19.2/addons/hr_holidays_attendance/models/hr_work_entry_type.py#L10-L12 which depends on hr_attendance and auto-installs only when hr_attendance is present. l10n_us_hr_payroll_adp's dependency chain (l10n_us_hr_payroll -> hr_payroll) never pulls in hr_attendance, so the field doesn't always exist in the test's registry. However, when hr_holidays_attendance IS installed (e.g. on the full runbot build), the field must still be set to False: leaving it at its default causes hr.leave.create() to run hr_holidays_attendance's _check_overtime_deductible(), which raises "The employee does not have enough extra hours to request this leave." for the overtime leaves created further down in setUpClass, since the test employees have no recorded attendance/overtime hours. Solution: Only include overtime_deductible in the write() vals when the field exists on hr.work.entry.type, keeping it disabled either way. runbot-941182 Forward-Port-Of: odoo/enterprise#126491
Commission reports now use simpler internal row identifiers instead of more complex text-based IDs. This reduces unnecessary complexity in the sales commission reporting logic without changing what users see in the report.
Original PR description
Unlike achievement report ids, commission report ids do not need to identify source records. ROW_NUMBER() is enough to identify the grouped rows and avoids using string ids.
Appointment cancellation emails are now sent in the language of the customer who booked the appointment, instead of defaulting to the staff member's language. This makes cancellation messages consistent with booking confirmations and reduces customer confusion for multilingual businesses.
Original PR description
**Problem:** When an appointment is cancelled, the cancellation email is always sent in the organizer's (staff member's) language, ignoring the booking customer's language. The booking confirmation,…
**Problem:**
When an appointment is cancelled, the cancellation email is always sent in the organizer's (staff member's) language, ignoring the booking customer's language. The booking confirmation, by contrast, is correctly localized.
**Steps to reproduce:**
1. Set a contact's language to a non-default one (e.g. Romanian).
2. Book an appointment for that contact (they are the booker/attendee).
3. Cancel the appointment.
4. The customer received the confirmation in Romanian but the cancellation email arrives in English.
**Current behavior:**
The cancellation email is rendered in the organizer's language.
**Expected behavior:**
The cancellation email is rendered in the booking customer's language, like the confirmation/invitation email.
**Cause of the issue:**
The cancellation uses `appointment_canceled_mail_template`, whose `lang` is `{{ object.partner_id.lang }}`. On `calendar.event`, `partner_id` is `related='user_id.partner_id'`, i.e. the organizer, not the customer. The template is posted once per event (via `_track_template`), so its single rendering language applies to every recipient, including attendees whose own language differs. The confirmation email is unaffected because it is the per-attendee `attendee_invitation_mail_template` (model `calendar.attendee`), rendered once per attendee in that attendee's language.
**Fix:**
Deriving the language from `appointment_booker_id` makes the cancellation consistent with the other appointment mails, which are meant for the person who booked the meeting. It falls back to `partner_id` when there is no booker (e.g. an event not created through the appointment flow), preserving the previous behavior in that case.
opw-6323179
Forward-Port-Of: odoo/enterprise#125927
Forward-Port-Of: odoo/enterprise#124116Portal users editing Knowledge articles can now view and edit existing voice transcription text without seeing the voice recording controls reserved for internal users. This keeps collaboration safe while preserving access to existing article content.
Original PR description
Portal users can edit Knowledge articles but should not have access to the voice recording feature, which is reserved for internal users. The readonly component is already used in read-only views, so reusing it in the editor for portal users is safe, it still renders the existing transcription content correctly without exposing any recording controls. Portal users can still edit the text content inside the component thanks to the editable descendants concept, which allows the editor to manage specific editable areas even within a readonly component. Task-6320461 Forward-Port-Of: odoo/enterprise#126606
The Australian payroll update process now includes salary rule category data when refreshing payroll rules. This prevents update failures when new payroll rule categories are introduced, helping payroll maintenance run more reliably.
Original PR description
ir_cron_update_payroll_data updates the payroll data including rules but fails if a new rule category is introduced. This commit adds the hr_salary_rule_category_data file to the list of data files to update. task-6351929 Forward-Port-Of: odoo/enterprise#122392
Payslip reports now better detect when a note is effectively empty, even if it contains only hidden formatting tags. This prevents blank note sections from appearing on payroll documents, keeping payslips cleaner for employees and payroll teams.
Original PR description
The condition was only checking if the note field was truthy. Actually, this can be a problem if the field is with only empty tags. Instead, we should use is_html_empty.
Fixed an issue where uploading a document from a contact could place it in the last folder used in Documents, such as Finance, instead of the intended My Drive/All workspace location. This prevents files from being misfiled and helps users find contact-related uploads where they expect them.
Original PR description
Steps to reproduce ================== 1. Open Documents. 2. Select Finance. 3. Return to the home page and open the Contacts app. 4. Open any contact. 5. Click the Documents stat button. 6. Upload a document. Issue ===== The document is uploaded to the Finance folder instead of My Drive. Reason ====== When uploading a document using the upload button, we use `currentFolderAccessToken` to determine the destination folder. When opening the Documents view from a contact, `searchpanel_default_folder_id` is set to `False` so that documents are uploaded to the `All` workspace. However, when the search model is loaded, we do not reset `currentFolderAccessToken` when `folder_id` is `False`, causing the previously selected folder (Finance) to be reused. Task-6352242 Forward-Port-Of: odoo/enterprise#123285
Fixed an issue that could prevent event badges from printing when the badge layout included an image. This ensures staff can print badges reliably without encountering an error during event check-in or preparation.
Original PR description
Before this commit, some faulty logic in the `load_image` function caused a traceback when the ESC/LABEL badge template tried to use an image field. This bug only surfaced recently due to changes in how binary fields are accessed. After this commit, the logic is fixed and the traceback no longer occurs. task-6452486
The cash basis reporting tests now use the outstanding receipts account configured in the system instead of relying on a fixed account code. This prevents false test failures when account codes differ between databases, improving reliability without changing business functionality.
Original PR description
Description of the issue this commit addresses: Commit 63f5646cfd75 made the tests use the default outstanding account but hard-coded code 101403. In an all-module database, generated account codes depend on existing accounts, so Outstanding Receipts may use code 101404 and make otherwise correct report assertions fail. --- Desired behavior after this commit is merged: This commit derives the expected report line name from the configured outstanding receipts account, making the assertions independent of its generated code. --- runbot-[231581](https://runbot.odoo.com/odoo/error/231581) Forward-Port-Of: odoo/enterprise#126743 Forward-Port-Of: odoo/enterprise#125652
This fixes a timing issue in the batch picking barcode flow test by waiting for the first scanned product quantity to update before selecting the next product. It helps prevent incorrect quantity splits during automated validation, reducing false failures and improving confidence in barcode picking behavior.
Original PR description
Problem: When scanning the first product, the second move line is clicked immediately after.…
Problem: When scanning the first product, the second move line is clicked immediately after. https://github.com/odoo/enterprise/blob/bfb8bab7636a84c829f16087771e00bf31ad2cce/stock_barcode_picking_batch/static/tests/tours/tour_test_barcode_batch_flows.js#L1529-L1541 If this happens before the first scan has finished, its quantity is incorrectly applied to the second move line that is clicked. This causes a 0 - 3 split instead of a 1 - 2 split, which results in there only being 6 move lines instead of 7. We updated our quantity on the `currentLine` https://github.com/odoo/enterprise/blob/bfb8bab7636a84c829f16087771e00bf31ad2cce/stock_barcode/static/src/models/barcode_model.js#L1478 When finding `currentLine`, we go through `_findLine` and use `this.selectedLineVirtualId`, which is the one that is currently selected in the UI https://github.com/odoo/enterprise/blob/bfb8bab7636a84c829f16087771e00bf31ad2cce/stock_barcode/static/src/models/barcode_model.js#L1705-L1712 Purpose: By adding this step, we wait until the first line’s quantity to be updated before it moves on and clicks on the second product. runbot-941211 Forward-Port-Of: odoo/enterprise#126847 Forward-Port-Of: odoo/enterprise#124162
Preparation tickets in self-order point of sale now use the language configured for the default self-order user. This helps kitchen or preparation staff receive tickets in the expected language, reducing confusion and order handling mistakes.
Original PR description
Before this commit the preparation ticket wasn't using the lang of the default pos_self_order user. Now it will use it.
Steps to reproduce: 1) Install hr_holidays & hr_attendance. 2) Enable "Display Extra Hours" & "Absence Management" in the Attendance app settings. 3) Make a new employee, in the form view click the "+" button to make a new hr version record for this employee. 4) Set the date to be the first of the last month. 5) In the Attendance app -> New -> Select the new employee. 6) Select the check in and out dates to be from last month, edit the times such that the time worked is between 7 and 8 h
Original PR description
Steps to reproduce: 1) Install hr_holidays & hr_attendance. 2) Enable "Display Extra Hours" & "Absence Management" in the Attendance app settings. 3) Make a new employee, in the form view click the…
Steps to reproduce: 1) Install hr_holidays & hr_attendance. 2) Enable "Display Extra Hours" & "Absence Management" in the Attendance app settings. 3) Make a new employee, in the form view click the "+" button to make a new hr version record for this employee. 4) Set the date to be the first of the last month. 5) In the Attendance app -> New -> Select the new employee. 6) Select the check in and out dates to be from last month, edit the times such that the time worked is between 7 and 8 hours (ex 9:00am to 4:55pm). 7) In the Attendance app -> Reporting -> Attendances -> the test employee should have a negative value for "Worked Extra Hours" 8) Create a new time off type, enable "Deduct Extra Hours" & disable "Requires Allocation" use hours as the Unit of measure. 9) Open the time off smart button menu from the test employee's form view. Issue) The value seen in the report from step 7 is not the same as what the user sees in the dashboard. Notes) This issue can occur when an employee scheduled to work for 8 hours a day only clocks in for 7:55 hours leading to a negative extra time. The back-end has the correct value stored and sends it to the browser. The issue occurs because the JavaScript function that converts the decimal number of hours into a string (9.5 -> "9:30") does not work with negative input. This PR resolves that issue. opw-6417543 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280491
Before this commit: Deleting a record that uses a filterable selection field with `whitelist_fname` raises a traceback because the record field data becomes undefined during deletion. Steps to reproduce: 1. Install Belgium Accounting (l10n_be). 2. Create a contact and set a Peppol scheme and endpoint. 3. Delete the contact -> a traceback is raised. After this commit: The selection field safely handles undefined record field data during record deletion without causing an error. no-t
Original PR description
Before this commit: Deleting a record that uses a filterable selection field with `whitelist_fname` raises a traceback because the record field data becomes undefined during deletion. Steps to reproduce: 1. Install Belgium Accounting (l10n_be). 2. Create a contact and set a Peppol scheme and endpoint. 3. Delete the contact -> a traceback is raised. After this commit: The selection field safely handles undefined record field data during record deletion without causing an error. no-task Forward-Port-Of: odoo/odoo#280153 Forward-Port-Of: odoo/odoo#278808
### Issue: A user who can modify `tax_exigibility` but does not have `account.group_account_readonly` is blocked from switching to `on_payment` There is no way to complete the operation as the account field is not visible and cannot be filled before saving ### Cause: When `tax_exigibility` is set to `on_payment`, the view expects `cash_basis_transition_account_id` to be filled before saving`_constrains_cash_basis_transition_account` then validates that the account is reconcilable `cas
Original PR description
### Issue: A user who can modify `tax_exigibility` but does not have `account.group_account_readonly` is blocked from switching to `on_payment` There is no way to complete the operation as the…
### Issue: A user who can modify `tax_exigibility` but does not have `account.group_account_readonly` is blocked from switching to `on_payment` There is no way to complete the operation as the account field is not visible and cannot be filled before saving ### Cause: When `tax_exigibility` is set to `on_payment`, the view expects `cash_basis_transition_account_id` to be filled before saving`_constrains_cash_basis_transition_account` then validates that the account is reconcilable `cash_basis_transition_account_id` was restricted to `account.group_account_readonly`, hiding it from other users The field is never rendered, so it cannot be filled The `required` constraint is never evaluated client-side and `_constrains_cash_basis_transition_account` raises a `ValidationError` on save because the account is empty There is no reason to restrict `cash_basis_transition_account_id` independently — if a user can modify `tax_exigibility`, they must also be able to set the linked account ### Steps to reproduce: - Install `account` - Enable Cash Basis in Settings (On RunBot ensure no default account is set) - Enable Developer Mode in Settings - Go to Settings > Users & Companies > Users - Open the current user and disable both: `Show Accounting Features - Readonly` and `Show Full Accounting Features` (if set) - Go to Invoicing > Configuration > Taxes - Open any tax and go to Advanced Options - Change Tax Exigibility to Based on Payment Before the fix, the account selector is not displayed and saving raises an error opw-6359173 Forward-Port-Of: odoo/odoo#274728
Before this commit, the `empty a many2one field in list view` test sometimes failed, because the many2one value wasn't correctly unset (`first record` was selected). This happened because we cleared the input and automatically validated (typically with tab). However, it could happen that the validation occurred after the dropdown was opened, so the first value of the dropdown was selected. As a matter of fact, adding `await runAllTimers()` after clearing the input is a way to make the test fail
Original PR description
Before this commit, the `empty a many2one field in list view` test sometimes failed, because the many2one value wasn't correctly unset (`first record` was selected). This happened because we cleared…
Before this commit, the `empty a many2one field in list view` test sometimes failed, because the many2one value wasn't correctly unset (`first record` was selected). This happened because we cleared the input and automatically validated (typically with tab). However, it could happen that the validation occurred after the dropdown was opened, so the first value of the dropdown was selected. As a matter of fact, adding `await runAllTimers()` after clearing the input is a way to make the test fail deterministically. This commit avoids the issue by emptying the many2one without validation, so it basically only set the input value to the empty string, but doesn't tab/enter or anything else, hence it never selects an unwanted value. runbot error-941430 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#280351
This is rather an attempt of fix, as we couldn't reproduce the error locally or in a multi build., so we have no guarantee that this strenghtens the test. The test sometimes fails as `.o_crop_icon` can't be found within 200ms. Before that, we wait for the video element to be ready (we rely on a patch of the `isVideoReady` method of the component to know that the video is ready). Once it is, the isReady flag in the state is set to true and the CropOverlay component renders its `o_crop_icon` el
Original PR description
This is rather an attempt of fix, as we couldn't reproduce the error locally or in a multi build., so we have no guarantee that this strenghtens the test. The test sometimes fails as `.o_crop_icon`…
This is rather an attempt of fix, as we couldn't reproduce the error locally or in a multi build., so we have no guarantee that this strenghtens the test. The test sometimes fails as `.o_crop_icon` can't be found within 200ms. Before that, we wait for the video element to be ready (we rely on a patch of the `isVideoReady` method of the component to know that the video is ready). Once it is, the isReady flag in the state is set to true and the CropOverlay component renders its `o_crop_icon` element. Our guess is that we may sometimes early return in `isVideoReady`, because the component has been destroyed (a new rendering might be on the way). To ensure that we don't take that as the ready signal in the test, we now only consider that we're ready if isVideoReady returned true (i.e. no early return). runbot error-241798 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#280329
16 changes
Enhancements to existing features
Before this commit, contains() and its variants gave the client 3 seconds, and the bus helpers 2 seconds. The problem is that the first wait after openDiscuss pays for the whole mount, /mail/data and /discuss/channel/messages. Measured from openDiscuss resolving to the message being in the DOM: - 250 to 460ms on an idle machine; - 867 to 5258ms over 10 runs with the CPU throttled 4x, which is what a busy runbot looks like, 3 of the 10 over 2 seconds; - 1474 to 6912ms with the CPU throttl
Original PR description
Before this commit, contains() and its variants gave the client 3 seconds, and the bus helpers 2 seconds. The problem is that the first wait after openDiscuss pays for the whole mount, /mail/data and…
Before this commit, contains() and its variants gave the client 3 seconds, and the bus helpers 2 seconds. The problem is that the first wait after openDiscuss pays for the whole mount, /mail/data and /discuss/channel/messages. Measured from openDiscuss resolving to the message being in the DOM:
- 250 to 460ms on an idle machine;
- 867 to 5258ms over 10 runs with the CPU throttled 4x, which is what a
busy runbot looks like, 3 of the 10 over 2 seconds;
- 1474 to 6912ms with the CPU throttled 6x, 5 of 6 over 3 seconds.
"Reactions are ordered by id" fails 1 run in 60 at 4x for that reason.
Note that a longer timeout costs nothing on a green build: the timer is cleared as soon as the element is there, so it only delays the report of a test that was going to fail anyway.
This commit raises both to 10 seconds, the delay a tour step already gets in macro.js. test_js.py runs the presets with timeout=15000, so hoot fails the test itself at 15 seconds and 10 leaves room for the rest of the test.
This should also close most of the open runbot errors shaped like:
Failed to find x of "..." (Timeout of 3 seconds). Found 0 instead.
The element does show up in those, just after the wait gave up.
https://runbot.odoo.com/odoo/error/944188
web companion https://github.com/odoo/odoo/pull/279984
Forward-Port-Of: odoo/odoo#280586
Forward-Port-Of: odoo/odoo#279983The feature was never merged on IAP/Internal's side and we now have a new task to move the setting up of the auto-refill from the local db to the IAP server, so the feature is no longer relevant on the client side. Task-6397951 Forward-Port-Of: odoo/odoo#277512
Original PR description
The feature was never merged on IAP/Internal's side and we now have a new task to move the setting up of the auto-refill from the local db to the IAP server, so the feature is no longer relevant on the client side. Task-6397951 Forward-Port-Of: odoo/odoo#277512
Comply with new provincial fiscal transparency regulations (CABA, Entre Ríos, Chubut, and other provinces still not defined) that mandate detailing Perception IIBB taxes on B2C invoices (Factura/Invoice B): - Some jurisdictions mandate a new description for IIBB perception taxes on the invoice, - We need to put the IIBB perception taxes in the special "Fiscal Transparency Regime" table, and exclude them from the standard tax table, task-6367087 Forward-Port-Of: odoo/odoo#278729 Forwar
Original PR description
Comply with new provincial fiscal transparency regulations (CABA, Entre Ríos, Chubut, and other provinces still not defined) that mandate detailing Perception IIBB taxes on B2C invoices (Factura/Invoice B): - Some jurisdictions mandate a new description for IIBB perception taxes on the invoice, - We need to put the IIBB perception taxes in the special "Fiscal Transparency Regime" table, and exclude them from the standard tax table, task-6367087 Forward-Port-Of: odoo/odoo#278729 Forward-Port-Of: odoo/odoo#277673
The decorator `no_retry` can be used on any objects and just sets a flag. When checking if we should retry a test, let's also check the value on the class. This was broken by 2423b460526e9e7dd9ea8a7b3f7e40c93ab708f5. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280641
Original PR description
The decorator `no_retry` can be used on any objects and just sets a flag. When checking if we should retry a test, let's also check the value on the class. This was broken by 2423b460526e9e7dd9ea8a7b3f7e40c93ab708f5. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280641
Resolved issues and error corrections
The Vietnamese financial reports now classify short-term loan balances under held-to-maturity investments, in line with Circular 99/2025. This helps companies using the Vietnam localization produce balance sheets that match current reporting requirements.
Original PR description
### Expected behavior: As per circular 99/2025, short-term loan (12831) balance is required to fall under Held to Maturity Investment (Code 123) instead of 112, translated: ``` Short-term held-to-maturity investments (Code 123): includes held-to-maturity investments with a remaining term of 12 months or less from the end of the accounting period, such as term deposits, bonds, commercial paper, loans, and other debt securities. This item does not include held-to-maturity investments that have been presented in the item “Cash equivalents” ``` ### Steps to reproduce: Install `l10n_vn_reports` module ### Fix: PO validated: Update the Balance Sheet code formula for the 12381 account opw-6413120 Forward-Port-Of: odoo/enterprise#126763
The Australian payroll update process now includes salary rule category data when refreshing payroll rules. This prevents update failures when new salary categories are introduced, helping payroll maintenance run more reliably.
Original PR description
ir_cron_update_payroll_data updates the payroll data including rules but fails if a new rule category is introduced. This commit adds the hr_salary_rule_category_data file to the list of data files to update. task-6351929 Forward-Port-Of: odoo/enterprise#122392
The Barcode app welcome screen now shows the package scanning instruction when package tracking is enabled. This helps warehouse users understand they can scan a package to find the related transfer, reducing confusion during daily inventory operations.
Original PR description
When packages are enabled, the barcode scanner welcome screen does not display the instructions for scanning a package. ## Steps to reproduce - Enable `Packages` in Inventory settings. - Open the Barcode application. - Observe the instructions listed on the welcome/landing page. - Notice that the instruction `Scan a package to find a transfer` is missing, even though packages are enabled. ## Issue The MainMenu component has a getter barcodeHomeHelper that checks this.packageEnabled to construct the barcode scanner helper bullet points. However, during setup, the configuration value was incorrectly assigned to this.packagesEnabled. ## Fix Correct the variable name typo in the main menu setup so that the package related instructions are correctly displayed when packaging is enabled. [^1] [^1]:  Forward-Port-Of: odoo/enterprise#124658
Users without payroll permissions can now open time off type details without hitting an access error. The payroll-only field is hidden from users who do not have the required access, keeping the Time Off workflow usable for regular employees.
Original PR description
## Steps to reproduce: - Install Payroll and Time off - Create a user with no payroll rights - Log in as this created user - Go to time off and click on one day from the dashboard - Click on the pop-up button next to the time off type - An access error get triggerd that you can't access unpaid_structure_ids ## Cause: As this commit https://github.com/odoo/enterprise/pull/118037/changes/3a66d1405f2fb657c2660db72679e4ac523c3b83 this field have a specific group so when trying to show it in a view for a user that doesn't have this group it will trigger an access error ## Fix: We add the group to the field in the view so if the user doesn't have the group it won't be accessed at all opw-6376005
Appointment cancellation emails are now sent in the language of the customer who booked the appointment, matching the behavior of confirmation emails. This avoids confusing customers with cancellation messages in the staff member's language while preserving the old behavior for non-appointment events.
Original PR description
**Problem:** When an appointment is cancelled, the cancellation email is always sent in the organizer's (staff member's) language, ignoring the booking customer's language. The booking confirmation,…
**Problem:**
When an appointment is cancelled, the cancellation email is always sent in the organizer's (staff member's) language, ignoring the booking customer's language. The booking confirmation, by contrast, is correctly localized.
**Steps to reproduce:**
1. Set a contact's language to a non-default one (e.g. Romanian).
2. Book an appointment for that contact (they are the booker/attendee).
3. Cancel the appointment.
4. The customer received the confirmation in Romanian but the cancellation email arrives in English.
**Current behavior:**
The cancellation email is rendered in the organizer's language.
**Expected behavior:**
The cancellation email is rendered in the booking customer's language, like the confirmation/invitation email.
**Cause of the issue:**
The cancellation uses `appointment_canceled_mail_template`, whose `lang` is `{{ object.partner_id.lang }}`. On `calendar.event`, `partner_id` is `related='user_id.partner_id'`, i.e. the organizer, not the customer. The template is posted once per event (via `_track_template`), so its single rendering language applies to every recipient, including attendees whose own language differs. The confirmation email is unaffected because it is the per-attendee `attendee_invitation_mail_template` (model `calendar.attendee`), rendered once per attendee in that attendee's language.
**Fix:**
Deriving the language from `appointment_booker_id` makes the cancellation consistent with the other appointment mails, which are meant for the person who booked the meeting. It falls back to `partner_id` when there is no booker (e.g. an event not created through the appointment flow), preserving the previous behavior in that case.
opw-6323179
Forward-Port-Of: odoo/enterprise#125927
Forward-Port-Of: odoo/enterprise#124116This fix prevents duplicate report lines from being created when a user clicks to expand the same report line multiple times quickly. As a result, expanded lines can be collapsed reliably, improving the stability of financial report navigation especially on slow connections.
Original PR description
Steps to reproduce:- - Open any report. - Click on a particular line to unfold more than once very fast(or throttle network to 3G) - Once line is unfolded click again to fold that line. - Line is not…
Steps to reproduce:- - Open any report. - Click on a particular line to unfold more than once very fast(or throttle network to 3G) - Once line is unfolded click again to fold that line. - Line is not folding. Cause:- - When we clicked multiple times to unfold line, duplicate child lines were created(as many times as many times we clicked). - Because when first promise was not resolved so `unfolded = false` and we clicked again so new promise also tries to unfold the same line, resulting in unfolding the same line multiple times. - In version 17.0 these duplicate child lines are created but somehow not visible but it breaks `foldLine`. From version 18.0 onwards these duplicate child lines are visible. Solution: In `unfoldLine` set the flag `unfolding`. So in all clicks other than first, we get `unfolding = true` and don't proceed further, preventing unfolding the same line multiple times. task-6260425 Forward-Port-Of: odoo/enterprise#126765 Forward-Port-Of: odoo/enterprise#120392
Portal users editing Knowledge articles can now view and edit existing voice transcription text without seeing the internal-only recording controls. This keeps article editing available while preventing portal users from accessing a feature reserved for internal staff.
Original PR description
Portal users can edit Knowledge articles but should not have access to the voice recording feature, which is reserved for internal users. The readonly component is already used in read-only views, so reusing it in the editor for portal users is safe, it still renders the existing transcription content correctly without exposing any recording controls. Portal users can still edit the text content inside the component thanks to the editable descendants concept, which allows the editor to manage specific editable areas even within a readonly component. Task-6320461 Forward-Port-Of: odoo/enterprise#126606
The cash basis accounting report tests now use the actual configured outstanding receipts account instead of assuming a fixed account code. This prevents false test failures when account codes differ between database setups, improving release reliability without changing user-facing behavior.
Original PR description
Description of the issue this commit addresses: Commit 63f5646cfd75 made the tests use the default outstanding account but hard-coded code 101403. In an all-module database, generated account codes depend on existing accounts, so Outstanding Receipts may use code 101404 and make otherwise correct report assertions fail. --- Desired behavior after this commit is merged: This commit derives the expected report line name from the configured outstanding receipts account, making the assertions independent of its generated code. --- runbot-[231581](https://runbot.odoo.com/odoo/error/231581) Forward-Port-Of: odoo/enterprise#126743 Forward-Port-Of: odoo/enterprise#125652
Uploading a document from a contact now places it in the intended default area instead of reusing the last folder selected in Documents. This prevents files from being accidentally stored in the wrong workspace, reducing confusion and manual cleanup for users.
Original PR description
Steps to reproduce ================== 1. Open Documents. 2. Select Finance. 3. Return to the home page and open the Contacts app. 4. Open any contact. 5. Click the Documents stat button. 6. Upload a document. Issue ===== The document is uploaded to the Finance folder instead of My Drive. Reason ====== When uploading a document using the upload button, we use `currentFolderAccessToken` to determine the destination folder. When opening the Documents view from a contact, `searchpanel_default_folder_id` is set to `False` so that documents are uploaded to the `All` workspace. However, when the search model is loaded, we do not reset `currentFolderAccessToken` when `folder_id` is `False`, causing the previously selected folder (Finance) to be reused. Task-6352242 Forward-Port-Of: odoo/enterprise#123285
Before this commit, turning the camera on during a call could leave the camera off in every member's UI, including the local user's own tile, while the video was already being sent. This happens because toggleVideo awaits network.updateUpload before updateAndBroadcast, and updateUpload waits on the ready promise of every peer. A single member whose handshake never completes holds isCameraOn and isScreenSharingOn for everyone. This commit fixes the issue by broadcasting the state first and
Original PR description
Before this commit, turning the camera on during a call could leave the camera off in every member's UI, including the local user's own tile, while the video was already being sent. This happens because toggleVideo awaits network.updateUpload before updateAndBroadcast, and updateUpload waits on the ready promise of every peer. A single member whose handshake never completes holds isCameraOn and isScreenSharingOn for everyone. This commit fixes the issue by broadcasting the state first and awaiting the upload after. Note that updateUpload sends its info snapshot to the peers synchronously, so they still learn the new track. Back-port of https://github.com/odoo/odoo/pull/279106 Forward-Port-Of: odoo/odoo#280492 Forward-Port-Of: odoo/odoo#280014
Steps to reproduce: 1) Install hr_holidays & hr_attendance. 2) Enable "Display Extra Hours" & "Absence Management" in the Attendance app settings. 3) Make a new employee, in the form view click the "+" button to make a new hr version record for this employee. 4) Set the date to be the first of the last month. 5) In the Attendance app -> New -> Select the new employee. 6) Select the check in and out dates to be from last month, edit the times such that the time worked is between 7 and 8 h
Original PR description
Steps to reproduce: 1) Install hr_holidays & hr_attendance. 2) Enable "Display Extra Hours" & "Absence Management" in the Attendance app settings. 3) Make a new employee, in the form view click the…
Steps to reproduce: 1) Install hr_holidays & hr_attendance. 2) Enable "Display Extra Hours" & "Absence Management" in the Attendance app settings. 3) Make a new employee, in the form view click the "+" button to make a new hr version record for this employee. 4) Set the date to be the first of the last month. 5) In the Attendance app -> New -> Select the new employee. 6) Select the check in and out dates to be from last month, edit the times such that the time worked is between 7 and 8 hours (ex 9:00am to 4:55pm). 7) In the Attendance app -> Reporting -> Attendances -> the test employee should have a negative value for "Worked Extra Hours" 8) Create a new time off type, enable "Deduct Extra Hours" & disable "Requires Allocation" use hours as the Unit of measure. 9) Open the time off smart button menu from the test employee's form view. Issue) The value seen in the report from step 7 is not the same as what the user sees in the dashboard. Notes) This issue can occur when an employee scheduled to work for 8 hours a day only clocks in for 7:55 hours leading to a negative extra time. The back-end has the correct value stored and sends it to the browser. The issue occurs because the JavaScript function that converts the decimal number of hours into a string (9.5 -> "9:30") does not work with negative input. This PR resolves that issue. opw-6417543 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280491
Before this commit: Deleting a record that uses a filterable selection field with `whitelist_fname` raises a traceback because the record field data becomes undefined during deletion. Steps to reproduce: 1. Install Belgium Accounting (l10n_be). 2. Create a contact and set a Peppol scheme and endpoint. 3. Delete the contact -> a traceback is raised. After this commit: The selection field safely handles undefined record field data during record deletion without causing an error. no-t
Original PR description
Before this commit: Deleting a record that uses a filterable selection field with `whitelist_fname` raises a traceback because the record field data becomes undefined during deletion. Steps to reproduce: 1. Install Belgium Accounting (l10n_be). 2. Create a contact and set a Peppol scheme and endpoint. 3. Delete the contact -> a traceback is raised. After this commit: The selection field safely handles undefined record field data during record deletion without causing an error. no-task Forward-Port-Of: odoo/odoo#280153 Forward-Port-Of: odoo/odoo#278808
10 changes
Resolved issues and error corrections
The Barcode app welcome screen now correctly shows guidance to scan a package when package tracking is enabled. This helps warehouse users discover package-related transfers directly from the scanner landing page and avoids confusion during daily inventory operations.
Original PR description
When packages are enabled, the barcode scanner welcome screen does not display the instructions for scanning a package. ## Steps to reproduce - Enable `Packages` in Inventory settings. - Open the Barcode application. - Observe the instructions listed on the welcome/landing page. - Notice that the instruction `Scan a package to find a transfer` is missing, even though packages are enabled. ## Issue The MainMenu component has a getter barcodeHomeHelper that checks this.packageEnabled to construct the barcode scanner helper bullet points. However, during setup, the configuration value was incorrectly assigned to this.packagesEnabled. ## Fix Correct the variable name typo in the main menu setup so that the package related instructions are correctly displayed when packaging is enabled. [^1] [^1]:  Forward-Port-Of: odoo/enterprise#124658
Vietnam financial reports now classify short-term loan balances under held-to-maturity investments, aligning the balance sheet with Circular 99/2025. This helps businesses produce compliant local reports without manual reclassification.
Original PR description
### Expected behavior: As per circular 99/2025, short-term loan (12831) balance is required to fall under Held to Maturity Investment (Code 123) instead of 112, translated: ``` Short-term held-to-maturity investments (Code 123): includes held-to-maturity investments with a remaining term of 12 months or less from the end of the accounting period, such as term deposits, bonds, commercial paper, loans, and other debt securities. This item does not include held-to-maturity investments that have been presented in the item “Cash equivalents” ``` ### Steps to reproduce: Install `l10n_vn_reports` module ### Fix: PO validated: Update the Balance Sheet code formula for the 12381 account opw-6413120 Forward-Port-Of: odoo/enterprise#126763
Users can now remove tags from documents directly in the list view without seeing an error. The fix keeps the row in edit mode while interacting with tag controls, so the tag removal completes as expected while normal clicks outside the row still exit editing.
Original PR description
Removing a tag from a document in the list view raises "onDelete is not a function" and the tag is not removed. ### Steps to reproduce - Open Documents in list view with the Tags column shown. -…
Removing a tag from a document in the list view raises "onDelete is not a function" and the tag is not removed. ### Steps to reproduce - Open Documents in list view with the Tags column shown. - Select a document that has tags. - Click the tags cell to edit it, then click a tag's delete button. => The error is raised. ### Cause `onGlobalClick` calls `leaveEditMode()` for every click, in-row clicks included, because it runs before the guard that ignores them (the base list renderer returns early on in-row clicks). A tag's delete button, and its `onDelete` callback, only exist while the field is editable. Clicking it fires `onGlobalClick` first, which leaves edit mode and turns the field read only, so `onDelete` becomes undefined. The click then reaches the button and calls it, raising the error. ### Fix Return early on clicks originating inside a data row, before `leaveEditMode()`, as the base list renderer does. Clicks outside a row still leave edit mode. opw-6399042
Appointment cancellation emails are now sent in the language of the customer who booked the appointment, matching the behavior of confirmation emails. If no booking customer is available, the system keeps the previous organizer-language behavior to avoid disrupting other calendar events.
Original PR description
**Problem:** When an appointment is cancelled, the cancellation email is always sent in the organizer's (staff member's) language, ignoring the booking customer's language. The booking confirmation,…
**Problem:**
When an appointment is cancelled, the cancellation email is always sent in the organizer's (staff member's) language, ignoring the booking customer's language. The booking confirmation, by contrast, is correctly localized.
**Steps to reproduce:**
1. Set a contact's language to a non-default one (e.g. Romanian).
2. Book an appointment for that contact (they are the booker/attendee).
3. Cancel the appointment.
4. The customer received the confirmation in Romanian but the cancellation email arrives in English.
**Current behavior:**
The cancellation email is rendered in the organizer's language.
**Expected behavior:**
The cancellation email is rendered in the booking customer's language, like the confirmation/invitation email.
**Cause of the issue:**
The cancellation uses `appointment_canceled_mail_template`, whose `lang` is `{{ object.partner_id.lang }}`. On `calendar.event`, `partner_id` is `related='user_id.partner_id'`, i.e. the organizer, not the customer. The template is posted once per event (via `_track_template`), so its single rendering language applies to every recipient, including attendees whose own language differs. The confirmation email is unaffected because it is the per-attendee `attendee_invitation_mail_template` (model `calendar.attendee`), rendered once per attendee in that attendee's language.
**Fix:**
Deriving the language from `appointment_booker_id` makes the cancellation consistent with the other appointment mails, which are meant for the person who booked the meeting. It falls back to `partner_id` when there is no booker (e.g. an event not created through the appointment flow), preserving the previous behavior in that case.
opw-6323179
Forward-Port-Of: odoo/enterprise#125927
Forward-Port-Of: odoo/enterprise#124116The portal now correctly reduces a user's pending signature count after they sign a document. This prevents users from seeing completed signature requests as still awaiting their action.
Original PR description
Version: 18.0 Steps to reproduce: - Create a sign request with two signers. - Assign the first signature to a portal user. - Log in as the portal user and sign the document. Issue: After signing, the to-sign count in the portal does not decrease. This is because the query only checks the overall sign request state instead of the individual signer's item state, so the count remains unchanged Fix: Added an item level state check to the count query so it only counts items that are still pending for that specific user. Task ID: 6412976 Forward-Port-Of: odoo/enterprise#125632
Portal users can continue editing Knowledge articles that contain voice transcriptions, but they will no longer see or access the recording feature reserved for internal users. Existing transcription content remains visible and editable where appropriate, reducing confusion and helping keep restricted tools limited to the right audience.
Original PR description
Portal users can edit Knowledge articles but should not have access to the voice recording feature, which is reserved for internal users. The readonly component is already used in read-only views, so reusing it in the editor for portal users is safe, it still renders the existing transcription content correctly without exposing any recording controls. Portal users can still edit the text content inside the component thanks to the editable descendants concept, which allows the editor to manage specific editable areas even within a readonly component. Task-6320461 Forward-Port-Of: odoo/enterprise#126606
Uploading a document from a contact now places it in the intended default Documents area instead of reusing the last folder selected in Documents. This prevents files from being accidentally saved under an unrelated workspace such as Finance, reducing confusion and manual cleanup.
Original PR description
Steps to reproduce ================== 1. Open Documents. 2. Select Finance. 3. Return to the home page and open the Contacts app. 4. Open any contact. 5. Click the Documents stat button. 6. Upload a document. Issue ===== The document is uploaded to the Finance folder instead of My Drive. Reason ====== When uploading a document using the upload button, we use `currentFolderAccessToken` to determine the destination folder. When opening the Documents view from a contact, `searchpanel_default_folder_id` is set to `False` so that documents are uploaded to the `All` workspace. However, when the search model is loaded, we do not reset `currentFolderAccessToken` when `folder_id` is `False`, causing the previously selected folder (Finance) to be reused. Task-6352242 Forward-Port-Of: odoo/enterprise#123285
Australian payroll data updates now include salary rule categories, preventing update failures when new categories are added. This helps keep payroll configuration updates reliable with minimal disruption.
Original PR description
ir_cron_update_payroll_data updates the payroll data including rules but fails if a new rule category is introduced. This commit adds the hr_salary_rule_category_data file to the list of data files to update. task-6351929 Forward-Port-Of: odoo/enterprise#122392
This fix makes the automated batch picking flow wait for the first scanned product quantity to update before moving to the next item. It prevents quantities from being assigned to the wrong line, improving reliability of barcode picking validation.
Original PR description
Problem: When scanning the first product, the second move line is clicked immediately after.…
Problem: When scanning the first product, the second move line is clicked immediately after. https://github.com/odoo/enterprise/blob/bfb8bab7636a84c829f16087771e00bf31ad2cce/stock_barcode_picking_batch/static/tests/tours/tour_test_barcode_batch_flows.js#L1529-L1541 If this happens before the first scan has finished, its quantity is incorrectly applied to the second move line that is clicked. This causes a 0 - 3 split instead of a 1 - 2 split, which results in there only being 6 move lines instead of 7. We updated our quantity on the `currentLine` https://github.com/odoo/enterprise/blob/bfb8bab7636a84c829f16087771e00bf31ad2cce/stock_barcode/static/src/models/barcode_model.js#L1478 When finding `currentLine`, we go through `_findLine` and use `this.selectedLineVirtualId`, which is the one that is currently selected in the UI https://github.com/odoo/enterprise/blob/bfb8bab7636a84c829f16087771e00bf31ad2cce/stock_barcode/static/src/models/barcode_model.js#L1705-L1712 Purpose: By adding this step, we wait until the first line’s quantity to be updated before it moves on and clicks on the second product. runbot-941211 Forward-Port-Of: odoo/enterprise#126847 Forward-Port-Of: odoo/enterprise#124162
Features or functions removed from Odoo
An obsolete automated test was removed because it checked behavior that is no longer supported after a prior change. This keeps the accounting test suite aligned with the current, simpler reconciliation model setup that uses contains-style matching instead of complex regular expressions.
Original PR description
After merging pr #117256 the behavior for creating auto reconcile models for structured reference is no longer working so the test for checking that behavior should be removed. This was an FP request to use contains instead of regex in auto reconcile model creation because it was too complicated for users. no task id Forward-Port-Of: odoo/enterprise#126635
4 changes
Enhancements to existing features
Bank synchronization now recognizes a new type of warning from Odoo’s financial connection service that should not stop the connection. This helps avoid unnecessary error states, keeping online bank links active when the issue is not blocking.
Original PR description
Odoofin now sends a 'non_blocking_error' error response to indicate that the state on account.online.link shouldn't be set to error. In this commit, we start using it. Task ID: 6358809 Forward-Port-Of: odoo/enterprise#123287
Resolved issues and error corrections
This fix prevents approval requests from trying to send notification messages while temporary form data is being recalculated. It avoids crashes when users edit forms that include approval request links added through Studio, especially for approved or refused requests.
Original PR description
**Before this change** We have the potential to attempt to send a notification email from virtual records created by our `BaseModel.new()` method. This can happen during an `onchange` request, given…
**Before this change** We have the potential to attempt to send a notification email from virtual records created by our `BaseModel.new()` method. This can happen during an `onchange` request, given that we'll be working with a virtual "snapshot" record to recompute potentially changed values for our origin record after a change to one or more values. This issue manifests when using Studio to attach a many2many field to a form view, where the related model is "Approval Request". If an approval request record in either the "Approved" or "Refused" state is attached to our record via this new Studio field, any `onchange` requests will trigger this bug. This is because we can't send messages on a virtual record. **After this change** Prevent the creation and sending of a message if we are computing the request status of a virtual `approval.request()` record. The field `request_status` on this model is computed and stored, but the fact that it is a computed field means that it must be recomputed for a virtual record, even if the origin record already has a stored `request_status`. Thus, we may need to compute a `request_status` value for an ephemeral "snapshot" record. Though the issue only manifests for the "Approved" and "Refused" states, this PR expands on a test that covers every approval request state. opw-6390453 Forward-Port-Of: odoo/enterprise#125374
Fixed an issue where uploading a document from a contact could incorrectly place it in the previously selected Documents folder, such as Finance. Uploads opened from a contact now go to the intended default workspace, helping users find files where they expect them.
Original PR description
Steps to reproduce ================== 1. Open Documents. 2. Select Finance. 3. Return to the home page and open the Contacts app. 4. Open any contact. 5. Click the Documents stat button. 6. Upload a document. Issue ===== The document is uploaded to the Finance folder instead of My Drive. Reason ====== When uploading a document using the upload button, we use `currentFolderAccessToken` to determine the destination folder. When opening the Documents view from a contact, `searchpanel_default_folder_id` is set to `False` so that documents are uploaded to the `All` workspace. However, when the search model is loaded, we do not reset `currentFolderAccessToken` when `folder_id` is `False`, causing the previously selected folder (Finance) to be reused. Task-6352242 Forward-Port-Of: odoo/enterprise#123285
This fix prevents a timing issue in the batch picking barcode flow where a scan could be applied to the wrong item if the next line was selected too quickly. It ensures the first item quantity is updated before moving on, making automated validation of warehouse batch picking more reliable.
Original PR description
Problem: When scanning the first product, the second move line is clicked immediately after.…
Problem: When scanning the first product, the second move line is clicked immediately after. https://github.com/odoo/enterprise/blob/bfb8bab7636a84c829f16087771e00bf31ad2cce/stock_barcode_picking_batch/static/tests/tours/tour_test_barcode_batch_flows.js#L1529-L1541 If this happens before the first scan has finished, its quantity is incorrectly applied to the second move line that is clicked. This causes a 0 - 3 split instead of a 1 - 2 split, which results in there only being 6 move lines instead of 7. We updated our quantity on the `currentLine` https://github.com/odoo/enterprise/blob/bfb8bab7636a84c829f16087771e00bf31ad2cce/stock_barcode/static/src/models/barcode_model.js#L1478 When finding `currentLine`, we go through `_findLine` and use `this.selectedLineVirtualId`, which is the one that is currently selected in the UI https://github.com/odoo/enterprise/blob/bfb8bab7636a84c829f16087771e00bf31ad2cce/stock_barcode/static/src/models/barcode_model.js#L1705-L1712 Purpose: By adding this step, we wait until the first line’s quantity to be updated before it moves on and clicks on the second product. runbot-941211 Forward-Port-Of: odoo/enterprise#126847 Forward-Port-Of: odoo/enterprise#124162
5 changes
Resolved issues and error corrections
This update reverts a dependency configuration change in the Helpdesk Knowledge website module because such dependency changes are not allowed in stable releases. It helps keep the stable version predictable and reduces the risk of unexpected installation or upgrade behavior.
Original PR description
Changes to depends are not allowed in stable Merge through saas-19.1 https://github.com/odoo/enterprise/pull/126260 Forward-Port-Of: odoo/enterprise#126892
The Vietnam reports module now classifies short-term loan balances under held-to-maturity investments as required by Circular 99/2025. This helps businesses produce compliant balance sheets with the affected amounts shown in the correct reporting line.
Original PR description
### Expected behavior: As per circular 99/2025, short-term loan (12831) balance is required to fall under Held to Maturity Investment (Code 123) instead of 112, translated: ``` Short-term held-to-maturity investments (Code 123): includes held-to-maturity investments with a remaining term of 12 months or less from the end of the accounting period, such as term deposits, bonds, commercial paper, loans, and other debt securities. This item does not include held-to-maturity investments that have been presented in the item “Cash equivalents” ``` ### Steps to reproduce: Install `l10n_vn_reports` module ### Fix: PO validated: Update the Balance Sheet code formula for the 12381 account opw-6413120 Forward-Port-Of: odoo/enterprise#126763
Deleting a product document now also removes the hidden email alias created for its mirrored document record. This prevents leftover aliases from causing errors when users create new aliases later.
Original PR description
Steps to reproduce: 1. Install documents and sales 2. Check product document centralization from settings, and no alias should be set 3. Now go to setting>techinical>emial>aliases>remove default…
Steps to reproduce: 1. Install documents and sales 2. Check product document centralization from settings, and no alias should be set 3. Now go to setting>techinical>emial>aliases>remove default filter and check total count 4. Go to products and add a document from the smart button 5. Now again check the aliases total count (1 increased) 6. Delete the document on the product. 7. Alias is not deleted 8. Try to add an alias from the settings Issue: - When `documents_product` is installed and product document centralization is enabled, deleting a `product.document` can leave an orphan `mail.alias` resulting in an error while creating a new alias. Cause: - A product document owns an `ir.attachment`, and that attachment is mirrored as a `documents.document` for `product.product` / `product.template`. In 18.0, `documents.document` inherits `mail.alias.mixin`, so the mirrored document also owns a `mail.alias`. - Deleting `product.document` deletes its `ir.attachment`. The linked `documents.document` is then removed by SQL `ondelete='cascade'` on `attachment_id`, not through ORM `unlink()`. Because the document unlink logic does not run, the alias cleanup from the mail alias mixin is skipped. Why not reproducible in 19.0: - In 19.0, `documents.document` uses `mail.alias.mixin.optional` instead of `mail.alias.mixin`. Binary mirrored documents no longer create a `mail.alias` unless an `alias_name` is explicitly set, so product mirrored documents do not create aliases in the first place. Solution: - Override `ir.attachment.unlink()` in `documents`. before deleting attachment, collect linked document aliases then delete attachment. SQL cascade deletes linked documents.document rows and now we delete collected aliases. opw-6019294 Forward-Port-Of: odoo/enterprise#110486
Fixed an issue where uploading a document from a contact could place it in the last-used Documents folder, such as Finance, instead of the intended personal workspace. This prevents files from being stored in the wrong location and reduces confusion when managing contact-related documents.
Original PR description
Steps to reproduce ================== 1. Open Documents. 2. Select Finance. 3. Return to the home page and open the Contacts app. 4. Open any contact. 5. Click the Documents stat button. 6. Upload a document. Issue ===== The document is uploaded to the Finance folder instead of My Drive. Reason ====== When uploading a document using the upload button, we use `currentFolderAccessToken` to determine the destination folder. When opening the Documents view from a contact, `searchpanel_default_folder_id` is set to `False` so that documents are uploaded to the `All` workspace. However, when the search model is loaded, we do not reset `currentFolderAccessToken` when `folder_id` is `False`, causing the previously selected folder (Finance) to be reused. Task-6352242 Forward-Port-Of: odoo/enterprise#123285
This fix makes the shared budget test use a properly configured second company instead of relying on whatever company data already exists. It helps prevent false test failures related to missing warehouse setup, improving release stability without changing user-facing budget features.
Original PR description
Description of the issue this commit addresses: The shared budget test selects an arbitrary existing company. On saas-18.3, that company can have no warehouse or incoming picking type. Creating its purchase order then fails because the required picking_type_id is empty. --- Desired behavior after this commit is merged: This commit uses the standard accounting test helper to create a fully configured second company, making the shared budget test independent of existing company and warehouse data. --- runbot-[226623](https://runbot.odoo.com/odoo/error/226623)
13 changes
Enhancements to existing features
Customer portal counters are now calculated with only the level of detail each portal item needs. Alerts still show exact counts, while other portal entries use a faster existence check to improve performance without changing what users see.
Original PR description
Make sure the search_count performed on the portal entries to get the customer portal counters are using the correct limit depending on what is actually needed. Portal entries of type "alert" need the accurate count to be displayed on the alert, no limit should be used. Other portal entries only need to know whether there is at least one matching record in order to display the entry or not, limiting the search_count to 1 to gain some performance. Task-5387495
This update adds more automated checks around marketing automation participant synchronization, especially when scheduled processing fails. The goal is to catch regressions earlier and improve confidence in future performance and reliability work for large databases.
Original PR description
Backport various tests added in Odoo 19.4+ in order to better spot potential regressions. Add new tests for synchronization cron behavior, notably in case of failure. Forward-Port-Of: odoo/enterprise#126810 Forward-Port-Of: odoo/enterprise#126334
The AI-powered website sales module was updated to stay compatible with recent changes in the main Odoo platform. This helps keep the online sales experience reliable and ready for continued improvements.
Original PR description
Community PR: - https://github.com/odoo/odoo/pull/242995 task-5405584
Test helpers now freeze all Odoo time sources consistently, so automated tests no longer compare a frozen application time with a live database timestamp. This reduces flaky test results across affected apps without changing production behavior.
Original PR description
*= marketing_automations, whatsapp_events
**Odoo has two clocks**
When you write a test and “freeze time,” you usually mean everything should behave as if it’s that moment. In Odoo, that’s not one clock it’s two:
| Source | What it drives |
|:------------|-------------:|
| datetime.now() / fields.Datetime.now() | Domains, defaults, Python logic, “is this overdue?” checks |
| env.cr.now() / cursor.now() | ORM create_date, write_date, and other DB-timestamp fields |
Freezegun only freezes the first one.
So a test can end up in this broken state:
```py
with freeze_time("2024-01-01"):
record = self.env['res.partner'].create({'name': 'Test'})
# Datetime.now() says: 2024-01-01
# record.create_date says: 2025-06-05 (real DB time)
```
The test thinks it’s January 1st, but the record was stamped with today. Any logic comparing “now” vs create_date will be wrong or flaky.
com: https://github.com/odoo/odoo/pull/268541
task-6124007Bank synchronization now recognizes a new type of temporary, non-blocking error from Odoofin. This helps avoid marking a bank connection as failed when the issue does not require stopping the synchronization flow.
Original PR description
Odoofin now sends a 'non_blocking_error' error response to indicate that the state on account.online.link shouldn't be set to error. In this commit, we start using it. Task ID: 6358809 Forward-Port-Of: odoo/enterprise#126737 Forward-Port-Of: odoo/enterprise#123287
In the payroll run wizard, clicking an employee row now opens that employee's record instead of changing the selection. This makes it faster and clearer for payroll users to review employee details during pay run preparation.
Original PR description
Currently, clicking a row on the first screen of the payrun wizard toggles the employee selection instead of opening the employee record. This change ensures that clicking a row directly opens the employee record. task-6372551
Resolved issues and error corrections
The Barcode app now correctly shows the instruction to scan a package when package handling is enabled. This helps warehouse users find transfers from package barcodes directly from the welcome screen and avoids confusion during scanning workflows.
Original PR description
When packages are enabled, the barcode scanner welcome screen does not display the instructions for scanning a package. ## Steps to reproduce - Enable `Packages` in Inventory settings. - Open the Barcode application. - Observe the instructions listed on the welcome/landing page. - Notice that the instruction `Scan a package to find a transfer` is missing, even though packages are enabled. ## Issue The MainMenu component has a getter barcodeHomeHelper that checks this.packageEnabled to construct the barcode scanner helper bullet points. However, during setup, the configuration value was incorrectly assigned to this.packagesEnabled. ## Fix Correct the variable name typo in the main menu setup so that the package related instructions are correctly displayed when packaging is enabled. [^1] [^1]:  Forward-Port-Of: odoo/enterprise#124658
AI chat now works more reliably when users move into areas such as Shopfloor where view options may not be available. The change prevents an error that could stop users from sending AI chat messages in those contexts.
Original PR description
### Issue When AI chat is used in views where `config.viewSwitcherEntries` is not initialized, sending a message raises a `TypeError` because the code attempts to call `.map()` on an undefined value.…
### Issue
When AI chat is used in views where `config.viewSwitcherEntries` is not initialized, sending a message raises a `TypeError` because the code attempts to call `.map()` on an undefined value.
### Steps to Reproduce
[Video](https://drive.google.com/file/d/1i7kWDnmv4mebGtf1to12BNHCG5omBTXl/view?usp=sharing)
1. Click the **Ask AI** button.
2. Open the AI chat.
3. Keep it open and navigate to the **Shopfloor** app.
4. Send a message in the AI chat.
### Error
```text
TypeError: Cannot read properties of undefined (reading 'map')
at WithSearch.getCurrentViewInfo
```
### Fix
Safely handle cases where `config.viewSwitcherEntries` is undefined by using optional chaining and falling back to an empty array.
**Before**
```js
result.available_view_types = config.viewSwitcherEntries.map((v) => v.type);
```
**After**
```js
result.available_view_types =
config.viewSwitcherEntries?.map((v) => v.type) || [];
```
opw-6414684
Forward-Port-Of: odoo/enterprise#126738
Forward-Port-Of: odoo/enterprise#125821This fixes payroll behavior so refund payslips are not recalculated when a user clicks Compute. This helps preserve the intended refund amounts while still allowing users to clear lines with Reset when needed.
Original PR description
When a payslip is a refund payslip, we don't want to compute it if we click on "compute". The lines can still be reset when clicking on "reset" Forward-Port-Of: odoo/enterprise#126845
Australian payroll data updates now include salary rule category information. This prevents scheduled payroll updates from failing when new categories are added, helping payroll maintenance run more reliably.
Original PR description
ir_cron_update_payroll_data updates the payroll data including rules but fails if a new rule category is introduced. This commit adds the hr_salary_rule_category_data file to the list of data files to update. task-6351929 Forward-Port-Of: odoo/enterprise#122392
Appointment cancellation emails are now sent in the language of the customer who booked the appointment, matching the behavior of confirmation emails. This prevents customers from receiving cancellation notices in the organizer's language and improves communication clarity.
Original PR description
**Problem:** When an appointment is cancelled, the cancellation email is always sent in the organizer's (staff member's) language, ignoring the booking customer's language. The booking confirmation,…
**Problem:**
When an appointment is cancelled, the cancellation email is always sent in the organizer's (staff member's) language, ignoring the booking customer's language. The booking confirmation, by contrast, is correctly localized.
**Steps to reproduce:**
1. Set a contact's language to a non-default one (e.g. Romanian).
2. Book an appointment for that contact (they are the booker/attendee).
3. Cancel the appointment.
4. The customer received the confirmation in Romanian but the cancellation email arrives in English.
**Current behavior:**
The cancellation email is rendered in the organizer's language.
**Expected behavior:**
The cancellation email is rendered in the booking customer's language, like the confirmation/invitation email.
**Cause of the issue:**
The cancellation uses `appointment_canceled_mail_template`, whose `lang` is `{{ object.partner_id.lang }}`. On `calendar.event`, `partner_id` is `related='user_id.partner_id'`, i.e. the organizer, not the customer. The template is posted once per event (via `_track_template`), so its single rendering language applies to every recipient, including attendees whose own language differs. The confirmation email is unaffected because it is the per-attendee `attendee_invitation_mail_template` (model `calendar.attendee`), rendered once per attendee in that attendee's language.
**Fix:**
Deriving the language from `appointment_booker_id` makes the cancellation consistent with the other appointment mails, which are meant for the person who booked the meeting. It falls back to `partner_id` when there is no booker (e.g. an event not created through the appointment flow), preserving the previous behavior in that case.
opw-6323179
Forward-Port-Of: odoo/enterprise#125927
Forward-Port-Of: odoo/enterprise#124116Code cleanup and technical improvements
The IoT connection layer was updated to use Odoo’s newer plugin approach, making the underlying code more flexible and easier to maintain. This should not change day-to-day workflows, but it helps improve reliability and future development for IoT, Point of Sale IoT, and self-ordering IoT features.
Original PR description
OWL3 introduced the concept of Plugins, which can be used similarly to services but are more flexible and have stronger typing. This commit does the following: - Converts the longpolling and websocket services to plugins. - Converts the IoT HTTP service to a plugin, but retains a service wrapper as it is still used in many places. - Use the plugin instead of the service for ORM and notification in all IoT code.
This update keeps German POS certification and Mexican POS e-invoicing modules aligned with a recent loyalty system refactor. It renames an internal coupon reference to a card reference, reducing inconsistency and helping prevent future maintenance issues without changing business workflows.
Original PR description
As part of the pos_loyalty refactor, the pos_orderline.coupon_id field was renamed to pos_orderline.card_id. This PR renames wherever the field is used to keep everything consistent Community PR-[#274951](https://github.com/odoo/odoo/pull/274951) Upgrade PR-[#10948](https://github.com/odoo/upgrade/pull/10948)
3 changes
Resolved issues and error corrections
The Vietnamese financial reports now classify short-term loan balances under held-to-maturity investments instead of cash equivalents. This aligns the balance sheet with Circular 99/2025, improving regulatory accuracy for companies using Vietnam localization reports.
Original PR description
### Expected behavior: As per circular 99/2025, short-term loan (12831) balance is required to fall under Held to Maturity Investment (Code 123) instead of 112, translated: ``` Short-term held-to-maturity investments (Code 123): includes held-to-maturity investments with a remaining term of 12 months or less from the end of the accounting period, such as term deposits, bonds, commercial paper, loans, and other debt securities. This item does not include held-to-maturity investments that have been presented in the item “Cash equivalents” ``` ### Steps to reproduce: Install `l10n_vn_reports` module ### Fix: PO validated: Update the Balance Sheet code formula for the 12381 account opw-6413120 Forward-Port-Of: odoo/enterprise#126763
Users can now successfully re-invite a portal member to a shared Documents folder after that member's previous access expired. This prevents misleading success messages and ensures the invited person regains access as expected.
Original PR description
Sharing a folder with a portal user with an expiration date cannot be done again after the access has expired. The sharing dialog reports success, but the user does not get access and is no longer…
Sharing a folder with a portal user with an expiration date cannot be done again after the access has expired. The sharing dialog reports success, but the user does not get access and is no longer listed. ### Steps to reproduce - In Documents, share a folder with a portal user and set an expiration date. - Wait until the expiration date has passed. - Share the same folder with the same user again from the invite box. => The dialog says the member was added, but the user has no access and does not appear under "People with access". ### Cause The invite box has no expiration field. When re-inviting a user, it updates the existing `documents.access` record and passes `None` for the expiration, which keeps the old `expiration_date`. If that date is already in the past, the user remains expired even though the invite reports success. ### Fix Pass `False` instead of `None` when inviting a member so the existing record's expiration date is cleared. Re-inviting an expired user now restores access. Setting an expiration from the "People with access" list is unchanged. opw-6387559
Uploading a document from a contact now sends it to the intended My Drive/All workspace instead of reusing the last folder selected in Documents. This prevents files from being misplaced, reducing confusion and cleanup for users who work across Contacts and Documents.
Original PR description
Steps to reproduce ================== 1. Open Documents. 2. Select Finance. 3. Return to the home page and open the Contacts app. 4. Open any contact. 5. Click the Documents stat button. 6. Upload a document. Issue ===== The document is uploaded to the Finance folder instead of My Drive. Reason ====== When uploading a document using the upload button, we use `currentFolderAccessToken` to determine the destination folder. When opening the Documents view from a contact, `searchpanel_default_folder_id` is set to `False` so that documents are uploaded to the `All` workspace. However, when the search model is loaded, we do not reset `currentFolderAccessToken` when `folder_id` is `False`, causing the previously selected folder (Finance) to be reused. Task-6352242 Forward-Port-Of: odoo/enterprise#123285
5 changes
Enhancements to existing features
Receipt printer selection now uses the same broader matching rules already applied to preparation printers. This helps businesses connect supported printers more consistently in Point of Sale setups without unnecessary restrictions.
Original PR description
In odoo/enterprise#124306, we removed the subtype from the printer domain, but only for preparation printers. We also update it for receipt printers.
Resolved issues and error corrections
This change reverts a dependency declaration in the Helpdesk Knowledge website module because dependency changes are not allowed in stable releases. It helps keep upgrades predictable and avoids unexpected installation or compatibility effects for customers.
Original PR description
Changes to depends are not allowed in stable Merge through saas-19.1 https://github.com/odoo/enterprise/pull/126260
This fix ensures Google Reserve availability responses never report more open spots than the total spots available. It helps avoid misleading booking information in rare cases where appointment capacity settings are configured inconsistently.
Original PR description
This commit makes sure that we never send more "spots_open" than there are "spots_total" when Google Reserve asks for availabilities. This could happen in very rare case when customers create configurations that do not make sense (for example a table of 6 but no management of capacities and configuring 125 spots per resource). Task-6449615
The IoT device list now keeps pagination working when users open a device record. This prevents navigation issues in the device list and makes managing multiple IoT devices smoother.
Original PR description
Since #72351, the pagination on IoT devices was broken due to how we were getting to the full device form when clicking on a record. We now change the override to use the existing method from the framework `switchToForm` which handles it better. opw-6058532 Forward-Port-Of: odoo/enterprise#126486
MyInvois expects the CountrySubentityCode to be fixed as '17' for customers located outside Malaysia, since Malaysian state codes don't apply to them. Set it to '17' instead of the raw state name for non-Malaysian customers. task-6388521 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280550
Original PR description
MyInvois expects the CountrySubentityCode to be fixed as '17' for customers located outside Malaysia, since Malaysian state codes don't apply to them. Set it to '17' instead of the raw state name for non-Malaysian customers. task-6388521 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#280550