Thursday, July 16, 2026
16 changes · saas-18.4
Enhancements to existing features
The automated lint checks will no longer retry after a failure because these checks should produce consistent results. This avoids wasting staging and build time, especially when longer lint jobs fail and would previously run again unnecessarily.
Original PR description
Test lints should be deterministic so retrying them doesn't make sense. While lints are not the slowest, pylint can take 10~15mn in later branches, and with retrying that's half an hour (and some) to get a failure, which is all that in wasted staging and build time. Forward-Port-Of: odoo/odoo#276600
Resolved issues and error corrections
POS administrators can now open and manage Point of Sale payment method forms without encountering an access error. The payment provider field is only shown to users with the right roles, preventing confusion for other staff.
Original PR description
Only admin users have read access to the `payment.provider` model. Opening the PoS payment method form as a non-admin would raise an access error because the `online_payment_provider_ids` many2many field tries to fetch `payment.provider` records on form load. Grant read-only access on `payment.provider` to `group_pos_manager` so POS admins can use the field. Restrict the field's group in the form view to `point_of_sale.group_pos_manager,base.group_system` so it is not rendered for users without either role. opw-6208656 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#276416 Forward-Port-Of: odoo/odoo#263837
The Accounting app no longer shows a broken ZIP export action for selected invoices, bills, and similar records. Users should use the existing PDF download options instead, avoiding the confusing “Nothing to export” error.
Original PR description
### Steps to reproduce the issue: 1. Download Accounting 2. Go to one move type (ex. customer invoices, vendor bills, etc.) 3. Select some records, click the wheel button and then export ZIP 4. Error raised: Nothing to export ### Cause of the issue: Commit 438603ac forward-ported the zip export feature from v17 to v18, but failed to adapt the action_export_zip function and import the controller route. ### Reason to introduce the fix: It has been decided to completly remove the button EXPORT ZIP since we already have other buttons that download/export the PDFs in a zip file (ex. button Download PDF). opw-6313750 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#273296
A website performance check now handles unexpected query cases without failing. This makes monitoring more reliable and helps avoid interruptions caused by edge-case traffic patterns.
Original PR description
Rather than assign `log_target` and blow up if the third branch is taken, use a `defaultdict` and just increment the count in each branch on hit. The continue in the third branch is not strictly necessary, but that way it protects us if anyone decides to add post-processing which also doesn't account for the third branch. Forward-Port-Of: odoo/odoo#276481
A typo in an accounting field reference was corrected so analytic profitability calculations can use the intended data. This helps avoid incorrect or failed profitability computations in accounting reports or related views.
Original PR description
Fix field name typo. @qrtl QT6381 Forward-Port-Of: odoo/odoo#276119
User-defined filters now show archived users who are still assigned to them. This prevents teams from mistaking a shared filter for a global one and helps administrators understand exactly who a filter applies to.
Original PR description
Since 414e55cf7c397, we can assign multiple users to a user-defined filter but because it's now a many2many, any user that got archived won't be shown in the `user_ids` fields anymore, it could mislead the filter being a global filter; whereas it's not. This commit also display archived users so we can see all users effectively assigned to the user-defined filter. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#275841
This fixes an issue where open menus or popovers could remain visible over the Point of Sale screensaver after inactivity. The screensaver now closes these overlays when it appears, giving staff a cleaner and less confusing idle screen.
Original PR description
Currently, overlays remain open when the POS screensaver is loaded [^1]. #### Steps to reproduce: - Open the POS interface. - Open a dropdown/popover menu (e.g., the navbar hamburger menu). - Wait for the screensaver (SaverScreen) to trigger due to inactivity. - The open dropdown menu remains visible on top of the screensaver. #### Issue Dropdowns and popovers are rendered as active overlays outside the main screen container. While the screensaver setup closes active dialogs, it does not handle active overlays. #### Fix Retrieve the overlay service in SaverScreen and close all active overlays during its setup phase using a dedicated `closeAllOverlays` method. [^1]:  Forward-Port-Of: odoo/odoo#276155 Forward-Port-Of: odoo/odoo#274716
This fix ensures recurring calendar events are exported with the correct recurrence information. It prevents affected calendar apps from rejecting invites, helping users reliably share repeating meetings.
Original PR description
Description of the issue/feature this PR addresses:
Current behavior before PR: meeting.rrule is stored as a full dateutil rrule string, e.g.: "DTSTART:20250218T113209\nRRULE:FREQ=YEARLY;COUNT=720"
Passing the full multi-line string as a single RRULE property value causes vobject to emit two RRULE lines, where the first one ("RRULE:DTSTART:...") has no FREQ. This is not standard-compliant and is rejected by calendar clients (e.g. Thunderbird: "invalid frequency null").
Desired behavior after PR is merged: Only a single RRULE line is generated.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#274490Scheduled generation of missing work entries now uses the language set on the system user instead of defaulting to English. This ensures payroll work entry names appear in the expected language for organizations using localized payroll workflows.
Original PR description
Issue: ---------------------------------------- When generating work entries with the CRON "Generate Missing Work Entries", the name of the work entries is always in English. Steps to reproduce: ---------------------------------------- - Create a new employee, setup a running contract for them - Run the schedule action "Generate Missing Work Entries" - In Payroll > Work Entries, search for the work entries of the new employee - Their name are in French Cause: ---------------------------------------- When running the cron, `self.env.lang` is `False` so the text aren't translated. Solution: ---------------------------------------- In `_cron_generate_missing_work_entries()` we specify `self.env.user.lang` in the context. As `_cron_generate_missing_work_entries()` uses the root user to run, the language of the work entries will be the one specified on Odoobot. opw-6369109 Forward-Port-Of: odoo/odoo#275952
Website editors can no longer accidentally delete the hidden tab link structure when using backspace in a tabs block. This prevents crashes when adding new tabs and keeps tab content properly aligned with tab labels.
Original PR description
Steps to reproduce: 1. Drop a .s_tabs snippet 2. Click inside a tab to move the selection in it 3. Press backspace (remove each tab name + the last one should be empty) 4. Click on the "+" in the sidebar to add a Tab => Crash or on step 3: 3. Press backspace to delete one tab => Check the DOM: the tab has been removed, but the tab-pane element is still in the DOM and won't be deleted. This is easily fixed by adding `oe_unremovable` on tab links. task-4671317 Forward-Port-Of: odoo/odoo#275240
The website builder no longer lets users save product-specific Alternative Products blocks as reusable snippets. Saved custom snippets also become fully editable blocks, so users can move or remove them normally on other pages.
Original PR description
Before this commit, two issues affected custom snippet saves. First, the new builder allowed saving the Alternative Products block from product pages as a custom snippet. This block is generated from the current product alternatives, so it is page-specific content. Second, a block saved as a custom snippet could keep `oe_unremovable` or `oe_unmovable` on its root. A saved custom snippet is meant to become a normal reusable block. Once the user drops it on another page, they should be able to remove it or move it like any other block. After this commit, builder plugins can declare selectors that cannot be saved as custom snippets, and `website_sale` uses this to exclude the Alternative Products block. The saved copy also drops `oe_unremovable` and `oe_unmovable` on its root before calling `ir.ui.view.save_snippet`. task-6296872
This fix prevents the voice detection sensitivity indicator in call settings from showing an outdated level after a user quickly stops a test. It improves the reliability of voice and video settings by ensuring the indicator resets correctly when monitoring stops.
Original PR description
**Purpose of this PR:** Before this PR, the Voice detection sensitivity indicator in call settings could get stuck showing the last detected level after clicking "Stop" right after "Test". The…
**Purpose of this PR:** Before this PR, the Voice detection sensitivity indicator in call settings could get stuck showing the last detected level after clicking "Stop" right after "Test". The `AudioWorkletNode`'s port kept receiving tic messages briefly after `disconnect()`, since disconnecting only unroutes the audio graph and does not stop the worklet from posting pending messages. <img width="546" height="73" alt="voice_test_bug" src="https://github.com/user-attachments/assets/05a10d23-fe60-4a85-b906-bfec6d235ec5" /> Steps to reproduce: 1. Open Voice & Video Settings. 2. Start the Voice detection sensitivity test. 3. Quickly click Stop immediately after clicking Test. 4. It may take a few tries, but eventually the Voice detection sensitivity indicator remains stuck at the last detected level. > [!NOTE] > this is timing-dependent. A tic message must already be in-flight from the worklet thread when `disconnect()` runs, so it won't happen every attempt. This race condition existed in the `disconnect` callback of `_loadAudioWorkletProcessor` since #66611, but stayed silent until #183969 introduced the Voice detection sensitivity feature in call settings, exposing it. This PR clears `port.onmessage` before disconnecting so late tic messages can no longer update the Voice detection sensitivity indicator after monitoring has stopped. Forward-Port-Of: odoo/odoo#275933
This update fixes issues in the Mail app’s web push subscription flow so devices are correctly recognized as belonging to the current user and refreshed subscriptions update existing records instead of creating duplicates. This helps keep browser notification registrations accurate and reduces duplicate device entries.
Original PR description
This fixes two bugs in the web push subscription flow:
- register_devices() compared partner records with 'is not' instead of '!='. Records loaded via sudo() live in a different environment than self.env.user, so 'is not' was always True and the ownership guard never behaved as intended. Use '!=', which compares record identity by model and id as Odoo's ORM intends.
- webclient.js sent the previous subscription endpoint under the snake_case key 'previous_endpoint', while the server reads it as 'previousEndpoint' (kw.get('previousEndpoint', endpoint)). The mismatch meant the lookup always fell back to the new endpoint, so a refreshed subscription created a duplicate device instead of updating the existing one. Send the camelCase key to match the server.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#276082This fixes an internal test issue in the Hong Kong payroll accounting area caused by an incorrect update between versions. It helps ensure payroll-related tests reflect the current date field behavior in this release, reducing the risk of unnoticed regressions.
Original PR description
Fixes a wrong forward port where the test were not updated to set the correct date field, which were changed in 18.4
This fixes how contract update templates are chosen during salary contract flows. The system now uses the current contract version when available, or falls back to the offer's contract template, helping ensure employees receive the right contract update document.
Original PR description
contract update template should come from current version if any or from the offer's contract template. Task-6094733
This fix removes unnecessary blank lines when combining invoice addenda with terms and conditions in Uruguayan electronic invoices. It helps prevent addenda text from being pushed onto a separate page when the content should fit on the same page.
Original PR description
## Context When generating a CFE (Comprobante Fiscal Electrónico) that contains both a configured addenda (e.g. bank account details stored in `l10n_uy_edi_addenda_ids`) and terms & conditions from…
## Context
When generating a CFE (Comprobante Fiscal Electrónico) that contains both a configured addenda (e.g. bank account details stored in `l10n_uy_edi_addenda_ids`) and terms & conditions from the invoice's `narration` field, the resulting addenda string could end up with unnecessary blank lines between the two sections, causing the addenda to be rendered on a separate page even when the logical content fits within the 6-line threshold.
## Root Cause
`_l10n_uy_edi_get_addenda` joins both parts without stripping whitespace from either of them first, and adds two lines between addendas and terms and conditions:
addenda = addenda + "\n\n" + term_and_conditions if addenda else term_and_conditions
Two sources independently introduce extra newlines around the separator:
1. **Addenda content** — `_get_legends` returns the raw `content` field value of each addenda record. These fields commonly end with a trailing `\n`, so the addenda string already ends with a newline before the `"\n"` separator is concatenated.
2. **`html2plaintext`** — the `narration` field is stored as HTML. When converted to plain text, `html2plaintext` typically wraps paragraph content in leading/trailing newlines.
The combination of the trailing `\n` from the addenda, the explicit `"\n\n"` separator, and the leading/trailing `\n` from `html2plaintext` produces 2–3 consecutive newlines, which `splitlines()` counts as blank lines.
A realistic 4-line addenda + 1-line narration thus produces **7 lines** instead of the expected 5, crossing the 6-line threshold in `_get_report_params` and triggering `adenda=true` — which forces the addenda onto a separate page unnecessarily.
## Steps to Reproduce
1. Configure a `l10n_uy_edi.addenda` record of type `addenda` with multi-line content (4 lines)
2. Create and confirm an invoice with `narration` set to a short single-line term
3. Generate the CFE PDF via Uruware.
4. Observe that the addenda is rendered on a separate page despite the logical content being only 5 lines.
<img width="1042" height="448" alt="image" src="https://github.com/user-attachments/assets/b538211c-5f37-4648-979d-99cd75cf31c2" />
## Fix
Strip leading and trailing whitespace (including newlines) from both parts before joining them. The ternary is also replaced with an explicit `if/else` for clarity:
def _l10n_uy_edi_get_addenda(self):
addenda = self.l10n_uy_edi_document_id._get_legends("addenda", self)
if self.narration:
term_and_conditions = html2plaintext(self.narration).strip()
if addenda:
addenda = addenda.strip() + "\n" + term_and_conditions
else:
addenda = term_and_conditions
return self._l10n_uy_edi_clean_non_ascii_chars(addenda)
This guarantees exactly one `\n` separator between sections regardless of how the content fields were stored or how `html2plaintext` formatted the narration.
The threshold logic in `_get_report_params` is unchanged: addendas that genuinely exceed 6 lines (after wrapping at 140 chars) continue to be printed on a dedicated page.
Result
<img width="1117" height="456" alt="image" src="https://github.com/user-attachments/assets/3a2d942c-8c37-40f6-bc25-470c0bd25b08" />
Forward-Port-Of: odoo/enterprise#119283