Thursday, September 10, 2026
21 changes · saas-19.4
Resolved issues and error corrections
Fixes an issue where applying text animation across headings and paragraphs in the website builder could disrupt page alignment. This helps editors animate selected text without unexpectedly changing the layout of centered content.
Original PR description
### Problem: Text animation used a single span around the full selection range. When a selection crossed block elements, this placed headings and paragraphs inside a span, producing invalid HTML and changing their layout. ### Steps to reproduce: 1. Drag & drop a snippet (like the "Cover" block) that contains page centered text 2. Select a range of text spanning multiple block elements within the added snippet 3. Add an animation to the selected text. <img width="800" height="200" alt="image" src="https://github.com/user-attachments/assets/45074927-4d60-4517-b65b-7ba6d667e060" /> ### Solution: This PR splits the selection by block and creates one inline animation wrapper per block instead. The builder applies animation options to the resulting elements as one group, without changing the document's block structure. task-5155887 Forward-Port-Of: odoo/odoo#283176
This fixes an issue where Odoo could mark a chatter message as edited even when the user only opened edit mode and saved without changing anything. The change improves trust in message history by ensuring the edited label appears only after a real content change.
Original PR description
Steps to reproduce: ------------------------------------- 1. Open the chatter of any record 2. Open the full composer for the Log Note 3. Add some text and paste some images > Post the log note 4.…
Steps to reproduce: ------------------------------------- 1. Open the chatter of any record 2. Open the full composer for the Log Note 3. Add some text and paste some images > Post the log note 4. Click on the Edit option 5. Save the message without editing anything Observation: ------------------------------------- You will notice that the (edited) label appears even though the message wasn't edited at all, only the edit mode was made active. Issue: ------------------------------------- When a message is posted via the mail composer, the email conversion pipeline injects MSO conditional comments like `<!--<![endif]-->` and `<!--[if mso]>...<![endif]-->` into the HTML body. These comments are added by the `_hideForOutlook` and `createMso` https://github.com/odoo/odoo/blob/083e091eaa7d983f6c826c02b0d0652a821227fb/addons/mail/static/src/views/web/fields/html_mail_field/convert_inline.js#L1978-L1988 https://github.com/odoo/odoo/blob/083e091eaa7d983f6c826c02b0d0652a821227fb/addons/mail/static/src/views/web/fields/html_mail_field/convert_inline.js#L1699-L1707 functions to ensure Outlook compatibility, they wrap responsive elements so that Outlook receives simplified table-based fallbacks while modern clients see the original layout. The stored message body on the server retains these comments. When a user clicks "Edit" on such a message, the body is loaded into the OdooEditor. The browser's DOM parser treats `<!--<![endif]-->` as standard HTML comment nodes, which are not preserved in `innerHTML` serialization. So the editor returns the body without these comments, even if the user made no changes. The `edit()` method in then compares `updatedBodyEl.innerHTML` (from editor, no comments) against `messageBodyEl.innerHTML` (from server, has comments), finds a difference, and sends a update to the backend, which stamps the message with the (edited) label. Solution: ------------------------------------- Before comparing innerHTML, strip all HTML comment nodes from both the original and updated body elements. This is done on throwaway DOM elements created solely for comparison. The actual body sent to the server (`body` parameter) is never modified. Note: ------------------------------------- An alternative approach would be to strip comments at the string level using a regex (`html.replace(/<!--[\s\S]*?-->/g, '')`) before creating the DOM elements. This is valid since HTML comment syntax `(<!--...-->)` is strictly defined and no nesting is allowed, so the regex is reliable. opw-6328529 Forward-Port-Of: odoo/odoo#287472 Forward-Port-Of: odoo/odoo#274927
This fix prevents a website image upload test from accidentally trying to contact an external image service. It makes the automated test more stable and avoids false failures in validation runs.
Original PR description
test_02_image_upload_progress_unsplash monkeypatches HTML_Editor.media_library_search and Web_Unsplash.fetch_unsplash_images to avoid calling third-party APIs during the tour. But the routing map is cached (the "routing" ormcache) with the controller endpoints baked in: when it was already built with the original methods, the patches above are ignored. The original media_library_search then runs and performs a real HTTP request, which is blocked by the test suite, failing the tour with "Couldn't reach API endpoint". Invalidate the "routing" ormcache after patching so the requests are routed to the patched methods. Same change as done in https://github.com/odoo/odoo/pull/271536. https://runbot.odoo.com/odoo/error/939945
Creating a new job position no longer shows the same “Job Position created” message twice in the activity log. This removes visual clutter and makes recruitment records easier for users to read.
Original PR description
When creating a new job position, "Job Position created" was rendered twice in the chatter log. This occurred because the mail subtype definition specified a redundant `description` field with the exact same text as the subtype's name, causing the chatter logic to display both. Removing the explicit `description` field ensures the message is only displayed once upon job creation. Task: 6486002 Forward-Port-Of: odoo/odoo#285018
The Mexican POS self-invoicing test was adjusted to match the newer customer update flow. This helps ensure receipts and QR-code invoicing continue to work correctly now that public users can no longer edit customer details directly.
Original PR description
Before the related pr commit: - Public users could update customer data during the self-invoicing flow. - The test_qr_code_receipt_mx test relied on this behavior when updating customer data. After the ref commit: - Public users can no longer update customer data during self-invoicing. - Update test_qr_code_receipt_mx to create a new partner with the required customer data when the order is not linked to a customer. Related PR: odoo/odoo#283470 Task-6272660 Forward-Port-Of: odoo/enterprise#130700 Forward-Port-Of: odoo/enterprise#129948
This fix prevents spreadsheet cells from showing incorrect values when opened in Hoot test debug mode. It disables cell animations during these tests, avoiding display issues caused by the test environment while leaving normal spreadsheet behavior unchanged.
Original PR description
If you try to open a spreadsheet in debug mode in the Hoot tests, you will often end up with cells with wrong displayed data. That's because cell animations don't work in hoot (it patches `requestAnimationFrame`), so we end up with cell animations stuck in the first frame of the animation. We can simply disable the cell animations in the Hoot tests, as animations are not relevant to the tests. Task: [4909027](https://www.odoo.com/odoo/2328/tasks/4909027) Forward-Port-Of: odoo/odoo#216620
The missing e-invoice check now opens only the invoices that were actually identified as missing an e-invoice. This prevents users from seeing unrelated or all eligible invoices, reducing confusion during Indian GST return review.
Original PR description
The `missing_einvoice` check passed custom `views` but no explicit `domain`, so its action ignored the `moves` recordset and instead opened all matching `account.move` records — an unfiltered list when no invoices were missing, and every eligible invoice (not just the flagged ones) otherwise.
Guard the action to only build when `moves` is non-empty, and pass `domain=[('id', 'in', moves.ids)]` so it's always scoped to the invoices actually found.
task-6544790
Forward-Port-Of: odoo/enterprise#130483The web code editor now correctly allows protected attributes in self-closing template tags to be changed or removed when appropriate. This prevents editing issues in Odoo's template editor and makes the behavior consistent with regular tags.
Original PR description
Currently, when we get readonly attributes to prevent overwriting or deletion, we only ignore them if the selection that is being modified is contained between `<>` tags. To rectify this behavior, we also include the `<\>` self closing tags so that they may also be overwritten/deleted. opw-6325841 Forward-Port-Of: odoo/odoo#287393
This fix prevents the cursor from jumping to the start of a navigation link when users click inside editable link text in the website builder. It makes editing navigation labels smoother and reduces frustration during website customization.
Original PR description
Problem: Clicking inside a navigation link in website builder causes the caret to jump to the start of the link element. Cause: `LinkPlugin` unconditionally reset the selection to the start of non-editable link elements, ignoring whether the anchor node was inside an editable child element. Solution: Do not reset selection if the anchor node is inside a `contenteditable` element. Steps to reproduce: - Open website builder. - Click inside a navbar link to place the caret. => Caret no longer jumps to the start of the link. opw-6535386 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287420 Forward-Port-Of: odoo/odoo#286527
The Timesheet Assistant now avoids using partners that do not have an email address when generating sample data. This prevents a crash and lets users continue creating sample timesheet data reliably.
Original PR description
Forward-Port-Of: odoo/enterprise#131002
The Documents app now correctly prevents the company's project folder from being archived. This helps ensure important project document structures remain available and cannot be accidentally removed from active use.
Original PR description
The company's project folder is supposed to be [impossible](https://github.com/odoo/enterprise/blob/20cc61e69aa3f6a59de1e962b25ce11fa402bf22/documents_project/models/documents_document.py#L44) to archive, by using the `_unlink_except_company_folders` logic. However, the method that adds the company field to the list of fields to check was missing, so it was still possible to archive a folder set as projects folder. Forward-Port-Of: odoo/enterprise#120605
This update corrects internal automated tests for the Mail and Spreadsheet apps after a recent change to the test helper behavior. It helps keep development and quality checks reliable without changing how end users use Odoo.
Original PR description
Since commit f83282086a412b6560b20186230cfd8daf56e4f9, the hoot `__debug__` helper returns a function that returns the hoot runner rather than the runner itself. Some tests weren't adapted. Task: [6560305](https://www.odoo.com/web#id=6560305&cids=1&menu_id=4720&action=333&active_id=2328&model=project.task&view_type=form) 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
This fixes an issue where saving Point of Sale settings in a multi-company setup could add a manager's employee record from the wrong company. The change helps keep PoS employee access accurate and prevents cross-company employee assignments when configuring a PoS.
Original PR description
Steps to reproduce: - multi-company database, PoS Manager user with an employee in each company - current company set to company A - open the settings of a PoS configuration belonging to company B…
Steps to reproduce: - multi-company database, PoS Manager user with an employee in each company - current company set to company A - open the settings of a PoS configuration belonging to company B and save Issue: The advanced_employee_ids of company B's PoS now also contains the manager's employee of company A, an employee from another company. Cause: res.config.settings.create() appends the PoS managers' employees with `_get_group_pos_manager().user_ids.employee_id`. res.users.employee_id is computed for the current company (self.env.company), not for the company of the pos.config being edited, so the employees of the active company are linked instead of the PoS company's. pos.config.write() already resolves them with `with_company(config.company_id)` since fccccf6cabe5, but the settings path was left unscoped. Fix: Resolve employee_id with `with_company(config.company_id)` in the settings create, as done in pos.config.write(). Single-company databases are unaffected. opw-6544518 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287182
On mobile, opening a full-screen image preview now hides the editor toolbar and dismisses the keyboard. This keeps the preview controls accessible so users can view and manage images without interface overlap.
Original PR description
When displaying the full screen image preview lightbox on mobile, the toolbar remains displayed. Because of this, the toolbar of the lightbox cannot be accessed. This commit hides the toolbar when a lightbox is displayed. task-6370220 Forward-Port-Of: odoo/odoo#287268 Forward-Port-Of: odoo/odoo#274949
Sold-out event tickets are now correctly treated as unavailable instead of allowing the default maximum order quantity. This prevents future ordering flows from mistakenly offering tickets or slots that have no remaining capacity.
Original PR description
### Steps to reproduce: event = env['event.event'].create({ 'name': 'Repro Event', 'date_begin': '2026-09-01 08:00:00', 'date_end': '2026-09-01 18:00:00', }) ticket =…
### Steps to reproduce:
event = env['event.event'].create({
'name': 'Repro Event',
'date_begin': '2026-09-01 08:00:00',
'date_end': '2026-09-01 18:00:00',
})
ticket = env['event.event.ticket'].create({
'event_id': event.id,
'name': 'VIP',
'seats_limited': True,
'seats_max': 1,
})
env['event.registration'].create({
'event_id': event.id,
'event_ticket_id': ticket.id,
'name': 'Attendee 1',
'state': 'open',
})
ticket.seats_available -> 0
ticket.is_sold_out -> True
result = ticket._get_current_limit_per_order(event=event) print(result) # {ticket.id: 30} -- expected {ticket.id: 0}
### Issue and Expected
`_get_current_limit_per_order()` used `if not seats_available:` to detect the "no limit" case returned by `_get_seats_availability()`. That check is truthy for both `None` (genuinely no limit) and the integer `0` (fully booked), so a sold-out ticket/slot combination was incorrectly treated as unlimited and returned `limit_max_per_order or EVENT_MAX_TICKETS` (e.g. 30) instead of `0`.
### Fix
`_get_seats_availability()` explicitly documents `None` as the "no limit" sentinel, with `0` meaning "constrained, zero seats left". Use `seats_available is None` to preserve that distinction instead of a falsy check.
No functional regression was found in the current website_event flow: sold-out slots/tickets are filtered or re-derived independently before reaching this value in every existing UI path. This fixes the underlying contract of the method itself, so future or additional callers don't inherit the wrong value.
https://github.com/odoo/odoo/issues/284098
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#284100The appointments calendar now opens on the nearest upcoming booking instead of jumping to the farthest future booking. This helps users quickly see and manage the most relevant appointments without manual navigation.
Original PR description
Problem: The bookings calendar of an appointment type opens on the week of the most distant booking instead of the next one, even when a booking exists later the same day. Cause:…
Problem: The bookings calendar of an appointment type opens on the week of the most distant booking instead of the next one, even when a booking exists later the same day. Cause: `action_calendar_meetings` sets the landing date from `appointments[0].start`, where `appointments` is `self.meeting_ids.filtered_domain(domain)`. `calendar.event` is ordered on `start desc` and a one2many is read in the order of its comodel, so the first record is the furthest booking rather than the next one. The original `search([...], order='start')` was replaced by the one2many in 14b5caccc325 (odoo/enterprise#23191). Solution: Sort the filtered bookings on `start` in `action_calendar_meetings`. That method is the only place the initial date is built, and `action_calendar_event_view_request` reuses it for the gantt start date, so both entry points are covered. `calendar.event` keeps its `start desc` order, which the booking list views rely on. Steps to reproduce: - Go to Appointments. - Open the appointment type "Schedule a Demo" and click Appointments. - Click New, set the date to later today, save and go back. - Click New, set the date to one year from now, save and go back. - Go back to Appointments, reopen "Schedule a Demo" and click Appointments. - Switch to the calendar view. - Observe that the calendar opens on the week of the booking one year from now. Ticket [link](https://www.odoo.com/odoo/project.task/6480029) opw-6480029 Forward-Port-Of: odoo/enterprise#130385 Forward-Port-Of: odoo/enterprise#129000
This fixes a test issue in the mail testing module where large user ID numbers were not formatted consistently before being checked. The change helps prevent false test failures when the full test suite runs with higher database sequence values, improving release reliability without changing user-facing behavior.
Original PR description
When calling message_post() with custom tracking_values, the new_value field is rendered verbatim by the QWeb template into the message body. Automatic tracking (mail_track_mixin) already formats integers via formatLang before setting new_value, but the test was passing a raw integer (self.env.uid), causing a mismatch with the formatted value expected by assertTrackingValueInBody. This only manifests when uid >= 1000.
Step to reproduce:
- Create a db with test_mail installed
- run `psql <db_name> -c "SELECT setval('res_users_id_seq', 1234, true);"` (to forcefully increment the sequence)
- launch the test_track_multi_models test
The test will fail due to this sequence increment when the test suite in ran fully (without splits like on runbot).
Forward-Port-Of: odoo/odoo#286256This change makes an internal HTTP test run consistently instead of failing unpredictably. It helps keep automated quality checks stable so development teams can detect real issues faster.
Original PR description
[runbot-947020](https://runbot.odoo.com/odoo/runbot.build.error/947020) 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
Code cleanup and technical improvements
The accounting test suite was tidied by removing duplicate helper code and old commented-out test sections. This is an internal maintenance change that makes future accounting quality checks easier to manage without changing customer-facing behavior.
Original PR description
This commit cleans up the test suite within the `account_accountant` module by removing redundant methods and old commented code. no-task Forward-Port-Of: odoo/enterprise#130857 Forward-Port-Of: odoo/enterprise#130644
Documentation and clarification updates
This update records that Victor Hachard has signed Odoo's contributor agreement. It supports legal compliance for accepting contributions and has no direct impact on product features or users.
Original PR description
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Signing against 17.0 so the signature is forward-ported to all newer versions, as advised in #139403. Forward-Port-Of: odoo/odoo#287349 Forward-Port-Of: odoo/odoo#287231
ERPly S.R.L. has signed Odoo's Corporate Contributor License Agreement. This clears the legal approval requirement for their contribution, allowing the related work to proceed once other checks are complete.
Original PR description
ERPly S.R.L. (Santo Domingo, Dominican Republic) signs the Corporate Contributor License Agreement v1.0. This unblocks the `legal/cla` check on #286565, where every other CI check already passes. Signed by Rob Cruz (rob.cruz@erply.do, @rob-erply), acting on behalf of ERPly S.R.L. Forward-Port-Of: odoo/odoo#286665