Saturday, September 26, 2026
13 changes · master
Security fixes and vulnerability patches
Public job applications no longer update an existing contact just because the applicant used that contact's email address. This prevents someone submitting an application from changing an internal user's displayed name while still allowing recruitment records to match contacts by email.
Original PR description
Issue: An unauthenticated visitor can submit a public job application with an internal user's email address and another applicant name. The submission then replaces the internal user's display name.…
Issue: An unauthenticated visitor can submit a public job application with an internal user's email address and another applicant name. The submission then replaces the internal user's display name. Steps to reproduce: - Create an internal user and publish a job position. - Apply anonymously using the internal user's email and a different name. - Observe that the internal user's display name is replaced. Cause: `_inverse_partner_email()` handles a partner found from the submitted email as if it had already been explicitly linked to the applicant. It then synchronizes the untrusted applicant values onto that partner. Since public applications are created with elevated privileges, the email only match can modify an internal user's contact. https://github.com/odoo/odoo/blob/3b343f7b865bb1f57802f6df881ce4531d02c1ad/addons/hr_recruitment/models/hr_applicant.py#L228-L247 Solution: Use the applicant details only when the email lookup creates a new partner, and stop synchronization after an implicit email match. This allows applications to remain linked by email without letting the match modify an existing contact, while preserving synchronization for partners already explicitly linked to an applicant. opw-6472303 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286107 Forward-Port-Of: odoo/odoo#283546
Resolved issues and error corrections
The livechat panel styling has been adjusted to align with Odoo's frost design, including the transcript sender and channel information areas. Ended or disconnected chat states now display banners and disabled input areas consistently, giving visitors and operators a more polished and coherent experience.
Original PR description
Also adapt the "Livechat has ended" and "Visitor has disconnected" disabled composer / banner to match frost design. <img width="756" height="822" alt="Screenshot 2026-09-24 at 23 19 15" src="https://github.com/user-attachments/assets/68e831bb-d9c2-4359-8a8f-4a26cbadc4b3" /> <img width="811" height="819" alt="2" src="https://github.com/user-attachments/assets/c7788df1-4224-435a-ad9b-8249960bc43d" /> <img width="755" height="831" alt="Screenshot 2026-09-24 at 23 19 06" src="https://github.com/user-attachments/assets/92853df6-8252-43e7-a38d-1bef7ad50728" /> <img width="811" height="817" alt="4" src="https://github.com/user-attachments/assets/47d5ce3c-1e19-493a-9dde-1c0dcf3c8e99" /> Forward-Port-Of: odoo/odoo#290564
Code cleanup and technical improvements
The mail app now manages record-related background cleanup through a single shared lifecycle mechanism. This internal simplification reduces duplicated cleanup tracking and helps ensure records are properly cleared when the app shuts down, without changing user-facing behavior.
Original PR description
Before this commit, a record keeps the stop functions of its effects in the `disposeFns` set, while its scope already owns its computeds. There is no need for that set, as `scope.onDestroy` takes a stop function. This commit registers each stop function on the scope of the record and drops the set, so that a record has one owner for its computeds and its effects. Destroying the app destroys the scope of every record still in the store.
Documentation and clarification updates
A contributor has signed the Odoo Individual Contributor License Agreement. This confirms the legal permission needed for their related contribution to be accepted and maintained in the project.
Original PR description
Signature of the Odoo Individual Contributor License Agreement v1.0 for @mmircoli-nexapp, needed for the fix related to #290493. I confirm I have read the [CLA](https://github.com/odoo/odoo/blob/18.0/doc/cla/icla-1.0.md) and that the committer email matches the one used in my contributions. Forward-Port-Of: odoo/odoo#290549
The customer portal now shows quantities for hidden-composition sections in the same format as regular product lines. This removes a small visual inconsistency, making sales documents clearer and more polished for customers.
Original PR description
On the customer portal, a section with Hide Composition shows its quantity as 1.00 Units, while product lines show 1 Units. Add `min_precision: 0` to the collapsed section quantity, the same as product lines, so both display the same way. opw-6592535 Forward-Port-Of: odoo/odoo#290387
This fixes sales orders so individual items inside a combo product can no longer be changed separately from the product catalog. It helps keep combo product quantities consistent by requiring users to update the combo as a whole.
Original PR description
Steps to reproduce: - Create a sales order. - Add a combo product to the sales order. - Open the product catalog. - Try to update the quantity of an individual combo item. Issue: - Individual combo items can be updated from the product catalog, whereas the combo product should be updated as a whole. Cause: - In commit https://github.com/odoo/odoo/commit/d71d7b954ce3fa49b5c9d33129499b4deb0fc30a, the `combo_item_id` condition specific to the `sale` module was mistakenly dropped from the product catalog logic. Fix: - Restore the `combo_item_id` condition to make combo item lines read-only in the product catalog, preventing them from being updated individually. opw-6600702 Forward-Port-Of: odoo/odoo#290603
The Discuss call controls now stay disabled while a disconnect request is still being processed, preventing users from clicking Start Call too early and having the action ignored. This makes call reconnection behavior more reliable and avoids related test failures.
Original PR description
Before this commit, a click on "Start Call" right after "Disconnect" was ignored when the server had not answered the leave request yet. The test "only notified of a call disconnection when the server ends the session" failed on it:
Failed to find 1 of ".o-discuss-Call" (Timeout of 10 seconds).
Found 0 instead.
This happens because `leaveCall` ends the call before sending the leave request, since "[FIX] mail: only notify unexpected server call disconnects". As a result, "Start Call" and "Start Video Call" are back while `hasPendingRequest` is still true, and `toggleCall` ignores a click during a pending request.
This commit fixes the issue by disabling these two buttons during a pending request, as the join actions of a call already do. The test waits for "Start Call" to be enabled before clicking it.
https://runbot.odoo.com/odoo/error/947584
Forward-Port-Of: odoo/odoo#290585This fixes a timing issue in an automated mail test that could fail when the system responded slightly slower than expected. The change makes the test wait for the application to be ready before continuing, reducing false failures and improving confidence in test results.
Original PR description
Before this commit, mail_template_dynamic_placeholder_tour could fail when the server answers the onchange of "Applies to" slowly: Tour mail_template_dynamic_placeholder_tour failed at step Check if…
Before this commit, mail_template_dynamic_placeholder_tour could fail when the server answers the onchange of "Applies to" slowly:
Tour mail_template_dynamic_placeholder_tour failed at step Check if
the dynamic placeholder popover is opened
(trigger: div.o_model_field_selector_popover)
This happens because the tour waits a fixed 200ms after picking "Contact" before typing "#" in the subject. The popover needs the model, and the record only holds the model once the onchange answers. On runbot the answer came after 230ms, so "#" showed the "select a model" notification instead of the popover.
This commit fixes the issue by waiting for the internal link button of the many2one, which only renders once the record holds the model.
Note that this step relies on "[FIX] web: cancel the pending search on autocomplete select": without it, a search still pending on the picked value marks the input as edited, which hides that button.
https://runbot.odoo.com/odoo/error/947209
Ref commit: https://github.com/odoo/odoo/pull/287888
Forward-Port-Of: odoo/odoo#290364
Forward-Port-Of: odoo/odoo#290185Quotation templates with lines that do not specify a product now keep their manually entered prices when imported into a sales order. This prevents those custom lines from being incorrectly reset to zero, helping sales teams preserve accurate quote totals.
Original PR description
Importing a quotation template with productless lines caused those lines to have 0 as a price, regardless of whether a manual price was set on the template line. Make sure that some onchanges do not reset the values provided by the template, and that when triggered, the prices recomputation doesn't reset those manual prices either. Forward-Port-Of: odoo/odoo#289652
Calendar alarm notifications now use the application’s current date handling instead of the database clock, making reminders behave consistently when dates are simulated in tests. This helps prevent missed or unreliable reminder behavior and strengthens test coverage for calendar notifications.
Original PR description
This commit makes alarm notifications compatible with methods used in tests to mimic dates such as mock_datetime_and_now, while also fixing related tests. Before the commit, the method fetching potential alarms (i.e.: _get_next_potential_limit_alarm) relied on Postgres's now() function. The problem is that this function is not compatible with freezegun as they don't work at the same level. The solution is therefore to use python's now() function and then integrate the result into the query's body. Furthermore, it is required in tests to update the partner's calendar_last_notif_ack field as it will be by default set to the db's creation date. Error-947109 Forward-Port-Of: odoo/odoo#288576
Fixed an issue where merging contacts while working in a different company could incorrectly combine contacts that each had a linked portal user. The merge check now counts all linked users across companies, helping prevent duplicate user links on one contact and keeping contact data consistent.
Original PR description
Switching companies can let a contact merge bypass the check that prevents multiple users from ending up linked to the same contact. ### Steps to reproduce 1. As a user with Contact Creation rights…
Switching companies can let a contact merge bypass the check that prevents multiple users from ending up linked to the same contact. ### Steps to reproduce 1. As a user with Contact Creation rights and access to companies A and B, select company A. 2. Create two contacts with no company set and grant each portal access. 3. Select only company B. 4. Merge the contacts. The merge succeeds and links both portal users to the surviving contact. With company A selected, the same merge is correctly rejected. ### Cause The wizard checks that the contacts have at most one linked user in total, including archived users. However, it reads `user_ids` with the acting user's permissions. Company record rules hide both portal users when only B is selected, so the check finds none. The subsequent SQL update still moves both users' contact links to the surviving contact. ### Fix Use `sudo()` only for this check, since it must count every user whose link the merge would move. Retain `active_test=False` to include archived users. Add a regression test covering both company selections. opw-6586945 Forward-Port-Of: odoo/odoo#290643 Forward-Port-Of: odoo/odoo#290296
The Swiss payroll employee form now shows the work permit expiration date again. This helps HR keep required employee permit information visible and supports automated contract status checks that rely on that date.
Original PR description
…_date in employee form Add work_permit_expiration_date in Personnal Information page field is added in the standard view here: https://github.com/odoo/odoo/blob/18.0/addons/hr/views/hr_employee_views.xml#L184 this module override standard employee form view by making the entire Personal Information page invisible if CH and rewriting it: https://github.com/odoo/enterprise/blob/18.0/l10n_ch_hr_payroll_elm_transmission/views/l10n_ch_hr_payroll_employee_views.xml#L23 standard module hr_contract use this field in method [update_state](https://github.com/odoo/odoo/blob/18.0/addons/hr_contract/models/hr_contract.py#L184) called by cron [ir_cron_data_contract_update_state](https://github.com/odoo/odoo/blob/18.0/addons/hr_contract/data/hr_contract_data.xml#L53) Forward-Port-Of: odoo/enterprise#129875
This change simplifies how Discuss channel members are created behind the scenes, removing a special internal shortcut and making the process more explicit. It should preserve the same user experience while reducing maintenance risk in the mail and Discuss area.
Original PR description
Before this commit, the channel create sets `_bypass_create_check` in its context, and discuss.channel.member.create sudoes itself when it finds that key. This is the only way to sudo members that the ORM creates as part of the channel create. This commit creates the members after the channel instead, with an explicit sudo, so the member create has no special case anymore. As the members do not exist yet when the channel is created, this commit also: - sets `member_indices` in the channel create, from the member values, instead of computing it from the members. - drops `is_member` from the create rule of discuss.channel (the write rule keeps it). That check never denied a create, since the current user is always added as member. - counts the members of a chat in the channel create, since `_constraint_partners_chat` only runs when `channel_member_ids` is written on the channel. task-4714913