Thursday, August 27, 2026
144 changes · master
Security fixes and vulnerability patches
This update tightens how Odoo builds and runs dynamic database queries, reducing the chance of unsafe query construction and making automated checks simpler. The change affects many areas internally, with the main business benefit being stronger protection and more consistent maintenance across modules.
Original PR description
`odoo.tools.sql.SQL` was added in Odoo 17, and for the most part it's worked quite well. This PR aims to start the process of making it required for SQL queries, alongside simpler CI (the removal of…
`odoo.tools.sql.SQL` was added in Odoo 17, and for the most part it's worked quite well.
This PR aims to start the process of making it required for SQL queries, alongside simpler CI (the removal of the existing complicated pylint checker and the requirement of a semgrep that's both stricter and dumber).
Most of the changes are straightforward if wide ranging, however a few core changes stand out:
- support for strings in `Query.order` and `Query.add_where` has been removed, as they're essentially the same as concatenating unchecked strings into SQL queries (completely unnecessarily)
- because semgrep has limited support for interprocedural analysis and none for typechecking, it gets confused by things like
```python
def foo(self):
return SQL("SELECT 1")
def bar(self):
self.env.cr.execute(self.foo())
```
Because extracting bits and pieces into their own functions is both reasonable and a commmon pattern when building up queries, a fastpath was added for the case of `SQL("%s", var)`[^1], this essentially passes `var` through unchanged *if it's already an `SQL`*, which means it can be used as a safe *and* cheap way to whitelist the value for semgrep that something is fine.
- `cursor.execute_values(str)` is deprecated, it should receive a `Composable`
- `cursor.execute(str)` is not deprecated yet
- there are >150 extant literal-string queries (which have no safety issues but would likely still need to be migrated)
- `SQL` is not currently available in server actions, and the risk of exposing it has not been studied (though at first glance I don't really see more risks with it than with string queries)
## t-strings
Python 3.14 introduces t-strings, which would make *some* migrations simpler. However a lot of the migration work is from sub-queries needing to be migrated, which t-strings would not affect that materially. Debian Forky won't release until 2027 (if not delayed) and it's currently still on Python 3.13 (so is sid). Likewise Ubuntu 26.04 (the next LTS) although it does have 3.14 available as an optional additional (https://packages.ubuntu.com/search?keywords=python3.14).
While I do believe t-strings will make queries more convenient without loss of safety I don't think they make the migration so much easier that there is a good reason to wait *years* before committing.
## Static analysis
The more local behaviour allows for simpler static analysis (especially backed by dynamic checks): use semgrep to check that `SQL` calls only receive a literal first argument, and that `execute` only receives `SQL` objects[^2][^3]. This is slightly hampered by some limitations of semgrep e.g. semgrep sees
foo = "bar"
thing(foo)
as equivalent to
thing("bar")
but such is not the case for bespoke types / expressions. In theory [symbolic propagation](https://semgrep.dev/docs/writing-rules/experiments/symbolic-propagation) does that but it doesn't seem to always work, and furthermore as noted does not work in all cases (e.g. symbols don't currently propagate past branches).
This PR recommends resolving those issues via `SQL("%s", previously_generated_sql)` which it special cases (via an optimisation).
This PR has little to no effect on pylint: while the SQL linter is complex and brittle, experimentally removing it has limited impact on the pylint run time. However it's a big piece of the *motivation* for using pylint, removing it makes for a strong argument to eventually remove the pylint test entirely, replacing its various bits with other linters (ruff or semgrep rules, bespoke handrolled `ast`-based checkers).
odools might eventually handle this even better through models-aware typechecking, but it's currently far from being there.
## Possible Concerns
- how to make server actions work with this system (just expose `SQL`?)
- how to *transition* server actions (catch the warning and notify the admin?)
- `sql.Identifier(sqlype)` sort-of works but becomes case-sensitive (as with other identifiers), and the quoting of attributed types can be temperamental e.g. `CAST(1 as "numeric"(5, 3))` works but `CAST(1 as "char"(3))` fails with "type modifier is not allowed". After more experimentation, `char` seems to be a specifically non-working type, varchar, bit, time do work although the date/time fields in their standard (expanded) form seem extremely temperamental.
[^1]: well it also allows setting `to_flush` as that seems harmless and there's one case which was [committing a mess](https://github.com/odoo/odoo/blob/26440c28f8a242484959afc3d7993b40e4237e04/odoo/orm/models.py#L3245-L3247) for the sole purpose of updating the flushing
[^2]: the second part is not in this PR, but would be a good idea (if requiring a lot more migration time due to needing to take a decision wrt server actions)
[^3]: and eventually t-strings maybeThis update prevents sensitive database API keys from being exposed when creating a database from a template. It keeps the database creation process working while adding tests to confirm the key remains hidden from users and systems that should not see it.
Original PR description
This commit is a follow-up to 8741c123997438beb56fa065667b431616c809a5 which hardens the security of the `database_api_key` field on `project.project`. The issue is that a similar field is still accessible on the wizard allowing the creation of a database from a template, `project.template.create.wizard`. With this commit, the field is masked in the same way. A test checks that the key cannot be read in cleartext from the ORM any more, and that the database creation still initialize the correct key. Forward-Port-Of: odoo/enterprise#128810 Forward-Port-Of: odoo/enterprise#128596
Enhancements to existing features
Category header display settings are now managed at the website level instead of separately on each product category. This helps keep the shop experience consistent across all categories on the same website and reduces configuration confusion.
Original PR description
Before this commit, category header settings were stored on each product category, leading to inconsistent behavior across categories within the same website. This commit stores these settings on the website instead, making them consistent for all categories of a website. Upgrade PR:https://github.com/odoo/upgrade/pull/10604 task-6325882
Resolved issues and error corrections
This fixes an error that could block converting website contact form leads into opportunities when the visitor entered a new company name. Sales teams can now complete the lead conversion flow without the system applying an invalid customer type behind the scenes.
Original PR description
Steps to reproduce: 1. Have `website_crm` installed and CRM Leads enabled. 2. As a public visitor, go to the website's /contactus page. 3. Fill out the form, ensuring you type a new company in the…
Steps to reproduce:
1. Have `website_crm` installed and CRM Leads enabled.
2. As a public visitor, go to the website's /contactus page.
3. Fill out the form, ensuring you type a new company in the "Your Company" field, and submit.
4. As an internal user, go to CRM > Leads and open the newly created lead.
5. Click "Convert to Opportunity".
A ValueError is raised:
Wrong value for res.partner.type: 'lead'
The leads view (and lead actions) sets `default_type' as 'lead'` in the context. When converting a lead that has a `partner_name` (like those generated from the website contact form), `_create_customer` calls create method of partner model which triggers `_create_parent_from_name` to auto-create the parent company.
Since the parent company creation values don't include an explicit `type`, it falls back to `default_type` from the context, receiving 'lead', which is not a valid `res.partner.type` selection value.
Pop `default_type` from the context before creating the partner in `_create_customer`. The partner type is already explicitly set in `_prepare_customer_values` ('contact'), making context propagation unnecessary. If a specific type is needed for the parent company, `parent_additional_values` is the proper mechanism to use.
Task-6428783
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-prCode cleanup and technical improvements
Property fields can now decide individually whether their changes are recorded in the chatter. This gives businesses more control over which property updates are visible in communication history, helping reduce noise while preserving important audit trails.
Original PR description
Allow to track properties. Properties fields are all "tracked" but the property have their own tracking attribute that will define if the change are tracked or not in the chatter. TASK-5131127 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This update tidies up icon styling in several Odoo areas so status bubbles and website buttons appear more consistently. It removes outdated icon rules and fixes a small alignment issue in the website scroll button, improving visual polish without changing functionality.
Original PR description
[IMP] event, maintenance, project: clean up the state bubble scss --- Follow-up of 62bab6a4be6e, which sized the state selection bubbles from the shared bubble variables: a few per-module overrides…
[IMP] event, maintenance, project: clean up the state bubble scss --- Follow-up of 62bab6a4be6e, which sized the state selection bubbles from the shared bubble variables: a few per-module overrides were left behind. __Before commit__ The bubbles kept a negative `margin-top` to compensate their old position, and in project the `hourglass_empty` icon had its own `font-size` and `oi-lg` class, plus per-view `!important` overrides on `font-size`, `margin-top` and `padding-left` for the list view. __After commit__ Icons are sized from the bubble variables (`$o-bubble-color-size-xl`, `$o-bubble-color-size`), so all the per-icon nudges and `!important` rules are gone, along with the `oi-lg` class in the template. <img width="717" height="478" alt="image" src="https://github.com/user-attachments/assets/14ee6f32-53a2-40fe-bb72-c276a3f91330" /> task-6377407 [IMP] web, website: drop leftover font-awesome icon selectors --- Social media, share and scroll down button icons, as well as the webclient icon-only buttons, are now always rendered with `oi` classes. The `fa-stack` / `fa-Nx` / `fa-fw` counterparts in the selectors are dead code, so remove them. task-6377407 [FIX] website: vertically center the scroll down button icon --- __Problem__ Since icons are rendered with `oi` instead of `fa`, the arrow of the scroll down button sits slightly off-center in its round button. __Quick fix__ Force `vertical-align: middle` on the `.oi` pseudo-element. <img width="660" height="143" alt="image" src="https://github.com/user-attachments/assets/354ed891-a6e8-4ef1-b10b-a7e6f15925a3" /> task-6377407
This update makes automatic cursor focus more reliable when fields or buttons are displayed through dialogs or shared page areas. Users should see smoother interactions in places like mail GIF selection and quick-entry forms, with tests adjusted to match the intended behavior.
Original PR description
useAutofocus was built on useLayoutEffect, whose dependencies are recomputed from the render/patch cycle of the component calling it, with an untracked read of the ref so that component doesn't…
useAutofocus was built on useLayoutEffect, whose dependencies are recomputed from the render/patch cycle of the component calling it, with an untracked read of the ref so that component doesn't subscribe to it. When the ref is written by a render that component doesn't own, typically content passed to a <Dialog>'s slot, that cycle never runs and the element is never focused. Rewrite the hook on owl's useOnChange: the dependency is a tracked read of the ref, computed in a signal computation of its own, so the element is focused no matter which render writes the ref, and the component is still never subscribed, as its re-render would reset an input bound with t-att-value (e.g. calendar quick-create title). The callback stays untracked: el.focus() synchronously runs the focus handlers, and the signals they read must not become dependencies of the hook, or any later change to them would steal the focus back. navigation_hook.test.js's BasicHookParent fixture combined useAutofocus on an unrelated button with useNavigation's initial-item activation, relying on onMounted registration order to decide which one ended up with real focus. No real component pairs the two hooks that way, so move that call to "navigation with virtual focus", the only test that exercises the interaction, and where virtual focus never touches real focus.
Folded Kanban columns now display their unfold arrows more neatly and consistently, improving visual alignment with the rest of the column. Related tests were updated to focus on the intended scrolling behavior rather than fragile screen-size-specific values.
Original PR description
[IMP] web: align unfold arrows in folded kanban columns --- __Before commit__ <img width="319" height="266" alt="image"…
[IMP] web: align unfold arrows in folded kanban columns --- __Before commit__ <img width="319" height="266" alt="image" src="https://github.com/user-attachments/assets/8947d533-c1cf-43a4-a6dd-0939bc619102" /> The two arrows of the `o_column_unfold` button were spaced with horizontal paddings on the button and a `margin-right` on `arrow_left`, both swapped on hover to keep the folded column at a constant width. With the new icons, that no longer lines the arrowheads up with the rest of the folded column. __After commit__ <img width="216" height="153" alt="image" src="https://github.com/user-attachments/assets/34fb9c16-223e-4a9f-86ef-0bdad05c9363" /> The button has a fixed width and centers its icons, each clipped to the width of its arrowhead; only the `gap` between them grows on hover. The quick create `Add column` button drops its horizontal padding for the same reason. The unfold-scroll test used to pin the exact `scrollLeft` values that `scrollIntoView` produces on the CI viewport, and those depend on the width of a folded column. It now asserts what the feature actually guarantees: whether a scroll happened, and that the group brought into view ends up flush with the right edge of `.o_content`. Its last case also folds the last group, so that it really exercises the "unfolded group has no next group" branch. task-6377407
The accounting dashboard KPI calculations were cleaned up and covered by an automated test. This should help keep dashboard figures reliable while improving performance for important dashboard views.
Original PR description
* add a test for the function `get_account_dashboard_kpis` * use the ORM to compute the consolidated balances * avoid a `OR` in `_get_open_sale_purchase_query` as it would prevent using efficient indexes, while performance is crucial on the dashboard.
Belgian payroll now shows more complementary benefit information on payslips and improves how insurance contributions are configured and validated. This helps payroll teams reduce manual inputs, catch invalid insurance amounts earlier, and provide employees with clearer salary details.
Original PR description
Refactor existing Belgian salary rules to support the complementary information display section on payslips and enhance employee insurance configuration. - Add employee-level ambulatory insurance contribution and setting defaults. - Compute employer share for meal vouchers via new `MEAL_VOUCHER_EMPLOYER` rule. - Refactor termination rules (Hospital, Ambulatory, Group Insurance) to compute directly from contract version history instead of manual inputs. - Enable `display_in_pdf_extra_info` across relevant insurance rules. - Rename `F_SOCIAL_CONTRIB` and configure for extra info display. - Add payroll warnings to catch invalid employee insurance contributions. Task: 6327364
The accounting dashboard KPI cards have been refreshed to stay visible and easier to use. On mobile devices, the cards now appear in a horizontally scrollable row, improving access without taking up excessive screen space.
Original PR description
This commit improves the UI of the KPI cards in the account dashboard. There's no longer a close button on the cards, they're meant to always be visible on the dashboard. Also, the cards are now in a scrollable row on mobile, instead of being stacked vertically. Task ID: 6498831
The AI fields area has been simplified by removing older custom patches that are no longer needed. This should make future changes easier to maintain while keeping the user experience stable.
Original PR description
The property definition is now more expandable, then not needed patched has been removed.
Belgian payroll can now identify when an employee's salary is paid to a bank account owned or managed by someone else. This helps companies capture the required beneficiary details, especially for non-European accounts, improving compliance in exceptional payment situations.
Original PR description
Under specific circumstances, you can have your salary sent to another account, for example, you're under heavy fines and drawbacks, and your salary is managed by a legal advisor. In that case, you're not the proprietary of the account. Moreover, if you're not the proprietary and the account where money is sent is not european, we need to know the beneficiary city + country. Adding 2 new fields on `res.partner.bank` (displayed in form only for BE companies) : - `is_third_party`: computed (editable) boolean checking if `holder_name` and `partner_id.name` are matching or not. - `third_party_beneficiary_id`: res_partner managing the third party account. Task: 6431562
The quotation template form now separates subscription-specific settings into their own column. This makes the form easier to scan and helps users distinguish subscription configuration from general template details when setting up quotations.
Original PR description
Before this change: The quotation template form view displayed subscription specific fields together with the template's general fields in a single column. This made it hard to visually distinguish which fields belonged to the subscription configuration or the general template settings. After this change: The subscription fields are now separated into their own column. This groups related fields visually and makes the distinction between general template info and subscription specific settings clear. Impact: Improves the user experience by making subscription fields easier to locate and distinguish from other template fields, reducing the chance of confusion when configuring a subscription quotation template. task id: 6410262
Datetime fields can now automatically fill in a preset value when a user clicks them. This supports workflows like Social scheduling, where the planned time can default to one hour from now to speed up data entry.
Original PR description
Purpose ======= I social, we would like to automatically set the scheduled date to "now + 1 hour" when we click in the Datetime field. Task-6323897
The spreadsheet dashboard settings panel is now hidden from users who do not have administrator rights. This helps prevent non-admin users from seeing configuration options they cannot or should not manage, making the dashboard experience clearer and more controlled.
Original PR description
Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: task-6345154 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
The graph view toolbar has been simplified by removing repeated text labels for sorting and chart type controls. Users still get guidance through icons, tooltips, and accessibility labels, keeping the interface cleaner without reducing usability.
Original PR description
Before this commit, the graph view toolbar labelled its sort and chart-type button groups with "Order" and "Type". Both groups already carry an aria-label, and every button a tooltip, so the text only restated what the icons convey. After this commit, the groups rely on icons and tooltips alone. task-6497293 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Businesses can now turn an active asset set to no depreciation into a depreciable asset without manually recreating it. The system transfers the current book value to the selected asset account, closes the original asset, creates and validates the new depreciable asset, and makes navigation between the linked records easier.
Original PR description
Allow users to convert a running asset that uses the "No Depreciation" method into a depreciable asset. A new "Activate Depreciation" action is added to the asset modification wizard: - A transfer journal entry moves the book value from the current fixed asset account to a newly selected one. - The old asset is closed and a new asset is created on the target account, inheriting the book value, salvage value, and the depreciation parameters configured on the new account. - The new asset is validated immediately, computing its depreciation board from the activation date. task-6357855
Project sharing now separates collaborators from message followers, so businesses can grant limited task editing access without automatically changing notification followers. This improves control over external or portal user access while keeping project communication settings cleaner.
Original PR description
#TODO --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
The Indian localization stock demo data now includes a warehouse for the Indian demo company. This makes demos and testing smoother by removing the need for users, developers, or partners to manually create a warehouse when working with Indian e-waybill scenarios.
Original PR description
Following the task-4034713, we used to create warehouse for all company but after this task we only create warehouse for the main company while testing for Indian Demo company considering the ewaybills it's annoying to create a new warehouse for Devs and POs and even for Demos After this commit, we will create demo warehouse for the Indian demo company Task [link](https://www.odoo.com/odoo/project.task/4034713) task-4034713 Forward-Port-Of: odoo/odoo#283797
HR now skips an unnecessary calendar lookup when employee contract or work versions follow each other without a gap. This improves performance for related HR validations, reducing wait times and database load without changing business behavior.
Original PR description
Optimize `has_work_hours_between_versions` by adding a fast path for back-to-back versions. If a new version starts the exact day after the previous one ends, there is no time gap between them (the old version ends at midnight and the new one begins immediately). In this scenario, we can safely return `False` and bypass the expensive calendar lookup entirely. `._get_l10n_be_min_wage_invalid_employees` on next.odoo.com: | | time | SQL queries | |--------|--------|--------| | before | ~7.8s | 6744 | | after | ~2.4s | 491 | <img width="1874" height="995" alt="image" src="https://github.com/user-attachments/assets/2b61179b-10c5-46a6-a3ac-4a0cad14f6d6" /> <img width="1874" height="995" alt="image" src="https://github.com/user-attachments/assets/7a70fb39-ece9-4090-ba2e-f3c4acca2657" /> task-6472490 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282703
The timesheet ActivityWatch assistant now shows the name of the relevant Odoo record when it can identify one from a visited URL, instead of falling back to a broad app name. This makes time suggestions clearer and helps users recognize what they were working on more quickly.
Original PR description
Before this commit, when the ActivityWatch integration encountered unmatched Odoo URLs, it would fallback to displaying the general application name (e.g., "Working on Sales"). With this commit, if the URL path ends with a valid record ID (e.g., /odoo/departments/1) and the corresponding model can be identified, the assistant will attempt to fetch and display the actual record name (e.g., "Working on Research & Development"). task: 6365568 Forward-Port-Of: odoo/enterprise#127981 Forward-Port-Of: odoo/enterprise#124138
Invoice QR code settings and payment method labels now use the correct local payment scheme names, such as Pix, FPS, PayNow, PromptPay, VietQR, MMQR, KHQR, and SEPA where relevant. This makes setup clearer for businesses in each country and avoids showing SEPA-specific wording where it does not apply.
Original PR description
The QR-code setting on invoices was labelled "SEPA QR Codes" for every country, and the Payment QR-code selection offered "SEPA Credit Transfer QR" everywhere, even where SEPA does not exist. The bank account fields driving those codes were named "Proxy Type" and "Proxy Value", which said little to users. Each localization now names the setting and its QR method after the local payment scheme (Pix, FPS, QRIS, PayNow, PromptPay, VietQR, MMQR, KHQR) and offers it only to companies of that country. SEPA naming is kept for the SEPA zone, other countries get a generic "Payment QR Codes" label, and the bank fields become "Account Identifier Type" and "Identifier Value". task-6471076
Point of Sale manual data reloads now clear browser-stored local and session data in addition to the main offline database. This helps prevent outdated or mismatched information from causing inconsistent behavior after a reload.
Original PR description
Manual data reloads reset IndexDB but leave local and session storage intact. The goal is to clear them to avoid inconsistent data. task-6456447 Forward-Port-Of: odoo/odoo#284238 Forward-Port-Of: odoo/odoo#281456
Point of Sale now lets cashiers move on immediately after validating a paid order instead of waiting for receipt printing to finish. This should make checkout feel faster and reduce delays at the register, while automated checks were updated to match the new flow.
Original PR description
- Stop awaiting the receipt print in the POS after order payment validation - Adapt tours to this behavior change task-id: 6425204 enterprise PR: https://github.com/odoo/enterprise/pull/127818 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283823 Forward-Port-Of: odoo/odoo#280002
Bank journals now receive the right outstanding payment accounts automatically for Moroccan and Indian accounting setups. This helps ensure payments are reflected correctly in cash-basis tax reporting, even when bank synchronization or reconciliation is not available.
Original PR description
Reason: - Moroccan companies usually use cash basis accounting. And the cash basis entries are only made when the invoices are reconciled with the bank transactions. However, in Morocco, there is no…
Reason: - Moroccan companies usually use cash basis accounting. And the cash basis entries are only made when the invoices are reconciled with the bank transactions. However, in Morocco, there is no Moroccan bank available for bank synchronization. Before this commit: - We are not assigning the outstanding accounts on the bank journals by default. - Which is causing the issues when the user creates a payment without an entry and without having any bank transactions to reconcile it with. Therefore, the tax report won't show the moves and taxes that occurred in the period. After this commit: - Introduced a method for updating the accounts on the payment method lines of the bank journal in the account module, as we need the same functionalities in l10n_in as well. - For Moroccan localization, from now on, we are setting the outstanding accounts automatically on the bank journals. - The payment accounts are applied by default during CoA loading and whenever payment method lines are recomputed, ensuring accounts remain consistent. Task-6041119 Forward-Port-Of: odoo/odoo#254642
French localization return reports now display the expected status colors for DAS2 and fiscal declarations. This makes it easier for users to quickly understand the state of each return without changing report content or workflow.
Original PR description
During the development of the das2 report and fiscal declaration, we didn't change the _compute_visible_states to accept the return of those reports. By doing so, we now have colors on the returns. task-6297355 Forward-Port-Of: odoo/enterprise#120417
Appointment booking views now group entries by guest automatically, making it easier for staff to review bookings by customer. This helps teams quickly see each guest's reservations without manually changing the view.
Original PR description
Add default Group By Guest Task-id: 6253719 Forward-Port-Of: odoo/enterprise#118681
The replenishment workflow now gives buyers clearer guidance by showing expected daily demand, how long minimum stock will last, and how often replenishment is likely needed. Teams can also update key replenishment assumptions across multiple products at once, reducing manual work and making inventory planning more consistent.
Original PR description
We're refactoring the replenishment wizard: - removing the graph - adding the `sale_delay` field - storing the daily demand, percentage and period time for the demand on the orderpoint - adding 2 fields to show the minimum coverage (days covered by the minimum stock based on daily demand) and the replenishment frequency ((max - min) / daily demand) - adding a wizard to change the `based_on` and `percent_factor` on multiple orderpoints at once, which also recalculates daily demand, min & max - adding demo data task 6304907 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Module descriptions, summaries, and short labels are now exported and loaded with each individual module instead of being bundled into the core translation file. This makes translations work better for custom modules and avoids extra translation export work when modules are added or updated.
Original PR description
Currently a module's `description`, `shortdesc`, and `summary` that are defined in the manifest file are exported in the `base.pot` file. This means that in order to get them translated (in case of an update or a new module), the `base.pot` file needed to be re-exported again with all the possible modules in the addons paths. For custom modules, this also means that their manifest terms are not translated at all, because their terms are not in the `base.pot` file. This commit changes this by exporting the manifest terms in their own modules' POT file, and loading them from there as well. This way, the manifest terms of custom modules can be translated, and there is no need to re-export the `base.pot` file when a new module is added or updated. We also add a test to check that the custom reader implementation is faster than using `polib`.
The channel kanban view has been adjusted to better match the redesigned web interface. Users should see cleaner spacing, better-aligned cards, and more polished channel images when browsing channels.
Original PR description
This PR fixes the layout of the ungrouped kanban view for channels as it was relying on the margin around each `KanbanRecord` for spacing. As we rely on the gap for the spacing, apply the same approach + add some minor fine-tuning like a `border-radius` on the channel image, spacing between category title and cards task-6488068 follow-up of task-6330603 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This update strengthens automated checks so outdated frontend code patterns are caught before they can cause hard-to-find user interface issues. It helps prevent silent failures in areas such as messaging and point of sale by flagging incompatible code during quality checks.
Original PR description
Owl 3's useEffect takes a single callback and calls it with no arguments, but Owl 2 code still parses and runs. `useEffect(fn, () => [deps])` silently drops the dependencies, and `useEffect((el) => ...)` gets undefined parameters - nothing fails until a user walks the path. Add two no-restricted-syntax selectors so eslint catches both on the lint build. They are wrong by construction, so no exceptions are needed, and the useLayoutEffect shim keeps its two arguments under a different callee name. The rules go in test_lint/tests/eslintrc and, for point_of_sale, iot and obox, in web/tooling/_eslintrc.json. The chatter composer patch is the last community call site of that shape. Its body already reads both recipient lists, so dropping the dependency argument is enough; it matches master, so the forward-port is a no-op. Enterprise: https://github.com/odoo/enterprise/pull/128751 Forward-Port-Of: odoo/odoo#283883
Payroll users can now tap a full employee row to select it when reviewing payroll runs on mobile. Tapping the employee avatar opens the employee record, making navigation clearer and reducing selection mistakes on smaller screens.
Original PR description
this commit improves the employee selection in mobile view of hr_version_payrun_list making the click event on the record row to select the record and when the avater is clicked the user will be redirected to the employee form view. task-6469653
The HTML editor now prevents table merge or unmerge actions from affecting cells in a different table. This avoids accidental changes when users work with multiple tables in the same document.
Original PR description
Steps to reproduce: - Insert two tables in the editor. - Merge cells in the first table. - Select the merged cell in the first table. - Open the table menu for the second table. - Observe that the…
Steps to reproduce: - Insert two tables in the editor. - Merge cells in the first table. - Select the merged cell in the first table. - Open the table menu for the second table. - Observe that the "Unmerge Cells" option is available even though the second table has no merged cells. - Click "Unmerge Cells". - The merged cell in the first table is unexpectedly unmerged. Description of the issue: - The "Unmerge Cells" option is shown for the second table when a merged cell from the first table is selected. - Clicking the option unmerges the selected cell from the first table. Cause: - In `getSelectedCellsMergeInfo`, `canUnmerge` was determined using `td.rowSpan > 1 || td.colSpan > 1` without checking whether the cell belonged to the target table. Solution: - Verify that the selected cells (`td`, `firstCell`, and `lastCell`) belong to the `targetTable` before allowing merge or unmerge operations. - Prevent merge and unmerge operations from being applied to cells in a different table. task-6475293 Forward-Port-Of: odoo/odoo#283222
The pickup location search no longer pre-fills an imprecise ZIP code based on GeoIP, helping customers avoid seeing irrelevant nearby pickup points. The search prompt is clearer and the country selector is simplified when there is only one country option.
Original PR description
GeoIP guesses a visitor's location is not precise resulting in showing pickup points that are not close to the customer. Drop the GeoIP zip prefill.
Also clarify the search placeholder ("Zip or City") and hide the country dropdown's caret when there's only one option to pick. Safely fallback on the first country in the selector.
Forward-Port-Of: odoo/odoo#284465
Forward-Port-Of: odoo/odoo#284392UAE companies can now create and save salary bank accounts directly from Payroll Settings. This helps payroll teams complete required UAE WPS configuration without needing a workaround.
Original PR description
Issue: UAE companies cannot create their salaries bank account directly from Payroll Settings. The bank account cannot be saved, leaving payroll configuration incomplete and preventing the UAE WPS…
Issue: UAE companies cannot create their salaries bank account directly from Payroll Settings. The bank account cannot be saved, leaving payroll configuration incomplete and preventing the UAE WPS process from being completed. Steps to reproduce: * Configure an Emirati company with the UAE Payroll localization. * Open Payroll > Configuration > Settings. * Create a new Salaries Bank Account from the settings field. * Fill in the bank details and try to save the account. Cause: Since saas-19.2, the bank account form hides the required account holder and expects the opening field to provide it through `default_partner_id`. The UAE salaries bank account setting only restricts selectable accounts through its domain and does not provide that creation default. Newly created accounts therefore have no owner and cannot be saved. Domains only filter selectable records and do not initialize fields on new records. Since the shared bank account form hides the required partner, accounts created from Payroll Settings have no owner and cannot be saved. Solution: We need to provide the current company partner as the account creation default while retaining the existing selection domain. This preserves the company and country restrictions and guarantees that newly created salaries accounts satisfy the required ownership invariant. opw-6441848 Forward-Port-Of: odoo/enterprise#127777
Invoice document recognition now compares bank account numbers in the same cleaned format used by OCR. This helps the system correctly match supplier IBANs even when saved bank details include spaces, dots, or dashes, reducing manual corrections.
Original PR description
When looking for a matching IBAN, we were searching on the `acc_number` field, which can contain spaces or special characters (dots, dashes, etc). But the OCR always returns the IBAN in a sanitized format, without any space or special characters, so it should be compared against the sanitized IBAN of the partners. task-none (issue found by chance) Forward-Port-Of: odoo/enterprise#128264 Forward-Port-Of: odoo/enterprise#127775
Deleting one project will no longer incorrectly move folders from archived projects to the trash. This protects documents linked to archived projects from accidental disruption while still cleaning up folders that are truly unused.
Original PR description
Deleting a project also sends the folders of every archived project to the trash. ### Steps to reproduce - Install `documents_project`, where each project has its own Documents folder linked through…
Deleting a project also sends the folders of every archived project to the trash.
### Steps to reproduce
- Install `documents_project`, where each project has its own Documents folder linked through `project.project.documents_folder_id`.
- Create `Project 1`, `Project 2`, and `Project 3`, then archive the first two.
- Delete `Project 3`.
- The folders of `Project 1` and `Project 2` are moved to the trash with their contents, although both projects still exist and still reference them.
### Cause
`_archive_folder_on_projects_unlinked` only archives folders that are no longer used by any project. This was checked through a `documents.document` domain on `project_ids`.
The domain mixed two conditions on the same relation:
- `('project_ids', '!=', False)` checks that a folder has users,
- `('project_ids', 'not any', [('id', 'not in', self.ids)])` checks that it has no users outside the projects being deleted.
Those conditions are not evaluated the same way by the ORM. The first one keeps archived projects visible by disabling `active_test` internally, while the second one searches `project.project` normally and hides archived projects.
An archived project can therefore be counted as a folder user by one condition and ignored by the other, causing its folder to be archived.
### Fix
Check remaining users directly on `project.project` with `active_test=False`, so archived projects are included. Since only folders of deleted projects can become unused, the search starts from those folders instead of scanning all Documents.
opw-6442976
Forward-Port-Of: odoo/enterprise#127217This fix prevents subscription invoicing from crashing when an automatic payment fails due to an invalid or faulty payment token. It helps recurring billing jobs continue handling failures cleanly instead of stopping with an error.
Original PR description
Step to reproduce: - create a faulty token that won't work and link it to a subscription - launch the recurring invoice cron - the following traceback occurs ``` last_tx_sudo = (self.transaction_ids…
Step to reproduce:
- create a faulty token that won't work and link it to a subscription
- launch the recurring invoice cron
- the following traceback occurs
```
last_tx_sudo = (self.transaction_ids - existing_transactions).sudo()
```
When the payment fails, the system rollback and we store the last_tx_sudo value in a dedicated variable. After rollback, the record does not exists anymore. Therefore, accessing the value fails.
```
File "/home/odoo/src/enterprise/saas-18.2/sale_subscription/models/sale_order.py", line 1703, in _handle_automatic_invoices
if not last_tx_sudo or last_tx_sudo.renewal_state in ['pending', 'authorized']:
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 1439, in __get__
self.compute_value(record)
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 1603, in compute_value
records._compute_field_value(self)
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/models.py", line 4575, in _compute_field_value
determine(field.compute, self)
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 69, in determine
return needle(*args)
^^^^^^^^^^^^^
File "/home/odoo/src/enterprise/saas-18.2/sale_subscription/models/payment_transaction.py", line 25, in _compute_renewal_state
if tx.state in ['draft', 'pending']:
^^^^^^^^
File "/home/odoo/src/odoo/saas-18.2/odoo/orm/fields.py", line 1406, in __get__
raise MissingError("\n".join([
odoo.exceptions.MissingError: Record does not exist or has been deleted.
```
Moreover, since https://github.com/odoo/enterprise/pull/45236/files#diff-c36fd7952cc2bef40716419a668de41963d49e1aa4177d9319d503fc260da588R1678-R1682
```
if not last_tx_sudo or not last_tx_sudo.renewal_state not in ['pending', 'authorized']:
```
has become
```
if not last_tx_sudo or last_tx_sudo.renewal_state in ['pending', 'authorized']:
```
But it feels strange to unlink the invoice when the payment succeed.
This PR fixes it.
Forward-Port-Of: odoo/enterprise#129245
Forward-Port-Of: odoo/enterprise#83913This fixes how Odoo identifies extra email or message attachments that are not embedded directly in the message body. It helps ensure attachment lists are consistent and accurate for users viewing messages.
Original PR description
Before this commit, `extra_body_attachment_ids` is declared with `fields.Attr("ir.attachment", { compute() })`, while its compute returns the records of `attachment_ids` that the body does not inline. The model name is therefore the default of an attr field, and only a read inside an update cycle answers that string, as the compute runs on the first read outside one. No reader of the field does that today.
This commit declares the field as the `fields.Many("ir.attachment")` its compute returns, so that the declaration matches the value before the first compute as well as after.
Note that the added test asserts that a message inlining one of its two images lists only the other one, which nothing covered so far. It passes without this change.
Forward-Port-Of: odoo/odoo#284628
Forward-Port-Of: odoo/odoo#284445Automated onboarding tours now handle drag-and-drop steps more reliably, preventing tours from getting stuck during guided setup flows. This improves the reliability of Project and Helpdesk onboarding checks without changing everyday user workflows.
Original PR description
Robot mode (onboarding tours replayed with real actions instead of a human) got stuck on drag&drop steps: - tour_step_interactive.js's findTrigger() returned undefined for a "drag" event when no draggable ancestor was found, instead of falling back to the element itself. - tour_interactive.js's drop conditional only matched the exact pointerup/drop coordinates; clamp the point into the drop target's rect first, since it can land just outside due to rounding. - Reset tour.anchorEl when the pointer target disappears so a stale element isn't reused. Also mark project_tour's synchronization-only steps (waiting for a dirty form, a dropdown, ...) as isActive: ["auto"], since robot mode performs the real action and doesn't need them, and add project_tour and helpdesk_tour to the onboarding tours test coverage.
This fixes several issues when editing an Add to Cart button in the website builder, including action changes not applying, crashes after deleting the icon, and broken button content after copy/paste or text edits. This helps website editors reliably customize shopping buttons without creating broken storefront elements.
Original PR description
The commit c5a40a608280017ae9ea8f9e9e1c59f778d629ae updated to icons to use `data-icon` attribute instead of `fa-*` classes. This commit adapts the `addToCartAction` to correctly update the icon (by…
The commit c5a40a608280017ae9ea8f9e9e1c59f778d629ae updated to icons to use `data-icon` attribute instead of `fa-*` classes. This commit adapts the `addToCartAction` to correctly update the icon (by changing the attribute instead of the class). And fixes a few bugs related to that action as well. Steps to reproduce: - Open website builder - Drop a "Add to cart button" - Select a "Product" with no variants (for example "Chair protection") - Change the "Action" - Bug: the action is not changed (but a class with no effects is added) - - Select text in it - Copy - Paste - Bug: there is a `<button>` in a `<button>` - - Move caret just before the icon - Type text - Bug: the text goes outside the button - - Select a "Product" with no variants (for example "Chair protection") - Delete the icon - Change the "Action" - Bug: crash - - Select a "Product" with no variants (for example "Chair protection") - Select a few letter - Set their style to bold - Change the "Action" - Bug: only part of the text is changed task-6466422
Deleting an employee leave now triggers the related payslip information to be recalculated. This helps payroll stay accurate when time-off records are removed, reducing the risk of incorrect employee payments.
Original PR description
task-6510625 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
The app switcher has been adjusted to run more smoothly on mobile devices, particularly Android devices using Firefox. This improves day-to-day usability by reducing lag when moving between apps.
Original PR description
Prior to this commit, the app switcher was laggy and difficult to use on some mobile devices, especially on Android devices running Firefox. This commit removes and adjusts the CSS properties responsible for the performance issues.
When a leave entry is deleted, related payslips are now recalculated so payroll stays accurate. This helps prevent incorrect salary calculations caused by outdated leave information.
Original PR description
task-6510625
Corrects a setup error in the Turkish Nilvera e-invoicing module so the zero VAT warning works as intended. This prevents invoice processing errors when the system checks whether a sales invoice should display the zero VAT warning.
Original PR description
The `l10n_tr_zero_vat_warning` field is a boolean field but was incorrectly defined as [binary], causing the compute method to fail when assigning a boolean value. ```py File…
The `l10n_tr_zero_vat_warning` field is a boolean field but was incorrectly defined as [binary], causing the compute method to fail when assigning a boolean value.
```py
File "/home/odoo/src/odoo/saas-19.3/addons/l10n_tr_nilvera_einvoice/models/account_move.py", line 156, in _compute_l10n_tr_l10n_tr_zero_vat_warning
invoice.l10n_tr_zero_vat_warning = exempt_zero_tax and invoice.l10n_tr_gib_invoice_type == 'SATIS' and exempt_zero_tax in invoice.line_ids.tax_ids
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-19.3/odoo/orm/fields.py", line 1892, in __set__
self.write(protected_records, value)
File "/home/odoo/src/odoo/saas-19.3/odoo/orm/fields_binary.py", line 151, in write
super().write(records, value)
File "/home/odoo/src/odoo/saas-19.3/odoo/orm/fields.py", line 1583, in write
cache_value = self.convert_to_cache(value, records)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/saas-19.3/odoo/orm/fields_binary.py", line 83, in convert_to_cache
raise TypeError(f'{self}: use BinaryValue instead of {value.__class__.__name__}')
TypeError: account.move.l10n_tr_zero_vat_warning: use BinaryValue instead of bool
```
upg-4608394
[binary]: https://github.com/odoo/odoo/pull/242043/changes#diff-d3cbb345d0a5855b7d7aa91e64a0ff480e3e5acfa3b2c71503a23ca7f3c0c511R132
Forward-Port-Of: odoo/odoo#284165Changing the project on timesheets in bulk or through automated updates now clears any task that does not belong to the new project. This prevents inaccurate timesheet links and helps keep reporting and project tracking consistent.
Original PR description
When modifying project_id on a timesheet through mass edit/rpc or anything that is not triggering `onChange`. The task_id would not be reset if it doesnt' belong to the new project set on the timesheet. Steps to reproduce: ------------------- * Install studio for easier reproducing of the issue * Open the timesheet list view * Open studio and activate the mass edit on the view * Modify the project_id on multiple records > Observation: The task_id stays the same even if they do not belong to the new set project Why the fix: ------------ Instead of relying only on the onChange we add an inverse to the project_id that will reset the task when needed. opw-6259149 Forward-Port-Of: odoo/odoo#283882
This fix makes an automated website test wait until the relevant page record is fully selected before deleting it. It reduces random test failures, helping keep website-related quality checks stable without changing user-facing behavior.
Original PR description
Fix the random tour failure by making sure the record is selected before trying to delete it. runbot-944542 Forward-Port-Of: odoo/odoo#280644
This fixes website page caching so pages are refreshed after a visitor changes cookie preferences, such as moving from denying to accepting cookies. It helps ensure visitors see the correct page behavior and consent-dependent content instead of an outdated cached version.
Original PR description
Initially with [commit 958b41c4], when cookies were denied (the page is cached a 1st time), then accepted (the page cache must be invalidated), cached pages would be computed again. This behavior was lost with [6c8a90ec], since which website pages are cached more aggressively. [commit 958b41c4]: https://github.com/odoo/odoo/commit/958b41c4acec7e1700ca4d6e0b25ee0ad2aac9f1 [6c8a90ec]: https://www.github.com/odoo/odoo/commit/6c8a90ecba45fb99addf1b86fe237fd626fba650 task-6471290 Forward-Port-Of: odoo/odoo#284477 Forward-Port-Of: odoo/odoo#282737
Changing the project on timesheet entries now automatically clears any task that does not belong to the new project, even when updates are made in bulk or through automated processes. This prevents timesheets from being linked to inconsistent project and task combinations, improving data accuracy for reporting and billing.
Original PR description
When modifying project_id on a timesheet through mass edit/rpc or anything that is not triggering `onChange`. The task_id would not be reset if it doesnt' belong to the new project set on the timesheet. Steps to reproduce: ------------------- * Install studio for easier reproducing of the issue * Open the timesheet list view * Open studio and activate the mass edit on the view * Modify the project_id on multiple records > Observation: The task_id stays the same even if they do not belong to the new set project Why the fix: ------------ Instead of relying only on the onChange we add an inverse to the project_id that will reset the task when needed. opw-6259149 Forward-Port-Of: odoo/enterprise#128799
Users can now update rental start or end dates on sales orders even if they do not have direct access to planning slots. The related planning entries are still updated in the background, reducing errors and keeping rental schedules aligned.
Original PR description
This commit prevents a potential access error, if a user changes the rental start date and/or end date of a sale order without the access rights to the 'planning.slot' model. In this case, we want the write to be executed and changes repercuted to the associated slots. Forward-Port-Of: odoo/enterprise#128778 Forward-Port-Of: odoo/enterprise#128365
The IoT display browser now starts only after the system has finished initializing. This prevents startup error pages and helps the browser open correctly in fullscreen, improving reliability for IoT device displays.
Original PR description
Before this commit, the display driver (and therefore browser) were being started too early, causing the following issues: - The browser initially displays an error page, as it tries to load the status page before Odoo has finished starting. - The browser window is not fullscreen. This may be because it is started before labwc is fully initialised, as the correct fullscreen command line arguments are used. The second issue can be fixed by restarting the Odoo service, the first happens every time. After this commit, we start the IoT interfaces in the main run method, instead of when the module is imported, meaning that everything else has time to finish initialising. This solves both problems. task-6469793 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284386
Very short timesheet assistant events that are ignored will no longer change the user's active project or task. This prevents small, accidental activity records from influencing future timesheet suggestions and keeps time tracking more accurate.
Original PR description
Before this Commit, small events (<60s) would be ignored but could still set or change the project and task of future events. After this Commit, if an event is small enough to be ignored by the assistant, it is also unable to change the current project or task of the user. For this commit to work correctly, it is expected that each event objects from the assistant has a duration value. task-[6486183](https://www.odoo.com/odoo/project/4105/tasks/6486183) Forward-Port-Of: odoo/enterprise#128700
Deleting multiple email templates at once now works correctly in Field Service Planning. This prevents an error that blocked users from cleaning up templates in bulk, while still protecting the customer ratings template from accidental deletion.
Original PR description
Steps to reproduce: - 1. Install `planning_field_service`. 2. Open Settings > Technical > Email Templates. 3. Select two templates and delete them. Issue: - The deletion crashes with `ValueError: Expected singleton: mail.template(290, 212)`, and several templates can no longer be deleted at once. Cause: - `_unlink_customer_ratings_mail_template` guards the template configured for intervention customer ratings, but it reads `self.id`. An `@api.ondelete` hook is called once with the whole recordset being unlinked, so it raises as soon as more than one template is deleted. Fix: - Look up the configured template id in `self.ids` instead. task-6488394 Forward-Port-Of: odoo/enterprise#128905
This fixes an intermittent failure in an automated test for German POS certification by ensuring the test waits for order synchronization before checking the table badge. It helps keep the validation pipeline stable and reduces false failures that can delay releases.
Original PR description
Sometimes, `test_fiskaly_basic_order` test fails with the following error: ``` AssertionError: FAILED: [55/68] Tour FiskalyTour -> Step body:has(.pos-leftheader .badge:contains(5)). Element (body:has(.pos-leftheader .badge:contains(5))) has not been found. ``` `FloorScreen.clickTable()` clicks on the table and waits for a badge to appear. The badge is rendered once the table order is synced to the server. If the order is still syncing when the click on the table lands, the badge will not be present and triggers the failure. runbot-940256 Forward-Port-Of: odoo/enterprise#128998
The message interface styling was simplified by removing an expensive visual rule that offered little visible benefit. This should help keep the mail experience responsive while preserving the overall look for users.
Original PR description
This PR cleans up a complex selector that is quite costly without providing any striking visual value. task-6481656 Forward-Port-Of: odoo/enterprise#128906
Point of Sale payments using eWallets or gift cards now apply the full redeemed balance even when the related discount tax is forced to be tax-excluded. This prevents one-cent mismatches where the card balance is fully consumed but the customer order receives a slightly smaller discount.
Original PR description
When paying with an eWallet or gift card in POS, the reward line could end up 0.01 short of the actual card balance if the discount product's tax is configured as tax-excluded through a per-tax…
When paying with an eWallet or gift card in POS, the reward line could end up 0.01 short of the actual card balance if the discount product's tax is configured as tax-excluded through a per-tax override, regardless of the tax's own default configuration. The card is still debited for the full balance, but the order is only discounted by one cent less, so the amount charged to the customer no longer matches the amount consumed from the card. Steps to reproduce: ------------------- * Top up an eWallet (or gift card) with a balance of 10.00 * On the eWallet/gift card program's discount product, set an 18% tax whose Tax Computation is overridden to "Excluded" (price_include_override = tax_excluded), independently of the company's default tax configuration * In POS, add a product to an order and pay (partly) with that eWallet/gift card > Observation: Only 9.99 is deducted from the order total, while the backend correctly shows 10 consumed on the wallet/gift card. Why the fix: ------------ The reward line's price_unit was reconstructed from a one-time backward tax computation, then kept only the tax amount for taxes whose price_include field was true, dropping it for any tax forced excluded. That price_unit was later re-taxed forward using the tax's real (excluded) configuration, and the two roundings don't agree for rates like 18%, losing a cent. We now force special_mode "total_included" whenever an eWallet/gift card reward line's taxes are computed, not just at creation, so its tax-included total always equals the exact redeemed amount regardless of how the tax is configured, and store price_unit as that target amount directly. opw-5819389 Forward-Port-Of: odoo/odoo#284397 Forward-Port-Of: odoo/odoo#278568
The website builder now shows custom gradient buttons without a border when the border is set to zero. This prevents editors from seeing a misleading preview and helps ensure the editing experience matches the final website result.
Original PR description
Steps to reproduce: 1. Create a button 2. Choose "custom" in type 3. Put a gradient for Fill option 4. Remove border (put 0) Problem: Since [this commit][1] support has been added to preview custom…
Steps to reproduce: 1. Create a button 2. Choose "custom" in type 3. Put a gradient for Fill option 4. Remove border (put 0) Problem: Since [this commit][1] support has been added to preview custom buttons with gradient backgrounds in the website builder. However, if a user sets the border to 0px it a false border is shown. This is inconsistent the changes that will be applied to the button on the website. The cause is how the preview border is set. In the same [commit][1], borders are previewed at 2px regardless of their actual size. This works for solid background buttons but causes a gradient pseudo-border to appear with custom gradient buttons. Solution: The solution is to set the preview button's border-width styling to 0px in the case when the border is being changed and its width is set to 0. This styling does not affect the classes applied to the actual button being edited and is removed if the border thickness is changed again. [1]: https://github.com/odoo/odoo/commit/291a77c50f19622f8083a5e3798c17b49f3b1c7e task-6296905 Forward-Port-Of: odoo/odoo#279165
This fix prevents multiple Point of Sale devices from accidentally reusing the same empty draft order. It avoids duplicated order identifiers and helps keep restaurant table assignments intact when staff work across shared terminals.
Original PR description
When using multiple devices sharing draft orders, a race condition can happen where one device reuses another device's empty synced draft order. This leads to duplicate UUIDs, which triggers automatic order merging in `sync_from_ui` on the server and clears the table association. To prevent this: - Filter out synced orders (`!order.isSynced`) in `getEmptyOrder()`, `createOrderIfNeeded()`, and `setTable()` when looking for reusable empty orders. - This ensures each terminal only reuses its own locally created, unsynced empty orders, guaranteeing unique UUIDs per device session. task-id: 6296661 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269551
The overtime rules screen now shows the related employee versions button only to HR managers. This prevents HR officers from seeing an access-rights error when opening overtime rule records, improving reliability during normal use and upgrades.
Original PR description
The button requires the group `hr.group_hr_user`, but the button uses `versions_count`, that in its computation uses fields like `contract_date_start` that require the group `hr.group_hr_manager`. To avoid the mismatch, the button is restricted to only managers. This error was found in upgrades failing. To reproduce: - Install `hr_attendance`. - Assign any employee the Default Ruleset to make the button not invisible. - Change the HR security of your user to Officer. - Go to Attendance->Configuration->Overtime Rulesets and try to see the record. - A message will display the following error: ``` You do not have enough rights to access the field "contract_date_start" on Employee Record (hr.version). Please contact your system administrator. Operation: read User: 2 Groups: allowed for groups 'Employees / Administrator' ``` Forward-Port-Of: odoo/odoo#284194
Email notifications for tracked record changes now correctly display the arrows and parentheses that show what changed. This makes update emails clearer for users while preserving how older messages are displayed.
Original PR description
Bug === When notifying by email a tracking change, the arrow and parentheses are not rendered in the email body. Technical and Constraints ========================= The class `o_track` is only set in…
Bug === When notifying by email a tracking change, the arrow and parentheses are not rendered in the email body. Technical and Constraints ========================= The class `o_track` is only set in the web client template (`mail.Message`). There's no class in the body of the email that is sent. It can be rendered with "notification templates" that we cannot change either (and they just do `t-out="message.body"`, so the body field of the mail message has to be properly rendered). We also need existing mail messages to be rendered correctly, and so we need a way to differentiate mail messages created before the fix from those created after it, to know when to disable the arrow and parentheses. Alternatives ============ We have tough about many solutions, this one is the best we found based on the constraints we have 1. Add a class in 19.3, use that class to not remove the arrow on previous mail message. That solution required a migration script that will change all tracking messages. Because the initial migration of the tracking was really slow, we wanted to avoid that. 2. Add a class, and keep it forever. But that solution makes the body of the mail messages bigger, which defeat one of the purpose of the initial refactoring 3. Change the outgoing email without changing the body of the mail message. That solution was really not reliable (regex change to add the arrow, and we have no clean way to target the tracking rows) 4. During the migration create a system parameter with the date, and compare with the create_date of the mail message to know if we should add the arrows or not (but we will need to keep that system parameter forever, and the code to support both to) Task-6424104 Forward-Port-Of: odoo/odoo#282210
This fixes an internal test issue in the website shop area by making sure inactive products are excluded during test runs. It helps keep automated quality checks stable without changing what customers see in the online store.
Original PR description
Description of the issue/feature this PR addresses: Addresses an issue causing test failures by ensuring that [inactive products](https://github.com/odoo-dev/odoo/blob/dbc917ddc263a330ff70f5edec716ccafe88d7a6/addons/website_sale/tests/test_product_filters.py#L93-L99) are filtered out rather than leaking from the environment into the test execution. I have verified that this issue does not allow [inactive records to leak to customers](https://www.odoo.com/mail/message/1151343506). runbot-242426 Forward-Port-Of: odoo/odoo#284326 Forward-Port-Of: odoo/odoo#283973
This fix corrects how Belgian payroll reports split severance periods for DMFA declarations, so severance pay is allocated across the right quarters. It also preserves valid manually entered departure dates, reducing reporting errors and avoiding unwanted overwrites of HR adjustments.
Original PR description
- previously, the termination period was split from notice period start to actual departure date, ignoring the theoretical notice duration. Now, it correctly splits from actual departure date to theoretical end date, ensuring proper multi-quarter severance (Code 003) allocation. - Preserve departure_date if after dismissal_date, else default to theoretical notice end. previously the compute always overwrote any user input, ignoring manual adjustments task: 5407737 Forward-Port-Of: odoo/enterprise#112279
Fixes a printing issue where customized sale order PDFs could show an unwanted blank column after a column such as Taxes or Discount was removed in Studio. This keeps section and combo rows aligned correctly, improving the appearance of customer-facing sales documents.
Original PR description
**Steps to reproduce:** 1. Open a Sale Order report in Studio 2. Delete the Taxes column 3. Save 4. Create a sale order with at least one section line and products that have taxes 5. Print the sale…
**Steps to reproduce:** 1. Open a Sale Order report in Studio 2. Delete the Taxes column 3. Save 4. Create a sale order with at least one section line and products that have taxes 5. Print the sale order PDF **Issue:** - A blank column is rendered in the PDF report on section (and combo) rows whenever a column such as Taxes or Discount is removed via Studio. **Why this happens:** - The section row's `colspan` and the combo row's `colspan` were computed using `3 + (1 if display_discount else 0) + (1 if display_taxes else 0)`. - `display_taxes` and `display_discount` are derived from order data (i.e. whether any line has taxes/discounts), not from which columns are actually rendered in the table. - When Studio removes a column it deletes the `<th>` and matching `<td>` elements via XPath, but these Python variables remain `True`. As a result, section/combo rows still accounted for the removed column in their `colspan`, producing one extra cell and a visible blank column. **Fix:** - Introduce a `colspan_count` variable which is incremented inside each `<th>` body - Use that counter for `td_section_name` and `td_combo_name` instead of the previous formula. - Because the increment occurs inside the `<th>` element, it is skipped whenever the element is not rendered, whether because `display_taxes`/`display_discount` is `False` or because Studio's XPath removed the element entirely. opw-6433679 Forward-Port-Of: odoo/odoo#283657 Forward-Port-Of: odoo/odoo#280719
Odoo now recognizes Stripe refunds that were already created after a manually captured payment, even when Stripe sends a refund notification later. This prevents duplicate refund records with the same Stripe reference, helping keep payment and accounting records accurate.
Original PR description
Steps to reproduce: - Configure Stripe with manual capture. - Authorize and capture an online payment. - Refund the captured payment from Odoo. - Let the `charge.refunded` webhook be processed. The refund initiated from Odoo is created as a child of the capture transaction, while the webhook resolves the charge to the source transaction. The webhook only checked direct refund children of that source transaction, so it missed the existing refund and created a second refund transaction with the same Stripe refund reference. Look up existing Stripe refund transactions in the child and grandchild transactions of the source transaction before creating webhook refund transactions, so the webhook recognizes refunds already created under capture children. opw-6359020 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283790 Forward-Port-Of: odoo/odoo#276154
The Point of Sale now automatically chooses a product option when it is the only available choice, as long as the option type is not multi-select. This removes an unnecessary step for cashiers and helps products with simple variants be added to an order smoothly.
Original PR description
Before this commit: ----------- - When a product attribute had only one available value, it was not automatically selected for display types other than multi. After this commit: ------------ - Automatically select the attribute value when an attribute has a single available value and its display type is not multi, allowing the product to be added without any additional user interaction. Task-6327371 Forward-Port-Of: odoo/odoo#282350 Forward-Port-Of: odoo/odoo#272437
The mail thread data request now returns only the information that is actually needed for the current user and conversation. This reduces unnecessary data handling and helps keep mail-related views more consistent across access scenarios, including multi-company cases.
Original PR description
This change cleans up the requested data from `/mail/thread/data` route, ensuring it aligns with what is actually needed depending on the user and thread. part of task-6452761 Forward-Port-Of: odoo/odoo#284452 Forward-Port-Of: odoo/odoo#280713
This fixes an intermittent issue in the Lunch app's automated order check by ensuring the test waits for the intended product to appear after changing location. It helps avoid false failures caused by outdated demo products still showing briefly, improving confidence in release testing without changing user-facing behavior.
Original PR description
The lunch order tour selects `Farm 1` before ordering a product. However, it only waits for the location input to be updated before clicking the first kanban record. With demo data installed, a product from the previous location can still be displayed while the product model is being reloaded. The tour can therefore order a demo product instead of the product created by the test. This notably fails during weekends when the corresponding demo vendor is unavailable. To fix we need to wait for the product created by the test before clicking it. Besides selecting the intended product, this also ensures that the product reload following the location change has completed. [error-181572 ](https://runbot.odoo.com/odoo/error/181572) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284626 Forward-Port-Of: odoo/odoo#281753
Argentinian accounting users can now create invoices for foreign customers even when export journals are unavailable or archived. Instead of stopping the workflow with an error, the system falls back to a standard invoice type so sales can continue without extra journal setup.
Original PR description
### Issue before this commit: Before this commit, users were completely blocked from creating an invoice for a foreign partner (e.g., "Cliente del Exterior") if all exportation journals were archived…
### Issue before this commit: Before this commit, users were completely blocked from creating an invoice for a foreign partner (e.g., "Cliente del Exterior") if all exportation journals were archived or unavailable, as the system would immediately trigger a RedirectWarning error. ### Steps to reproduce the issue: 1. Download Accounting and l10n_ar 2. Go to contacts and create a new one with: 1. Country as United States 2. VAT number ex. 55000002126 3. AFIP Responsibility Type as Cliente del Exterior 3. Go to Journals, filter for sales journals and archive: 1. Electronic Exportation Invoice (FEX) 2. Expo Sales Journal 4. Go to invoices and create a new one for the client you just created 5. As soon as you insert the client you will receive the error: You are trying to create an invoice for foreign partner but you don't have an exportation journal ### Cause of the issue: https://github.com/odoo/odoo/blob/014d58e3204d17db6dcba3c8ab7d8ad35003300e/addons/l10n_ar/models/account_move.py#L186-L189 The _onchange_partner_journal method rigidly enforced the use of an exportation journal for foreign AFIP responsibility types (codes 8, 9, and 10). If the query failed to find an active export journal, the code intentionally threw a hard error instead of providing a fallback mechanism. ### Reason to introduce the fix: This fix is introduced to prevent unnecessary workflow blocks. By catching the missing journal and defaulting the document type to "Invoice B" (code 6), the user can now successfully generate the invoice using a standard domestic sales journal without being forced to configure an exportation journal. opw-6442501 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284629 Forward-Port-Of: odoo/odoo#282971
Chat windows now keep their usual display priority by default while allowing other parts of Odoo to adjust how they appear on top of screens when needed. This prevents layout conflicts on mobile views and makes future customizations safer without disrupting existing behavior.
Original PR description
The z-index of chat windows on mobile views was previously fixed at `1020`, preventing other modules from adjusting their stacking order. This commit introduces a configurable z-index for chat windows, defaulting to `1020` while allowing other modules to override it when needed. task-6412411 Forward-Port-Of: odoo/odoo#283671 Forward-Port-Of: odoo/odoo#283178
Contacts now validate tax numbers using the country set on the partner record instead of guessing from the first two characters of the tax number. This prevents valid Mexican RFCs and similar identifiers from being incorrectly treated as foreign VAT numbers, reducing false validation errors when saving contacts.
Original PR description
**Issue**: If a partner has a tax number that begins with a different country’s code (for instance, a Mexican RFC number that begins with “RO”, matching Romania), when a user tries to add the tax…
**Issue**: If a partner has a tax number that begins with a different country’s code (for instance, a Mexican RFC number that begins with “RO”, matching Romania), when a user tries to add the tax number to the partner, there will be a validation error.
**Steps to reproduce** (on fresh database with Contacts app and l10n_mx module installed):
1. Make a new contact.
2. Give the contact a Mexican address.
3. Give the contact the RFC number (or `vat` field): ROS561231GR8.
4. Try to save this change. Observe the validation error.
**Explanation**:
The `get_all_identifiers` method uses the first two characters of `partner.vat` as a heuristic to detect the issuing country, since many VAT formats start with a country code (e.g. RO1234567897). This prefix was used unconditionally whenever it matched an item from `get_tin_metadata_of_country`. without checking whether the VAT actually belongs to that country. Some countries' identifier formats begin with letters which are not country codes. In Mexico, for instance, RFC numbers start with letters derived from the partner's name, so a partner named “Sofia Rodriguez” would get an RFC starting with “RO”. Therefore, this heuristic can produce false-positive matches against unrelated countries.
**Solution**:
We no longer use a partner's vat number to detect the issuing country. Instead, we use the partner's `country_code` field as the issuing country.
opw-6471006
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#283376This update makes an error message clearer when sending a credit note through French e-invoicing in demo mode. Users should better understand what went wrong during EDI document generation, reducing confusion and support effort.
Original PR description
Steps to reproduce: - Install `l10n_fr_pdp` module > Switch to `FR Company` - Activate `French e-invoicing` (Demo mode) - Create a New `Credit Note` with `FR Customer` > Send Issue: The system currently displays a confusing error message during EDI document generation. We are making the error message clearer and more user-friendly. opw-6412521 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284189
Sales quotation and pro forma email templates now use separate complete sentences for quotations and orders. This lets translators adapt grammar correctly in languages where the words require different articles or adjective forms, improving customer-facing email quality.
Original PR description
The quotation and pro forma email templates inserted either "quotation" or "order" into shared translatable text. In French, for example, "devis" is masculine while "commande" is feminine, so the surrounding articles and adjectives cannot agree with both terms. Define a complete sentence for each document state so translators can translate the surrounding grammar independently. opw-6445304 Forward-Port-Of: odoo/odoo#284596 Forward-Port-Of: odoo/odoo#283229
Odoo no longer lets users choose Peppol identifier codes that have been deprecated or removed from the official specification. This helps prevent invalid Peppol registrations and partner records, reducing errors in electronic invoicing setup.
Original PR description
Peppol EAS codes 0037, 0213, 9955, and 0193 are deprecated or removed from the Peppol specification but are still present in the selection field on stable branches, allowing users to register invalid identifiers. See: [eas codes](https://docs.peppol.eu/edelivery/codelists/v9.7/Peppol%20Code%20Lists%20-%20Participant%20identifier%20schemes%20v9.7.html) Before: - deprecated EAS codes were listed alongside valid ones in the partner's available Peppol EAS options, allowing users to select an outdated identifier for new or duplicated partners, or during Peppol registration. After: - Excluded deprecated EAS codes from the available Peppol EAS selection list on partners, preventing users from selecting them for new or duplicated partners, or during Peppol registration. Removed Deprecated codes in Master: odoo/odoo#271288 Task [link](https://www.odoo.com/odoo/project.task/6299691) task-6299691 Forward-Port-Of: odoo/odoo#284062 Forward-Port-Of: odoo/odoo#271435
A small configuration error meant two important accounting report records were not individually protected from deletion as intended. This fix corrects the list so both reports remain safely protected, reducing the risk of accidental removal.
Original PR description
On `ir.actions.report` we want to block the unlinking of specific reports in odoo. However, when the list was created a comma was missed between `action_account_original_vendor_bill` and `account_invoice_without_payment` which means we were actually protecting against people unlinking `action_account_original_vendor_billaccount_invoice_without_payment`. Adding in that comma will allow these two records to be properly protected. task-none Forward-Port-Of: odoo/odoo#283323
When multiple projects are duplicated at the same time, each copied project now receives only the milestones from its original project. This prevents copied projects from being cluttered with unrelated milestones from other selected projects, keeping project plans accurate.
Original PR description
Before this commit, duplicating several projects at once from the list view gave every copy the milestones of all the duplicated projects, because the copy loop assigned the milestones of the whole recordset instead of the ones of the project being copied. Duplicating a single project behaves correctly, which hid the issue. Steps to reproduce: - create two projects with milestones enabled, add a milestone to the first one and two others to the second one - select both projects in the list view and duplicate them Each copy contains the three milestones instead of only the milestones of its original project. Solution: Copy the milestones of the project being duplicated. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#278520
Daily time off accruals based on worked time now respect the employee's local working calendar. This prevents employees on Monday-to-Friday schedules from incorrectly earning time off on Saturdays in certain time zones.
Original PR description
## Current behavior: On a Monday–Friday working schedule, a Daily accrual plan that is based on worked time grants accrued time on Saturday as well, even though Saturday is not a working day. The…
## Current behavior: On a Monday–Friday working schedule, a Daily accrual plan that is based on worked time grants accrued time on Saturday as well, even though Saturday is not a working day. The employee accrues on 6 days per week instead of 5 (Sunday is correctly skipped. Only Saturday is wrong). ## Expected behavior: The employee accrues only on the 5 working days (Mon–Fri) → 5 grants per week. Saturday and Sunday should add nothing. ## Setup: - Working schedule: Standard 40h/week, Monday–Friday, 08:00–17:00. - All timezones set to Australia/Brisbane (UTC+10) and matching: employee, working schedule, and user are all the same timezone. - Accrual plan milestone: accrue 5 Hours, Daily, "At the end of the accrual period", "Based on worked time = Yes". ## Steps to reproduce: - Create the working schedule and accrual plan above, with the calendar timezone set to Australia/Brisbane. - Assign the accrual allocation to an employee, Starting on a Monday. - On the Time Off dashboard, use "Balance at the (date)" to project the balance day by day across a weekend (Friday → Saturday → Sunday → Monday). ## Cause of the issue: Accrual period boundaries were built as naive UTC midnights instead of local calendar midnights. ## Fix: Localize accrual period boundaries in the employee/resource timezone before calling resource calendar APIs. This bug is reproducible in multiple versions. PRs for: - v19.0: https://github.com/odoo/odoo/pull/279029 - v18.0: https://github.com/odoo/odoo/pull/279036 opw-6316062 Forward-Port-Of: odoo/odoo#283583 Forward-Port-Of: odoo/odoo#279029
This fix ensures that country-specific mandatory customer fields remain visible when creating or editing customers in Point of Sale. It helps businesses in affected localizations complete invoicing and customer records correctly without missing legally or operationally required information.
Original PR description
*: l10n_{ar,co,in,pe,uy}_pos **Problem:** The POS "Edit/Create customer" Form was switched to a standalone, hardcoded form view (view_partner_form_pos_ui) that inherits nothing during [1], in the…
*: l10n_{ar,co,in,pe,uy}_pos
**Problem:**
The POS "Edit/Create customer" Form was switched to a standalone, hardcoded form view (view_partner_form_pos_ui) that inherits nothing during [1], in the attempt to simplify the view when accessed from the PoS.
Every field that localizations and other modules add to the partner form by inheriting base.view_partner_form therefore disappeared when accessed from PoS. Some of the fields are required, for example, to invoice.
**Solution:**
Keep the simplified view as the default, but route the view selection through an overridable hook that localization can tweak case by case. The override is applied to the affected POS bridges (see module list).
Add a test to prevent future regression.
**Note:**
Another possibility is to re-inherit for each localization the new
standalone view, but this fix would need to update the module to work,
while this one works with just a restart.
There are still ongoing discussion with PoS team to see if we really
want to go back to each localization needing to inherit backend views.
[1]: https://github.com/odoo/odoo/pull/230721/changes#diff-66cd201e7e8cfff5218a9fa93efd72f0bd77659b87359f2ca8763702462aaf92R26
opw-6244777 (many more)
Forward-Port-Of: odoo/odoo#268158Peruvian accounting reports now use the exchange rate already stored on each accounting entry instead of recalculating it during report generation. This reduces rounding discrepancies and helps businesses get more reliable report figures.
Original PR description
Previously, the `_get_ple_report_data` method computed the currency rate when called. Since the calculation was based on the entry totals, it was prone to rounding errors. This PR makes it use the rate stored in the entry itself. This should lead to more accurate results. opw-6411322 Forward-Port-Of: odoo/enterprise#128027 Forward-Port-Of: odoo/enterprise#126882
This update adjusts an internal automated test related to mobile mail notifications so it continues to match recent template tracking behavior. It helps keep quality checks reliable without changing the product experience for users.
Original PR description
Task-6424104 Forward-Port-Of: odoo/enterprise#127778
The French VAT report submission now treats notes containing only spaces as empty. This prevents incomplete files from being sent to ASPOne and avoids avoidable submission errors for users.
Original PR description
While sending the tax return to ASPOne, before adding the BC zone we are checking that BA zone won't be empty as if BC is completed there must be the BA zone in the xml file. The problem is that when we have only whitespaces, the condition will be respected but later on due to cleanup_xml_node(), the BA zone will not be rendered in the xml but BC will and it leads to an error This commit checks that express_mention_reason fields is not empty or not only whitespaces task-6476440 Forward-Port-Of: odoo/enterprise#128242
AI chat windows now appear in front of other chats and fullscreen editing screens on mobile. This prevents AI assistance and related popups from being hidden, making the feature usable in those views.
Original PR description
AI chats opened on mobile views could appear behind other chats. This was inconsistent with the expected stacking behavior, where newly opened chats should appear on top of existing ones. To reproduce: * Open the chatter of any module. * Open the message composer in fullscreen mode. * Click the AI button. This commit increases the z-index of AI chats on mobile views so they are displayed on top of other chats. task-6412411 Forward-Port-Of: odoo/enterprise#128649 Forward-Port-Of: odoo/enterprise#128346
Appointment invitation emails can now generate public calendar links without running into permission errors. This helps ensure invitees receive working calendar links and reduces failures when sending appointment-related emails.
Original PR description
Since calendar attendee access tokens are restricted to system users, appointment mail templates must sudo token reads when generating public calendar links. This follows the same pattern as the calendar mail templates and avoids an AccessError when rendering attendee invitation emails. ref: https://github.com/odoo/enterprise/commit/88a3cca752a5f726cd0260b485fc93f65a268cf8 Forward-Port-Of: odoo/enterprise#128959
Fixed a timing issue that could cause Point of Sale AvaTax orders to reload incorrectly after payment, sometimes making selected order lines disappear. The change makes order synchronization more reliable and improves automated test stability for AvaTax checkout flows.
Original PR description
The POS Avatax tour sporadically failed after returning from the payment screen. The race was reproduced locally by delaying the mocked Avatax response in mocked_request(). There's three somewhat…
The POS Avatax tour sporadically failed after returning from the payment screen. The race was reproduced locally by delaying the mocked Avatax response in mocked_request(). There's three somewhat related fixes. Firstly, get_order_tax_details() calls sync_from_ui(), which emits a SYNCHRONISATION notification. Unlike the normal POS sync path, the Avatax RPC did not pass the device context. The browser therefore treated its own notification as coming from another device and started an independent reload of open orders. That reload could replace the current order state after the tour returned to the product screen, causing the selected order line to disappear. We now pass the normal sync context so the browser can properly ignore its own notification. Secondly, we'll keep the complete sync_from_ui() response and replace its order, line, tax, and tax group data after the AvaTax calculation. We then simplify the processing client-side by moving towards the established pattern in the POS: missingRecursive() to load any other referenced records, and then pass that through loadConnectedData(). Lastly, clickPayButton() only waits for the payment screen element to be displayed. The AvaTax request starts from the screen's onMounted() callback, leaving a short window where the screen and its buttons exist but the request and UI blocker have not started yet. The next tour step can probably run during that window. To make sure this can't happen we explicitly waitRequest(). This first waits for requests to appear and then waits for them to complete. runbot-error-944281 Forward-Port-Of: odoo/enterprise#125944
Users can now open links included in spreadsheet cell comments with a normal click, as expected. This removes a frustrating interaction issue and makes shared references in comments easier to access.
Original PR description
Current behavior before PR: - Clicking a link in a cell comment did not work. A left click was blocked, while Ctrl+click (or Cmd+click) opened the link in a new tab. - This was caused by `t-on-click.prevent` on the comment thread and popover. It was originally added because the scroller service used the URL hash to scroll to anchors, which was removed in https://github.com/odoo/odoo/commit/711e9c9f24818714129f55283e2df64503d93605 Desired behavior after PR is merged: - `t-on-click.prevent` is removed and links in cell comments can be opened normally with both left click and Ctrl+click (Cmd+click on macOS). Task: [6448651](https://www.odoo.com/odoo/project/2328/tasks/6448651) Forward-Port-Of: odoo/enterprise#129188 Forward-Port-Of: odoo/enterprise#127473
This fix restores required customer information fields in the Point of Sale customer form for several country-specific invoicing flows. It prevents missing mandatory data from blocking invoices or compliance-related sales processes in affected localizations.
Original PR description
*: br,cl,ec,gt,it,ke,mx **Problem:** The POS "Edit/Create customer" Form was switched to a standalone, hardcoded form view (view_partner_form_pos_ui) that inherits nothing during [1], in the attempt…
*: br,cl,ec,gt,it,ke,mx **Problem:** The POS "Edit/Create customer" Form was switched to a standalone, hardcoded form view (view_partner_form_pos_ui) that inherits nothing during [1], in the attempt to simplify the view when accessed from the PoS. Every field that localizations and other modules add to the partner form by inheriting base.view_partner_form therefore disappeared when accessed from PoS. Some of the fields are required, for example, to invoice. **Solution:** Keep the simplified view as the default, but route the view selection through an overridable hook that localization can tweak case by case. The override is applied to the affected POS bridges (see module list). Add a test to prevent future regression. **Note:** Another possibility is to re-inherit for each localization the new standalone view, but this fix would need to update the module to work, while this one works with just a restart. There are still ongoing discussion with PoS team to see if we really want to go back to each localization needing to inherit backend views. [1]: https://github.com/odoo/odoo/pull/230721/changes#diff-66cd201e7e8cfff5218a9fa93efd72f0> opw-6244777 (many more) Forward-Port-Of: odoo/enterprise#119316
Cash in/out receipts in Point of Sale can now print even when a default printer has not been configured. The system now falls back to an available printer, reducing failed receipt printing during cash management operations.
Original PR description
## Description Fixes cash in/out receipt printing when no default printer is configured. ## Issue Previously, an early return in the printer selection logic prevented the fallback printer mechanism from being executed, causing receipt printing to fail when no default printer was configured. ## Fix Removed the early return so that the fallback printer selection logic can select an available printer before attempting to print the receipt. This ensures cash in/out receipts can be printed even when no default printer is configured. opw-6485495 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283789
A bug was fixed so planning sessions linked to quotations only use real sale order lines, not section or note rows from quotation templates. This prevents errors in a specific Field Service planning flow and helps keep quotation-to-planning links accurate.
Original PR description
This commit patches a niche bug involving creating a quotation via a quotation template containing a line section, then connecting it to an active planning session. The current architecture did not filter out `line_section` or `line_note` typed lines. This updated search domain resolves this issue. opw-6351484 Forward-Port-Of: odoo/enterprise#129228 Forward-Port-Of: odoo/enterprise#125056
This update fixes incorrect tax configuration details for Hungary in Odoo’s localization and electronic invoicing modules. It helps Hungarian companies apply and report taxes more accurately, reducing the risk of configuration-related accounting errors.
Original PR description
Adjusting incorrect tax configuration elements for Hungary. task-6397915 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284432 Forward-Port-Of: odoo/odoo#282697
This fixes an inventory forecast issue where subcontracted components could incorrectly appear as available before they were actually received or reserved. Businesses using subcontracting and make-to-order routes will see more accurate material availability, helping prevent premature production decisions.
Original PR description
### Steps to reproduce: - Enable Multi-Steps Routes, subcontracting and unarchive the MTO route - Create 3 products: Final Product (FP), Subcontracted Component (SB), Component (COMP) and put SB in…
### Steps to reproduce: - Enable Multi-Steps Routes, subcontracting and unarchive the MTO route - Create 3 products: Final Product (FP), Subcontracted Component (SB), Component (COMP) and put SB in MTO - Create a BOM for FP: 1 x SB - Create a subcontracted BOM for SB: 1 x COMP - Create and confirm an MO for 1 unit of FP > This generates a subcontracted MO for 1 unit of SB - Confrim the subcontracted PO and go back to the MO of FP #### > The component move forecast appears "Available" even if the SB unit is neither received nor 'pre-reserved' (the quantity of the move raw is still 0). ### Cause of the issue: The `forecast_widget` displays an available status in case the demand of the move is expected to be fulfilled and there is no `forecastExpectedDate`: https://github.com/odoo/odoo/blob/a46cdcd9d0b575eb668ed738565637f346bbdf7b/addons/stock/static/src/widgets/forecast_widget.xml#L1-L19 https://github.com/odoo/odoo/blob/4fbd88ad3ac2d92b47b024b96f1c40ed4b3f97e3/addons/stock/static/src/widgets/forecast_widget.js#L15-L26 Now, the issue is that this `forecastExpectedDate` is currently unreliable in this use case as the `forecast_expected_date` of the SB component move is incorrectly computed to be False rather than matching its subcontracted receipt counter part. To be more precise, the `forecast_expected_date` is computed based on the report lines: https://github.com/odoo/odoo/blob/8b8b99e371fcf214b9c55fb2fbfca20f2ee66f53/addons/stock/models/stock_move.py#L579-L581 https://github.com/odoo/odoo/blob/8b8b99e371fcf214b9c55fb2fbfca20f2ee66f53/addons/stock/models/stock_move.py#L2701 The component move is an out move of SB from Stock to Production and is linked to the finished subcontracted move of SB from Production to Subcontracting. In particular, this finished subcontracted move (which is assigned) contributes to the 'reserved' out qties on the get go and leads to an already reserved out quantity of 1.0 even thought the move is purely external and linked to the subcontractor process: https://github.com/odoo/odoo/blob/ef89bc530ffae93a553003559ca9078b7a9d0653/addons/stock/report/stock_forecasted.py#L241-L268 In turn, the `demand_out` matched its `reserved_out` (even thought this reserved_out should be 0) so that no `in_transit` move is provided to provide an `expected_date`: https://github.com/odoo/odoo/blob/ef89bc530ffae93a553003559ca9078b7a9d0653/addons/stock/report/stock_forecasted.py#L426-L435 opw-6445209 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283177
Opening the sales product configurator in debug mode no longer fails when a product has an empty custom attribute value. This keeps sales order editing reliable for configurable products and avoids interruptions for users or testers working with debug mode enabled.
Original PR description
This commit prevents a traceback when opening the product configurator in debug mode. Prop validation only happens in debug mode, which exposed an issue with products that allow entering custom attribute values (e.g. Acoustic Bloc Screen). When a custom value is left empty, it is read as `false` when custom attributes are retrieved from the frontend. As a result, the `custom_value` key in the `customPtavs` prop passed to the product configurator contains a boolean, whereas the prop expects a string. This commit ensures that an empty string is passed instead of `false` when opening the configurator for a line with an empty custom attribute value. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284482
This fixes how product names and descriptions appear in accounting line descriptions when space is limited. Product names will no longer be mistakenly shown in italic after wrapping, making accounting entries easier to read.
Original PR description
This commit removes the CSS hack used to make the description italic when a product is present in an AML. Instead, the product name and description are rendered separately using two spans in the readonly state. The previous `:first-line` approach did not handle line wrapping correctly: when the column was too narrow, part of the product name could wrap onto the next line and incorrectly appear italic. Rendering the two parts separately avoids this issue. Before | After -- | -- <img width="414" height="192" alt="image" src="https://github.com/user-attachments/assets/a178a35c-6d55-4982-a9c3-2fe727c626cc" /> | <img width="399" height="198" alt="image" src="https://github.com/user-attachments/assets/4716f93a-6d47-4631-a982-db43d9d38ef0" /> --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284232
This change makes an internal website editor test more reliable by waiting for the interface to finish updating before checking popup visibility. It helps reduce random CI failures, improving development stability without changing behavior for end users.
Original PR description
The test `undoing something on a target outside s_popup closes it` had a few fails in CI: the `fa-eye-slash` was not set as expected. This commit adds a `waitSidebarUpdated` call just before to ensure owl has no pending rendering when checking the eye. The fix is similar to aaf0f54d1feda60becb0bfbad578b366715c0172 which is about a similar failure in another test. runbot-938967 Forward-Port-Of: odoo/odoo#284381
Odoo now skips caption handling for unusual figure content, such as figures with no images or multiple images, instead of raising an error. This prevents Helpdesk tickets created from incoming emails from failing when the email contains valid but unexpected HTML.
Original PR description
**Steps to reproduce:** - Install Helpdesk - Create an email with a figure that has no image - Send it to Helpdesk email alias - Open up auto-created ticket from the email - `OwlError` is raised on `CaptionPlugin.addImageCaption` **Issue:** `CaptionPlugin` [1] was designed for `<figure>` elements with a single `<img>` and a single `<figcaption>` (mainly for editor direct interactions). But the HTML specifications also allow `<figure>` with 0 or more than 1 `<img>` element(s), in which case an error is raised (or some elements are removed). (see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/figure) **Fix:** Ignore such `<figure>` for now as it would require a rework of the plugin. [1] https://github.com/odoo/odoo/commit/b9d112a5800cfe11dc434caa0d335fa3f3db7178 opw-6413422 Forward-Port-Of: odoo/odoo#284139 Forward-Port-Of: odoo/odoo#279981
When translating content edited inside a related-record dialog, Odoo now saves those pending edits before opening the translation window. This prevents users from seeing outdated or missing text in the translation dialog, helping avoid incorrect translations.
Original PR description
Clicking the translate button saves the form's root record before opening the translation dialog, since https://github.com/odoo/odoo/commit/9da52919a03dbcee5209430918195158c0652099. A record opened…
Clicking the translate button saves the form's root record before opening the translation dialog, since https://github.com/odoo/odoo/commit/9da52919a03dbcee5209430918195158c0652099. A record opened in an x2many form dialog keeps its changes for itself until the dialog is saved, see https://github.com/odoo/odoo/blob/242f6d3cf7288853f163ac6986a3b7aa4279efaf/addons/web/static/src/model/relational_model/static_list.js#L193. Its pending changes are not part of the root record changes, so saving the root sends nothing to the server, and the translation dialog then shows the stored terms instead of the current content, or no terms at all when the stored value is empty. The fix changes openTranslationDialog in translation_button.js, the place that decides which record to save. When the record keeps its changes for itself (record._noUpdateParent), the record is saved directly, like the button did before the commit above. The root record is still saved in the other cases, so the editable list case that commit fixed keeps working. Steps to reproduce: 1. Activate a second language in Settings > Translations > Languages 2. Open the Surveys app, open a survey and click a question in the Questions tab 3. In the Description tab, change the description 4. Click the EN button on the description field => the translation dialog shows the terms of the previous description, not the current one Ticket [link](https://www.odoo.com/odoo/project.task/6237291) opw-6237291 Forward-Port-Of: odoo/odoo#283750 Forward-Port-Of: odoo/odoo#269507
This fixes an issue where editing a chatter message could break contact mentions when one contact's name or ID was contained inside another's. Users can now edit messages with multiple similar mentions without links being corrupted or moved.
Original PR description
# Introduction This PR fixes broken mention links linked to the fact that we replace strings without paying attention to the fact that some strings may contain others that we want to replace later.…
# Introduction
This PR fixes broken mention links linked to the fact that we replace strings
without paying attention to the fact that some strings may contain others
that we want to replace later. This affects both id's and names of records.
See commit messages for more details.
# How to reproduce
- Create Contact A and then Contact B and either :
- Contact B's id need to contain Contact A's id (e.g. Contact B id = 12; Contact A id = 1)
- Contact B's name need to contain Contact A's name (e.g. Contact B name = ABC; Contact A name = AB)
- In a chatter create a message mentionning first Contact B and then Contact A
> Depending on the version, you might need to reload the page here
- Edit the message and save
# The issue
We see a broken mention in the chatter
# Cause
When saving an edited message, we give the raw body of the message (without the mention links) and the mentionend partners to `generateMentionsLinks` : https://github.com/odoo/odoo/blob/f9f605b1783d252d5e005bec50a2a72dd4ae0e13/addons/mail/static/src/utils/common/format.js#L152
This method's purpose is to replace the text links ("@Contact A") with actual html links. It does so by enumerating each partner given as an argument and replace the text mention with a placeholder :
https://github.com/odoo/odoo/blob/f9f605b1783d252d5e005bec50a2a72dd4ae0e13/addons/mail/static/src/utils/common/format.js#L158
It will then replace the placeholders with actual links : https://github.com/odoo/odoo/blob/f9f605b1783d252d5e005bec50a2a72dd4ae0e13/addons/mail/static/src/utils/common/format.js#L208-L218
The issue is that in both of those steps, we can try to replace a string that is contained
in another string we want to replace.
For exemple :
"string123 some text string12"
If we try to replace "string12" first, then we will select the wrong string :
"[string12]3 some text string12".
opw-6313748
Forward-Port-Of: odoo/odoo#284016
Forward-Port-Of: odoo/odoo#272549This fix adjusts restaurant appointment point-of-sale tests so they continue to work after POS data reloads clear browser storage. It keeps the production behavior unchanged while preventing test failures and improving confidence in future updates.
Original PR description
A recent PR in the community repository introduced a full clear of both `localStorage` and `sessionStorage` when reloading POS data. While this is the intended behavior in production, it breaks the test framework. This commit mocks the `clear` methods directly within the tour steps right before the reload action. This ensures the test survives the page reload and keeps its state, without polluting the core production code with test-specific logic. task-6456447 Forward-Port-Of: odoo/enterprise#129010 Forward-Port-Of: odoo/enterprise#128091
This fix prevents a manufacturing test cleanup from accidentally touching stock rules outside the intended route. It reduces the risk of test failures caused by unrelated demo or company data being removed while keeping the change internal to test behavior.
Original PR description
`test_check_update_qty_mto_chain` was removing `stock.rule` records from other companies using `mto_route.rule_ids.search()`. Calling `search()` on a recordset does not restrict the search to the records already present in that recordset, so the domain was effectively applied to all `stock.rule` records. With demo data, this could attempt to unlink an unrelated stock rule that is still referenced by an existing stock move, causing a `stock_move_rule_id_fkey` foreign key violation. This commit restricts the search explicitly to rules belonging to `mto_route` before unlinking them. [error-940031 ](https://runbot.odoo.com/odoo/error/940031) --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282930
This fixes an issue where archiving or deleting one user could wrongly remove a shared contact from restricted discussion channels, even if another active user for that contact still had access. Contacts are now only unsubscribed when none of their remaining users qualifies for the channel, helping teams avoid accidental loss of communication access.
Original PR description
Before this commit, archiving or deleting a user removed its partner from every group restricted channel, even when another user of that partner was still active and in the group the channel requires. This happens because the members to unsubscribe are searched on partner_id alone, so the search cannot tell whether the partner keeps another user. This commit fixes the issue by unsubscribing a partner only when none of its remaining users has the group the channel requires. Forward-Port-Of: odoo/odoo#284354 Forward-Port-Of: odoo/odoo#283807
UPS shipping rate checks now follow UPS documentation by allowing phone numbers between 1 and 15 digits. This prevents valid customers in countries with shorter phone numbers, such as Luxembourg, from being blocked when requesting UPS shipping rates.
Original PR description
**Steps to reproduce:** - Create a contact with a phone number that has 9 characters - Setup an UPS carrier, a configuration that works is UPS Saver as Service Type and UPS Package/customer supplied…
**Steps to reproduce:** - Create a contact with a phone number that has 9 characters - Setup an UPS carrier, a configuration that works is UPS Saver as Service Type and UPS Package/customer supplied as a package type - Create a quotation, put the created contact as a client - Try adding a shipping and getting the rates - An User Error appears, the phone number is too short **Why the fix:** Before this commit, any phone number that was less than 10 characters would raise an User Error, but some countries, such as Luxembourg, use phone numbers that are nine characters long or even less. If we check the official UPS documentation (https://developer.ups.com/tag/Shipping?loc=en_EN#operation/Shipment), we can see in the Ship_to/Phone section, that the phone number should be a number between 1 and 15, not saying it should be 10 characters or more. <img width="495" height="473" alt="image" src="https://github.com/user-attachments/assets/fed82987-ffb8-4b84-b282-6c3d3b4f304e" /> After this commit, we adapt the way we prevent the user from inputing phone numbers to fit the official UPS documentation. opw-6307577 Forward-Port-Of: odoo/enterprise#128632 Forward-Port-Of: odoo/enterprise#122831
The German tax report XML now preserves cents for the Kz83 amount instead of rounding it down to a whole number. This helps ensure reported tax values remain accurate, for example keeping 26.40 as 26.40 rather than 26.00.
Original PR description
Description of the issue this commit addresses: The German tax report XML casts Kz83 to an integer before formatting it. This truncates decimal values, causing amounts such as 26.40 to become 26.00. --- Desired behavior after this commit is merged: This commit preserves the Kz83 decimal value and formats it with two decimal places in the German tax report XML. --- task-6414439 Forward-Port-Of: odoo/enterprise#125607
Appointment closing days are now refreshed immediately after being added, so teams can see schedule changes right away. The add closing day option is also limited to the appropriate appointment views and leave types, reducing confusion and preventing incorrect entries.
Original PR description
Fix some issues with the closing day feature rendering: - The closing day is not appearing in the gantt view after being created using the gantt "Add closing day" button. Re-fetching the gantt data after the closing day record creation to make sure the view is up-to-date. - The "Add closing day" button is visible from the calendar app but it should only be visible from appointment. As the calendar controller view is inherited in extension, the button was visible both from calendar and from appointment. Only displaying the button if we're in the appointment views. - In the appointment gantt, calendar and list views, making sure the "Add closing day" button only allows creating a leave of the same type as the currently opened views. In other word, hide the leave type 'resources' in the 'users' based views and the other way around. Task-6426018 Forward-Port-Of: odoo/enterprise#125854
Chilean electronic invoices that mention a foreign currency on invoice lines can now be imported even when the optional foreign-currency total is absent. This prevents mail-server invoice imports from failing and uses the standard total as a safe fallback.
Original PR description
When importing an incoming DTE through the fetchmail server, the total amount is read from the MntTotOtrMnda as soon as a Moneda node is present in the document. Steps to reproduce: - Set up a CL company with a DTE mail server - Fetch a DTE that includes the line-level Moneda node but does not include the header OtraMoneda block, so no MntTotOtrMnda - Run the fetchmail cron and check the logs Issue: The DTE fails to import Analysis: Occurs since https://github.com/odoo-dev/enterprise/commit/5805a92f91411846fdffa245cb047397cfc9b1f3 Moneda is defined at line level while MntTotOtrMnda in the optional header block Encabezado/OtraMoneda. Instead of assuming MntTotOtrMnda is always present whenever the document carries a foreign currency, fall back to the base-currency total MntTotal when it is missing. opw-6432612 Forward-Port-Of: odoo/enterprise#128766 Forward-Port-Of: odoo/enterprise#126869
UPS return shipments now include the commercial invoice in the delivery chatter, matching the behavior of outbound international shipments. US ZIP+4 postal codes are cleaned before being sent to UPS, preventing valid deliveries from being rejected.
Original PR description
Issues ----- 1. Commercial invoice is not forwarded to the user for the return delivery. 2. US postal codes of format 12345-6789 cause the delivery to be rejected. ----- Steps to reproduce issue 1…
Issues ----- 1. Commercial invoice is not forwarded to the user for the return delivery. 2. US postal codes of format 12345-6789 cause the delivery to be rejected. ----- Steps to reproduce issue 1 ----- - Set up UPS with return labels - Create an INTL delivery & confirm > OUT delivery has a commercial invoice in chatter, but the return doesn't Cause ----- The OUT and return call are not made using the same function. The OUT call is made via `ups_rest_send_shipping` which explicitly extracts the commercial invoice from the UPS response https://github.com/odoo/enterprise/blob/1a7c8ac34348ebc1ebe2da4100bdaec57484056f/delivery_ups_rest/models/delivery_ups.py#L204-L205 We should adapt `ups_rest_get_return_label` to match. ----- Steps to reproduce issue 2 ----- - Set up UPS - Create an american customer with a 9 digit zip (eg 20500-0003) - Create an delivery to the customer & confirm > Error: Invalid sold to postal code. Valid length is 0 to 9 alphanumeric Cause ----- The zip code is transmitted as-is, so we should sanitise it beforehand. https://github.com/odoo/enterprise/blob/c8c2f13b7fd17e215044fc62774f2b4a378aaf8c/delivery_ups_rest/models/ups_request.py#L368 Doc: https://github.com/UPS-API/api-documentation/blob/69e8a3cee7f9d3bf80735ae329aed0d8be156f97/Shipping.yaml#L5410-L5420 ----- Ticket: opw-6422500 Forward-Port-Of: odoo/enterprise#127375
Employees and managers can now mark multiple appraisals as done from the list view without encountering an error. The completion notification is now handled separately for each appraisal, making the batch action reliable.
Original PR description
Steps to reproduce: - select multiple appraisals and try to mark as done from list view. Issue: - The completion notification uses an appraisal variable assigned by a previous loop, raising an UnboundLocalError. Furthermore, message_notify() requires a singleton. Fix: - notify and post the completion message for each appraisal explicitly. task-6479018 Forward-Port-Of: odoo/enterprise#128207
This fixes an issue where companies that cannot receive Peppol invoices through Documents could lose their required incoming invoice journal setting. Incoming Peppol documents for affected companies, such as French companies using electronic invoicing rules, are now handled as vendor bills instead of being incorrectly stored in Documents.
Original PR description
Peppol documents can be received in a journal or in the Documents app (peppol_reception_mode). Some companies cannot use Documents: _peppol_allows_document_reception() returns False and the journal…
Peppol documents can be received in a journal or in the Documents app (peppol_reception_mode). Some companies cannot use Documents: _peppol_allows_document_reception() returns False and the journal stays required. This is the case of French companies (via l10n_fr_pdp). The onchange of the settings and the import did not check this method. So on a French company with the mode set to 'documents': - the Settings cleared the journal on each opening, while it was still required - the incoming documents were saved in Documents instead of vendor bills Steps to reproduce: - Create a Belgian company, with a purchase journal, and register it on Peppol as receiver - Set the reception mode to "Receive in Documents" - Change the fiscal position to France, and install l10n_fr_pdp, the Peppol part is replaced by "French Electronic Invoicing", so the radio button is not visible anymore, but the company still has peppol_reception_mode == 'documents'. - Open the Settings again: the field "Incoming Invoices Journal" is empty. opw-6429691 Forward-Port-Of: odoo/enterprise#128441 Forward-Port-Of: odoo/enterprise#126462
Rental orders will now appear in Intrastat reports only when their duration is at least two years. This prevents short-term rentals from being reported incorrectly, improving compliance and report accuracy.
Original PR description
Problem: Some rental orders are showing in Intrastat reports when they should not be showing. Only rental orders with duration of 2 years or more should be shown in Intrastat reports. However, all rental orders are being shown. <img width="783" height="768" alt="intrastat_leasing" src="https://github.com/user-attachments/assets/7419e3dc-7b3e-4234-809f-6973fef93fc1" /> Cause: When querying the lines to show in the Intrastat report, there is no condition that checks for the duration of rental orders. opw-6351456 Forward-Port-Of: odoo/enterprise#125042
Partial receipts processed in the Barcode app no longer remove pending operation-level quality checks when users return to the transfer. This keeps required quality controls in place until the whole receipt is properly completed or cancelled, reducing the risk of missed inspections.
Original PR description
Steps to reproduce --- 1. Create a quality control point on the Receipts operation type with Control per set to Operation. 2. Confirm a receipt of 2 units of the product: one pending operation…
Steps to reproduce --- 1. Create a quality control point on the Receipts operation type with Control per set to Operation. 2. Confirm a receipt of 2 units of the product: one pending operation quality check is created. 3. In the Barcode app, receive 1 unit and go back to the transfer with the back button. 4. The pending operation quality check is gone. Issue --- Going back from the Barcode app calls `post_barcode_process`, which on a partial reception splits the picked move into a done move and a remaining move, then merges the transient duplicate back with `_merge_moves`. https://github.com/odoo/enterprise/blob/b89614661682ecbd131d940539aac8afdd9d7289/stock_barcode/models/stock_move.py#L57-L60 `_merge_moves` cancels that transient duplicate through `_action_cancel` before unlinking it. https://github.com/odoo/odoo/blob/8f3100ca597559945cc42d9ef9517edbb40a900b/addons/stock/models/stock_move.py#L1400-L1401 The `quality_control` override of `_action_cancel`, picks the pending checks to drop from `is_product_canceled`, a `defaultdict(lambda: True)` keyed by `(picking, product_id)`. An operation check has no `product_id`, so its key is never computed by the loop and reads back the `True` default, so it is deleted even though the transfer still has a live move. Since an operation check covers the whole transfer, it must be dropped only when every move of its picking is cancelled. https://github.com/odoo/enterprise/blob/b89614661682ecbd131d940539aac8afdd9d7289/quality_control/models/stock_move.py#L68-L76 opw-6439179 Forward-Port-Of: odoo/enterprise#129051 Forward-Port-Of: odoo/enterprise#127427
Euro payments sent from bank journals in another currency can now be marked with the required SEPA values when the new setting is enabled. This helps businesses generate compliant payment files for SEPA-zone transfers without changing the journal currency, while leaving existing behavior unchanged by default.
Original PR description
Steps to reproduce: - Configure a bank journal whose currency isn't EUR (e.g. SEK, USD, GBP). - Use the generic ISO20022 payment method to send a payment in EUR to a SEPA-zone IBAN. - Generate the…
Steps to reproduce: - Configure a bank journal whose currency isn't EUR (e.g. SEK, USD, GBP). - Use the generic ISO20022 payment method to send a payment in EUR to a SEPA-zone IBAN. - Generate the pain.001 file: SvcLvl/Cd is NURG and ChrgBr is SHAR instead of the SEPA-mandated SEPA/SLEV. Cause of the issue: SvcLvl/Cd and ChrgBr are derived purely from the technical payment method code, not from whether the transaction actually qualifies as SEPA. The 'sepa_ct' payment method (which hardcodes SvcLvl=SEPA and ChrgBr=SLEV) is only ever offered on journals whose own currency is EUR. A journal in any other currency that occasionally sends a EUR payment therefore always falls back to the generic 'iso20022' payment method, which unconditionally reports NURG/SHAR. The same gap already exists, and is already solved, for Switzerland via the 'iso20022_ch_force_sepa' parameter, which dynamically remaps 'iso20022_ch' batches to 'sepa_ct' when their currency is EUR. No equivalent existed for any other country. Solution: Generalize that mechanism with a new opt-in parameter, 'account_iso20022.force_sepa_for_eur'. When set, a EUR-denominated batch generated through the generic 'iso20022' payment method is remapped to 'sepa_ct' for XML-generation purposes, so it correctly reports SvcLvl=SEPA and ChrgBr=SLEV. The parameter defaults to disabled, so the default behavior is unaffected unless explicitly turned on. opw-6006230 Forward-Port-Of: odoo/enterprise#127589
This fix allows users to enter and compare budget amounts on the Moroccan profit and loss report. It ensures the correct report column is used for budget comparisons and prevents entered budget values from disappearing.
Original PR description
Before this commit, it was impossible to use budget on the Moroccan P&L, for the following reasons: - The feature was designed for one-column reports. MA's P&L uses 3, one of which is the total of…
Before this commit, it was impossible to use budget on the Moroccan P&L, for the following reasons:
- The feature was designed for one-column reports. MA's P&L uses 3, one of which is the total of the two others.
=> We remove that requirement, and make sure to always select the 'balance' column as the reference for the budget comparison.
- When trying to input a budget amount in the report, the amount disappeared entirely.
=> This was because the total column of report was not using 'balance' as its expression label. We fix that by rewriting the expression labels of that report.
The fact we hardcode the use of 'balance' is arguable. It is however not possible here to rely on some custom handler to change a specific option key that would be used to generate the budget comparison data, since some of those data need to be generated in the get_options, before _custom_options_initializer even gets called. This is the simplest approach, and this case is rare enough for us to deem it acceptable.
opw-6385229
Forward-Port-Of: odoo/enterprise#129003
Forward-Port-Of: odoo/enterprise#128266Financial report snapshots are now paused when an open-ended fiscal or tax lock exception keeps a period editable. This prevents users from seeing outdated report amounts and removes snapshots that may have been created during the exception.
Original PR description
An open-ended fiscal or tax lock exception keeps the period editable, but snapshot generation did not consider it and could serve stale amounts. Prevent snapshots while a full exception is active and clear snapshots created during it. opw-6427776 Forward-Port-Of: odoo/enterprise#127525
Odoo now recognizes more modern search and AI crawlers so they can reach the correct website pages instead of getting stuck in repeated language redirects. This helps improve page inspection and indexing reliability while leaving normal visitor language behavior unchanged.
Original PR description
Modern crawlers now send an `Accept-Language` header (for example, `en-US,en;q=0.9`), whereas historically they did not. When that language differs from the website's default language,…
Modern crawlers now send an `Accept-Language` header (for example, `en-US,en;q=0.9`), whereas historically they did not. When that language differs from the website's default language, `ir.http._match()` issues a 303 redirect from `/page` to `/<lang>/page`. Since crawlers do not retain cookies, unrecognized agents are redirected on every request and never reach the default-language page. Customers reported that Google Search Console URL Inspection live tests only receive a redirect and that pages remain unindexed. Googlebot itself is not affected because it already matches the existing `bot` token. `_match()` already skips language redirects for recognized bots by serving the default-language page directly. Extend the `bots` user-agent list with modern crawler identifiers, each verified against vendor documentation: * `google-inspectiontool`: Search Console URL Inspection / Rich Results Test * `googleother`: Google generic crawler (`GoogleOther`, `GoogleOther-Image`, `GoogleOther-Video`) * `meta-external`: `meta-externalagent`, `meta-externalfetcher`, and `meta-externalads`, successors to the already-listed `facebookexternalhit` * `meta-webindexer`: Meta AI search indexer * `chatgpt-user`: OpenAI user-request fetcher (currently matched only through the `bot` substring in its info URL, which is fragile) * `claude-user`: Anthropic user-request fetcher * `perplexity-user`: Perplexity user-request fetcher The redirect behavior remains unchanged for human visitors. Localized pages continue to be crawlable through their own URLs (for example, `/fr/page`) via `hreflang` alternates. As a side effect, `link_tracker` and `mass_mailing_sms` no longer count clicks from these crawlers, and website visitor tracking skips them. task-6213245 Forward-Port-Of: odoo/odoo#275571
Duplicating project tasks, using task templates, or generating recurring tasks now preserves the correct dependency chain between sub-tasks. This prevents copied tasks from showing reversed or mismatched dependencies, helping teams keep project workflows accurate.
Original PR description
**Problem:** Duplicating a task, creating a task from a task template, or generating the next occurrence of a recurring task scrambles the dependencies between its sub-tasks: each copied sub-task…
**Problem:** Duplicating a task, creating a task from a task template, or generating the next occurrence of a recurring task scrambles the dependencies between its sub-tasks: each copied sub-task carries the dependencies of a different sub-task instead of its own. **Steps to reproduce:** 1. Enable Task Dependencies on a project. 2. Create a task with three sub-tasks and chain them: the second depends on the first, the third depends on the second. 3. Duplicate the task, or use "Create from template" if the task is a template. 4. Open the sub-tasks of the new task and look at their dependencies. **Current behavior:** The dependencies of the copied sub-tasks are shifted: the chain runs in the reverse order of the original one. **Expected behavior:** Each copied sub-task depends on the copy of the sub-task its original depended on, so the new task reproduces the original chain. **Cause of the issue:** `_create_task_mapping` builds the original to copy mapping by pairing `original_task.child_ids` with `copied_task.child_ids` positionally, on the assumption stated in its docstring that both recordsets share the same index order. They do not. `project.task._order` ends with `id desc`, so `child_ids` is read newest-first, while the copies are created by iterating the original `child_ids` in that same order. The copies' ids therefore ascend along the original list, and reading them back through `child_ids` returns them in the exact reverse order. `zip` then pairs each original with the copy of the sub-task at the mirrored position, and `_resolve_copied_dependencies` writes every `depend_on_ids` and `dependent_ids` onto the wrong copy. This affects every caller of that method: `copy`, the task template action, and the creation of the next occurrences of a recurring task. **Fix:** Sorting the copied children by id restores the correspondence because id order is the order in which the copies were created from the original list, an invariant that holds whatever `_order` does, whereas the previous code silently depended on `_order` producing the same sequence on both sides. `test_duplicate_project_with_subtask_dependencies` and `test_recurrence_copy_task_dependency` were reading the copies by `child_ids` index too, which the mirrored mapping happened to satisfy, so they passed on a wrong result; they now index them in creation order as well. opw-6386578 Forward-Port-Of: odoo/odoo#284548 Forward-Port-Of: odoo/odoo#280893
Zero-demand stock transfers are now included when calculating past forecasted inventory, preventing incorrect negative quantities from appearing historically. This helps businesses rely on more accurate stock forecasts after unplanned physical movements, though the stock report view must be updated for the fix to take effect.
Original PR description
**Problem:** When creating a transfer that moves out a product with zero demand quantity, it will change the forecasted quantity of that product in the past. **Cause:** The query filtered out the stock move with zero demand quantity, which preventing the system from accounting for unplanned physical transfers when retroactively calculating past inventory balances **Steps to reproduce the issue:** 1. Create a stock picking with 0 demand quantity that moves a product from an internal location to a virtual location or production location. 2. The forecasted quantity of the product becomes negative in the past. **Fix:** Add another check in the query to include stock moves with zero demand quantity. **Notes:** Since the forecast report is made from a SQL view, this will require a -u to update the report. opw-6462883 Forward-Port-Of: odoo/odoo#284000 Forward-Port-Of: odoo/odoo#283577
Odoo now correctly excludes temporary wizard screens from reference selections used by sales and marketing tracking. This prevents irrelevant internal options from appearing to users and keeps selections cleaner and less error-prone.
Original PR description
Various places mistakenly used `model.is_transient()` to filter the transient models, where the model is `ir.model` record itself, which always returns False since `ir.model` is a regular persistent model. As a result, transient models (wizards) were never filtered out and allowed into the `utm_reference` Reference field selection. This commit fixes it by using `self.env[model.model].is_transient()` to call `is_transient` on the actual model. Task-6458883 Forward-Port-Of: odoo/odoo#283824 Forward-Port-Of: odoo/odoo#282711
The sales flow now verifies whether a sales order requires a customer signature before allowing payment to proceed. This helps prevent orders from being paid or completed without required approval, improving compliance with business sales policies.
Original PR description
See also: - https://github.com/odoo/enterprise/pull/127041 Forward-Port-Of: odoo/odoo#283579 Forward-Port-Of: odoo/odoo#280403
Draft point-of-sale bills no longer show the QR code that lets customers create an invoice before the order is finalized. This prevents premature self-invoicing and avoids payment/order inconsistencies that could confuse staff and customers.
Original PR description
Step to reproduce: - install point_of_sale - have a pos, with `Early Receipt Printing` and `Self-service invoicing` enabled - open a pos ,select a product - from action button, click on "Bill"…
Step to reproduce: - install point_of_sale - have a pos, with `Early Receipt Printing` and `Self-service invoicing` enabled - open a pos ,select a product - from action button, click on "Bill" Observation: - We can see QR code in bill, using which a person can invoice itself, even when order is in draft state. - This cause a lot of anomoly like payment line not visible in pos order, even after successful payment Cause: - Prior to this version, `Qr` related data is shown only when `order.finalized` i.e. `status != draft` . https://github.com/odoo/odoo/blob/6f64942cbbbf2355f7328394a6d484f6828a80f1/addons/point_of_sale/static/src/app/components/receipt/order_receipt.xml#L76 - After commit https://github.com/odoo/odoo/commit/aeaca097ae39b293bff47458ae8af019585f9224 we removed this condition Fix: - The condition is brought back. opw-6427152 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284014 Forward-Port-Of: odoo/odoo#279382
This fix prevents errors when users filter sales orders using custom fields linked to project tasks. It makes sales and project reporting more reliable for teams that use related task information in their sales order views.
Original PR description
step to reproduce : 1. Create a related field on `sale.order`, for example: x_studio_production_stage = tasks_ids.stage_id.name 2. Use this field in a filter: [('x_studio_production_stage', 'ilike',…
step to reproduce :
1. Create a related field on `sale.order`, for example:
x_studio_production_stage = tasks_ids.stage_id.name
2. Use this field in a filter:
[('x_studio_production_stage', 'ilike', 'Dispatch')]
3. Applying the filter raises:
```python
Traceback (most recent call last):
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 2329, in _serve_db
return service_model.retrying(serve_func, env=self.env)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/service/model.py", line 188, in retrying
result = func()
^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 2384, in _serve_ir_http
response = self.dispatcher.dispatch(rule.endpoint, args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 2599, in dispatch
result = self.request.registry['ir.http']._dispatch(endpoint)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/addons/base/models/ir_http.py", line 353, in _dispatch
result = endpoint(**request.params)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/http.py", line 838, in route_wrapper
result = endpoint(self, *args, **params_ok)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/addons/web/controllers/dataset.py", line 32, in call_kw
return call_kw(request.env[model], method, args, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/service/model.py", line 97, in call_kw
result = method(recs, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/addons/web/models/models.py", line 67, in web_search_read
records = self.search_fetch(domain, specification.keys(), offset=offset, limit=limit, order=order)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 1408, in search_fetch
query = self._search(domain, offset=offset, limit=limit, order=order or self._order)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 5366, in _search
domain = domain.optimize_full(self)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 446, in optimize_full
return self._optimize(model, OptimizationLevel.FULL)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 460, in _optimize
previous, domain = domain, domain._optimize_step(model, next_level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 654, in _optimize_step
children = self._flatten(child._optimize(model, level) for child in self.children)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 609, in _flatten
for child in children:
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 654, in <genexpr>
children = self._flatten(child._optimize(model, level) for child in self.children)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 460, in _optimize
previous, domain = domain, domain._optimize_step(model, next_level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 654, in _optimize_step
children = self._flatten(child._optimize(model, level) for child in self.children)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 609, in _flatten
for child in children:
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 654, in <genexpr>
children = self._flatten(child._optimize(model, level) for child in self.children)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 460, in _optimize
previous, domain = domain, domain._optimize_step(model, next_level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 962, in _optimize_step
domain = self._optimize_field_search_method(model)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 1008, in _optimize_field_search_method
computed_domain = field.determine_domain(model, operator, value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/fields.py", line 1928, in determine_domain
return determine(self.search, records, operator, value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/fields.py", line 81, in determine
return needle(*args)
^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/addons/sale_project/models/sale_order.py", line 76, in _search_tasks_ids
query = self.env['project.task']._search(task_domain)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 5355, in _search
domain = Domain(domain)
^^^^^^^^^^^^^^
File "/home/odoo/src/odoo/19.0/odoo/orm/domains.py", line 259, in __new__
raise ValueError(f"Domain() invalid item in domain: {item!r}")
ValueError: Domain() invalid item in domain: ('id', 'any!', [('id', 'any!', <odoo.tools.query.Query object at 0x7aca184f4170>)])
```
Cause:
When searching on the related field, [_search_related()](https://github.com/odoo/odoo/blob/19.0/odoo/orm/fields.py#L768) converts the related path into an `any!` domain:
('tasks_ids', 'any!',
[('stage_id', 'any!', [('name', 'ilike', 'Dispatch')])]
)
During [Domain.optimize_full()](https://github.com/odoo/odoo/blob/19.0/odoo/orm/domains.py?utm_source=chatgpt.com#L436), [_optimize_field_search_method()](https://github.com/odoo/odoo/blob/19.0/odoo/orm/domains.py?utm_source=chatgpt.com#L1008) calls the field's search method, which invokes `_search_tasks_ids()` with `operator='any!'` and the related domain as `value`.
The existing [_search_tasks_ids()](https://github.com/odoo/odoo/blob/57c7c9938725d392a6f2cd6c89a861d2a8385c44/addons/sale_project/models/sale_order.py#L76) expects a normal search value and therefore generates an invalid nested domain.
Fix :
`_search_tasks_ids()` to directly pass the domain to `project.task._search()` when the operator is `any` or `any!`.
upg - 4584778
opw - 6475804
[here]: https://github.com/odoo/odoo/blob/19.0/odoo/orm/fields.py#L768
[here]: https://github.com/odoo/odoo/blob/19.0/odoo/orm/models.py#L5366
[here]: https://github.com/odoo/odoo/blob/19.0/odoo/orm/domains.py?utm_source=chatgpt.com#L436
[here]: https://github.com/odoo/odoo/blob/57c7c9938725d392a6f2cd6c89a861d2a8385c44/addons/sale_project/models/sale_order.py#L76
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#284101The accounting dashboard now shows the full invoice or bill amount for documents marked "To Check," instead of only the remaining unpaid balance. This avoids understating the value of documents that still need review after partial payments.
Original PR description
Currently, the "To Check" links on the dashboard display the residual amount of invoices and bills. Since the entire document needs to be checked regardless of partial payments, showing the remaining balance is misleading. This commit updates the `selects` list in `_get_to_check_payment_query` to use `amount_total` instead of `amount_residual`, ensuring the dashboard reflects the full value of the documents. Task-6478415 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284171
Customers who complete self-order purchases now receive receipt emails with the requested receipt image attached. This fixes missing receipt attachments for paid orders, improving proof-of-purchase delivery and customer communication.
Original PR description
Before this commit: ======================== * Receipt emails were sent without attachments for both paid and draft orders. * `fullTicketImage` and `basicTicketImage` were hardcoded to `false`. * As a result, paid orders were also sent without a receipt attachment. After this commit: ====================== * Receipt emails for paid orders now include the generated receipt image. * `fullTicketImage` and `basicTicketImage` are correctly handled to generate and attach the requested receipt image. Task-5353350 Forward-Port-Of: odoo/odoo#283947 Forward-Port-Of: odoo/odoo#237688
Employees who dismiss the attendance location warning will now see the pop-up close as expected. This prevents confusion when location access is blocked and users choose not to continue with check-in or check-out.
Original PR description
Steps to reproduce: -------------------------------------------- 1. Install Attendance module. 2. Enable `Device & Location Tracking` & `Attendance from Backend` in settings. 3. Block location access…
Steps to reproduce: -------------------------------------------- 1. Install Attendance module. 2. Enable `Device & Location Tracking` & `Attendance from Backend` in settings. 3. Block location access from the browser for this site (Site settings) 4. Try to checkIn/checkOut from the Dot in the systray 5. We'll have one confirmation pop-up asking to Proceed Anyway OR Discard Observation: -------------------------------------------- On clicking the discard button, Nothing happens. Issue: -------------------------------------------- In `confirmChecking()`, the `cancel` callback was defined as an arrow function using an expression body. In JavaScript, an assignment expression returns the assigned value. Since `this._attendanceInProgress` is set to `false`, the callback implicitly returns `false`. `ConfirmationDialog.execButton()` treats a `false` return value as a signal to keep the dialog open (used intentionally to block closing on validation failure) This caused the dialog to never call `this.props.close()`, leaving it permanently open when Discard was clicked. https://github.com/odoo/odoo/blob/5e84fdd99e34836a15cadc4fdf4b6bc449727e58/addons/web/static/src/core/confirmation_dialog/confirmation_dialog.js#L75-L89 Solution: -------------------------------------------- Change the `cancel` callback from an expression body to a block body, A block body arrow function returns `undefined` by default. This ensures `execButton` does not interpret the return value as a 'keep dialog open' signal, and correctly calls `this.props.close()` to dismiss the dialog. opw-6462439 Forward-Port-Of: odoo/odoo#284093 Forward-Port-Of: odoo/odoo#281702
This fixes signup link generation so the required signup purpose is always provided when creating access tokens. It helps prevent invite or portal access flows from failing when users need to sign up or access shared project content.
Original PR description
A `signup_type` is required to generate a token. Task-6452339 Forward-Port-Of: odoo/odoo#283417 Forward-Port-Of: odoo/odoo#280891
Deleting a draft invoice for timesheet-based services no longer changes which sales order line the timesheet hours belong to. This prevents sold hours from disappearing from the original order or being moved to another order when an invoice is removed and recreated.
Original PR description
Deleting a draft customer invoice linked to timesheets resets their timesheet_invoice_id so the hours become invoiceable again. This write also marks the timesheets' so_line for recompute, and the…
Deleting a draft customer invoice linked to timesheets resets their timesheet_invoice_id so the hours become invoiceable again. This write also marks the timesheets' so_line for recompute, and the re-derivation runs while the lines are no longer protected by the invoice link. When the task or project no longer resolves to a sale order item (e.g. it was unlinked after invoicing), the timesheets lose their sale order item or get reassigned to another one, so the delivered hours silently disappear from the original order line. Protect so_line during the write and drop the pending recompute: deleting an invoice must only make the hours invoiceable again, not change their allocation. Steps to reproduce: - Install Sales and Timesheets - Create a service product with invoice policy "Based on Timesheets" and "Create a task in a new project" - Create and confirm a sale order with this product - Log a timesheet on the generated task - Create the invoice (keep it in draft) - Remove the Sales Order Item from the task and from the project settings (or point them to a sale order item of another order) - Delete the draft invoice - Open the timesheet: its Sales Order Item is emptied (or replaced by the other order's item, whose delivered quantity now includes the hours sold on the original order), and the original line's delivered quantity is reset --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283831 Forward-Port-Of: odoo/odoo#279552
API documentation pages now display bullet points and lists with proper styling. This makes generated documentation easier to read and helps users understand reference information more clearly.
Original PR description
Bullet points and lists coming from the generated html by docutils were not properly styled. This commit fixes those cases. task-6484990 Forward-Port-Of: odoo/odoo#283835
This fixes invoice tax calculations when one tax increases the base amount used by a following tax on the same line. Businesses get more accurate tax breakdowns and totals in accounting documents, reducing reporting and reconciliation errors.
Original PR description
**Steps to reproduce:** - Create a tax that affects the base of the subsequent ones - Create an invoice with this tax and another one on the same line **Issue:** In "_aggregate_base_line_tax_details", the tax amount from the first tax should be included in the following values of the second tax: - raw_total_excluded - raw_total_excluded_currency - target_total_excluded - target_total_excluded_currency - total_excluded - total_excluded_currency But it is not. opw-6235909 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284066 Forward-Port-Of: odoo/odoo#279335
This change updates internal subscription-related tests so they stay aligned with recent changes in the related Odoo codebase. It helps maintain confidence that subscription flows continue to work as expected, without changing customer-facing functionality.
Original PR description
See also: - https://github.com/odoo/odoo/pull/280403 Forward-Port-Of: odoo/enterprise#128610 Forward-Port-Of: odoo/enterprise#127041
This fixes an issue where social media users saw an access error when liking a stream post. Likes are now processed safely in the background, improving the experience for users managing social streams.
Original PR description
Bug === When a social user like a stream post, an access error is raised because he has no write access on it. Task-6425391 Forward-Port-Of: odoo/enterprise#128993 Forward-Port-Of: odoo/enterprise#125973
The timesheet timer now excludes archived projects from its project dropdown, even when those projects were used in past timesheets. This prevents users from accidentally selecting inactive projects and keeps time tracking choices aligned with current project availability.
Original PR description
Steps to reproduce: ---------------------------------- 1. Install the `timesheet_grid` module. 2. Create a project and add any timesheet to it. 3. Archive the project. 5. From the systray timer,…
Steps to reproduce:
----------------------------------
1. Install the `timesheet_grid` module.
2. Create a project and add any timesheet to it.
3. Archive the project.
5. From the systray timer, click on the Project field.
Observation:
----------------------------------
The archived project is visible in the dropdown.
Issue:
----------------------------------
In Odoo, standard search views and `name_search` calls on `project.project` automatically respect `active_test=True`. When you open the timer, the frontend passes `{'timesheet_timer_search': True}` in the context to `name_search` with an empty query string. `name_search` overrides standard searching to retrieve recently used projects first by querying `account.analytic.line` via `_get_recently_used_records ('project_id', ...)`. `account.analytic.line` stores past timesheet logs. Even after a project is archived, historical timesheet records for that project still exist in `account.analytic.line`. Because `_get_recently_used_records` runs a `_read_group` query on `account.analytic.line` (which has no active field of its own), it fetched the `project_id` from historical timesheet entries without checking if the referenced project was active.
Solution:
----------------------------------
In `name_search`, explicitly append `[('active', '=', True)]` to the `project_domain` used when querying `_get_recently_used_records`. Standard form/list views using `_domain_project_id` already benefit from Odoo's default ORM `active_test=True` mechanism during standard `project.project` searches.
Note:
----------------------------------
Another solution was to add `active = true` in `getTimesheetTimerFieldInfo` https://github.com/odoo/enterprise/blob/22eb84cdc94ba334d42bad32fb491d35c8147c94/timesheet_grid/static/src/services/static_timesheet_timer_service.js#L322-L328
Fixing it in Python ensures that any call passing `timesheet_timer_search` in context (e.g. mobile widgets, custom RPCs, or python wizards) will benefit from the fix, rather than only patching a single OWL JS service.
opw-6445528
Forward-Port-Of: odoo/enterprise#127374A payroll-related test now uses the correct wage value depending on how employee pay is stored. This helps prevent false test failures and keeps pay gap reporting checks reliable across payroll configurations.
Original PR description
Without `hr_payroll`, the contract wage is stored in `wage`. With `hr_payroll`, hourly employees use `hourly_wage` instead. This commit uses `_get_contract_wage_field()` so the test sets the correct field in both cases. [error-237750](https://runbot.odoo.com/odoo/error/237750) Forward-Port-Of: odoo/enterprise#127398
This fix ensures timer and timesheet screens react correctly after an underlying framework change. Users should see active timers and timesheet status restored reliably instead of intervals refreshing unnecessarily or active timesheets being missed.
Original PR description
OWL3's useEffect takes one argument, so the OWL2 deps callback is dropped: timer_start_field re-arms its interval on every render instead of on a timer_start it compares by value, and timesheet_systray never binds its `loaded` parameter, so it never restores the active timesheet. Came in with odoo/enterprise#128151 and odoo/enterprise#125368. Effects kept: they arm an interval and call into the timer service, not a derivation, so useOnChange restores both declared dependency lists verbatim. see https://odoo.github.io/owl/documentation/v3/owl/reference/hooks.html#useeffect community: https://github.com/odoo/odoo/pull/283883 Forward-Port-Of: odoo/enterprise#128751
This update prevents an error during accounting reconciliation when users work with a parent company and branch company at the same time. It ensures the currency conversion uses the correct company context, allowing journal items across selected companies to be reconciled smoothly.
Original PR description
When having multiple companies selected at the same time, _get_conversion_rate returns: File "/data/build/odoo/odoo/orm/fields_misc.py", line 114, in get raise ValueError("Expected singleton: %s" %…
When having multiple companies selected at the same time, _get_conversion_rate returns:
File "/data/build/odoo/odoo/orm/fields_misc.py", line 114, in get
raise ValueError("Expected singleton: %s" % record)
1 - Create a new company with currency EUR.
2 - Create a branch company underneath the main company.
3 - In Accounting, install fiscal localization, e.g. Belgian Companies on the company configuration settings.
4 - Select an account like 600000 Raw Materials, and enable Allow Reconciliation on this account. The exact account isn't important, only that we can make credits / debits to it to be reconciled.
5 - With only the top level company selected, make a debit of 100 USD, e.g. Vendor Bill, set in currency USD to the account 600000.
6 - Now with only the branch level company selected, make a credit of 100EUR, e.g. Customers Invoices, set in currency EUR to the same account with an amount equal to the credit in step 5. (if 1USD == 1EUR, 1-1), so that there is no residual amount, i.e. credit == debit.
7 - Now select both the top level company and the sub branch company in the company context.
8 - In Journal Items, reconcile the unreconciled journal items for the Account 600000.
With this commit we select the first company of the aml instead of every companies on the amls.
opw-6290703
Forward-Port-Of: odoo/enterprise#123774This fixes an issue where a user who was explicitly added as an editor on a Documents folder could not update access rights for internal users. It ensures folder editors can manage the sharing permissions they are allowed to edit, reducing blocked collaboration workflows.
Original PR description
1. Create a non-company root folder 2. Edit rights as follows: * add Marc Demo as editor member * access for internal users and link to None 3. As Marc Demo, try updating Internal users access to "editor" ⮕ You can't. Task-6410610 Forward-Port-Of: odoo/enterprise#129054 Forward-Port-Of: odoo/enterprise#125191
Shopee shops can now be reauthorized with a different account, and Odoo will correctly update the shop connection. This prevents errors when businesses change API credentials or reconnect a shop under another Shopee account.
Original PR description
Context: when a user re-authenticate a shop, they might use different shopee.account (API key). Currently Odoo will not change the shopee.account when they re-auth with another shopee.account. Enable a shopee.shop switches to another shopee.account when we run `create_or_update_shop` function. Forward-Port-Of: odoo/enterprise#129143 Forward-Port-Of: odoo/enterprise#92446
Corrects a rounding mismatch in Peruvian electronic invoice XML that could cause invoices, especially down payment invoices, to be rejected by the tax validation service. This helps ensure taxable amounts match line totals and improves successful submission of Peruvian UBL 2.1 documents.
Original PR description
**Steps to reproduce:** - Install Accounting, Sales and l10n_pe_edi - Switch to a Peruvian company (e.g. PE Company) - Create a SO: * Customer: [a Peruvian customer] * Order Lines: | Product |…
**Steps to reproduce:**
- Install Accounting, Sales and l10n_pe_edi
- Switch to a Peruvian company (e.g. PE Company)
- Create a SO:
* Customer: [a Peruvian customer]
* Order Lines:
| Product | Quantity | Unit Price | Taxes |
| ------- | -------- | ---------- | ------- |
| any | 3.00 | 123.50 | VAT 18% |
| any | 2.00 | 27.544216 | 0% Ina |
| any | 1.00 | 43.490867 | 0% Exo |
- Confirm the SO
- Create a 40% down payment
- Confirm the down payment
- Process it to sent it to Peru UBL 2.1
**Issue:**
The following error message is returned by the OSE:
`3272|La base imponible a nivel de línea difiere de lainformación consignada en el comprobante - Detalle: xxx.xxx.xxx ticket : 20260000000000221633458 error: Error en la Linea Nro. :1. : 3272 (nodo: "cac:TaxSubtotal/cbc:TaxableAmount" valor: "148.20")`
**Cause:**
In the XML, one line has 148.19 for "cbc:LineExtensionAmount", but 148.20 for "cac:TaxSubtotal/cbc:TaxableAmount".
The issue is coming from the fact that "base_amount_currency" is used instead of "total_excluded_currency" for the computation of "cac:TaxSubtotal/cbc:TaxableAmount".
**Issue 2:**
When a tax is impacting the base amount of a following tax, its tax amount is not taken into account in "total_excluded_currency".
opw-6235909
Forward-Port-Of: odoo/enterprise#128886
Forward-Port-Of: odoo/enterprise#122310Users can now decline a signature request with a reason without triggering an error screen. The signing workflow now closes the decline window before showing the confirmation message, making the process smoother and more reliable.
Original PR description
Version: 19.4 Steps to reproduce: - Create a sign request with a signature and send it to a user - Decline the document as administrator with a reason Issue: Opening the thank you dialog before closing the decline dialog caused both actions to be processed together. This made the thank you dialog get built twice and both attempts were already destroyed before orm.call, raising a traceback. Fix: Close the decline signature dialog first, then open the thank you dialog. Task id - 6471923 Forward-Port-Of: odoo/enterprise#128345
This fixes seven mislabeled entries in the Mexican chart of accounts so their names match the official SAT catalogue. The correction helps ensure electronic accounting exports show the proper account descriptions, reducing confusion and compliance risk for Mexican companies.
Original PR description
Seven entries of the Mexican chart of accounts template carry a name belonging to a **different** group, copied from a neighbouring entry. Each record's XML ID still states the intended name, which…
Seven entries of the Mexican chart of accounts template carry a name belonging
to a **different** group, copied from a neighbouring entry. Each record's XML ID
still states the intended name, which is what this restores.
| Code | Field | Before | After |
|---|---|---|---|
| `6` | `name@es` | Gastos generales | Gastos |
| `252.07` | `name@es` | `account_subgroup_hipotecas_por_pagar_a_largo_plazo_nacional` | Hipotecas por pagar a largo plazo nacional |
| `602` | `name`, `name@es` | Cost of sales / Costo de venta | Selling expenses / Gastos de venta |
| `613` | `name@es` | Amortización contable | Depreciación contable |
| `614` | `name` | Accounting depreciation | Accounting amortisation |
| `701.06` | `name`, `name@es` | Interest on foreign bank charges / Intereses a cargo bancario extranjero | Interest payable by national natural persons / Intereses a cargo de personas físicas nacional |
| `702` | `name@es` | Utilidad cambiaria | Productos financieros |
### Why it is not cosmetic
The electronic accounting Chart of Accounts XML takes the `Desc` attribute of
every `<Ctas>` element from the *account group name* — `cfdicoa.xml`
(`t-att-Desc="account.get('name')"`), fed by `trial_balance.py`
`_l10n_mx_get_coa_values()`. Any `es_*` database therefore declares:
```xml
<catalogocuentas:Ctas CodAgrup="702" NumCta="702" Desc="Utilidad cambiaria" Nivel="1" Natur="A"/>
```
whereas the SAT catalogue (Anexo 24) publishes `702` as *Productos financieros*,
with `702.01 Utilidad cambiaria` … `702.10 Otros productos financieros` beneath
it. `CodAgrup` comes from `code_prefix_start` and stays correct, so the file
still validates against the XSD, but the declared description does not match the
official nomenclature. Trial Balance and Pólizas are unaffected — neither
exports group names.
### Evidence
- `252.07` contains its own XML ID as the Spanish name.
- `602` duplicates `501.01`, yet its children are `Sueldos y Salarios`,
`Compensaciones`, `Tiempos extras`.
- `613` and `614` are swapped in one language each: `613`'s children are
depreciations, `614`'s are amortisations.
- `701.06` duplicates `701.05` in both languages; the correct name is symmetric
to `701.07` and to `702.06`.
- `6` is the only single-digit root group whose Spanish name does not match its
XML ID (`account_group_gastos`).
### Notes
Introduced in d782b8b92557; correct in 15.0, where the names lived in
`account.account.tag.csv`. Still present in 18.0, 19.0 and master, hence
targeting 17.0. Template data only — existing databases are unaffected until the
chart is (re)installed, and renaming a group moves no balance.
Forward-Port-Of: odoo/odoo#277426
Forward-Port-Of: odoo/odoo#278328
Forward-Port-Of: odoo/odoo#277891The Belgian salary configurator now handles cases where no company bike is available. This prevents an error when users select the company bike option and keeps the offer setup process running smoothly.
Original PR description
Steps to Reproduce: - install l10n_be_hr_contract_salary module. - make sure that there is no model with vehicle type bike in fleet. - create an offer in recruitment. - open salary configurator. - click on company bike checkbox. Issue: - traceback occurs when enabling the company bike option without a configured bike. Reason: - the company bike depreciated cost value is empty when no bike is available, but the code tries to split it into bike options and vehicle ID resulting in a traceback. Solution: - Use the condition to check if the company bike depreciated cost is available before spliting the value. - Set the depreciated cost to 0 when no bike is selected. task-6468987 Forward-Port-Of: odoo/enterprise#127898
Cancelling and resetting a payslip now correctly returns related time off to be included in payroll calculations. This prevents approved leave from being missed when payroll teams revise payslips for the same period.
Original PR description
How to reproduce: - Create a payslip for an employee and validate it - Create a new time off for said employee during the same period as the payslip and validate it - Go back to the payslip, cancel it and reset it to draft - The new time off is not included in the payslip Reason: When a payslip is cancelled, if there are time off during the same period as the payslip, their state is not reset to "to compute in next payslip" and instead stays in "to defer to next payslip", causing the issue How it was fixed: Now, when a payslip is cancelled, the new function "return_time_off_to_normal" will catch all leaves that are in the same time frame as the payslip to reset their state to "to compute in next payslip". Task ID: 6431576 Forward-Port-Of: odoo/enterprise#128958 Forward-Port-Of: odoo/enterprise#126868
Point of Sale data loading has been redesigned so stores can sync only the records that changed instead of reloading everything each time. This should improve startup speed and scalability, especially for businesses with large product, customer, loyalty, restaurant, self-order, or localization datasets.
Original PR description
pos*: l10n_ar_pos, l10n_es_edi_verifactu_pos, l10n_in_pos, l10n_pe_pos, l10n_sa_edi_pos, point_of_sale, pos_discount, pos_event, pos_glory_cash, pos_hr, pos_loyalty, pos_online_payment_self_order,…
pos*: l10n_ar_pos, l10n_es_edi_verifactu_pos, l10n_in_pos, l10n_pe_pos,
l10n_sa_edi_pos, point_of_sale, pos_discount, pos_event, pos_glory_cash,
pos_hr, pos_loyalty, pos_online_payment_self_order, pos_qfpay,
pos_restaurant, pos_sale, pos_self_order, pos_self_order_event,
pos_self_order_qfpay
Rework the POS data loading mechanism to improve scalability and support
incremental syncing based on `write_date` timestamps.
- Move field/relation metadata computation into `PosLoadMixin` via new
`_load_data_relations()` and `_load_pos_data_domain_and_dependencies()`
methods, removing the centralized logic from `pos_session.py`
- Refactor `load_data()` to accept a `local_data` dict (model → {id:
write_date}) so the frontend can request only records newer than what
is already cached in IndexedDB
- Add `_load_pos_metadata()` and `_read_pos_data_from_metadata()` to
clearly separate metadata collection from record reading
- Add `load_pos_data_force_loading()` hook for models that always require
a full reload regardless of local cache
- Automatically append `write_date` to loaded fields for cache comparison
- Add template as record in the indexedDB cache
- Refactor `data_service.js`: extract `syncInitialData`, `cleanLocalData`,
`cleanOldModels`, and `handleLoadingDataError`; handle stale model
cleanup in IndexedDB when modules are uninstalled
- Compress multiple calls (load_data, load_data_params,
filter_local_data, ..) into a single load_data call.
- Change some loading method to use load_data instead of a custom method
for each model.
task-id: 4982785
Enterprise PR: https://github.com/odoo/enterprise/pull/93803
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-prEnterprise Point of Sale modules were aligned with Odoo's newer data loading approach, making setup and screen data retrieval more consistent across countries and POS features. This mainly supports reliability and maintainability, with added tests for preparation screen loading to reduce regression risk.
Original PR description
Align all enterprise POS modules with the refactored data loading mechanism introduced in community. pos_enterprise: - Add `PosLoadMixin` override with `_load_prep_data_domain_and_dependencies()`,…
Align all enterprise POS modules with the refactored data loading mechanism introduced in community. pos_enterprise: - Add `PosLoadMixin` override with `_load_prep_data_domain_and_dependencies()`, `_load_prep_metadata()`, and `_read_prep_data_from_metadata()` to mirror the new metadata-based loading pattern for preparation display data - Refactor `pos_prep_display.py`: replace `load_data_params()` and `load_preparation_data()` with `_load_metadata()` using the new mixin; remove the centralized `_load_pos_data_relations()` call on `pos.session` - Update `_load_pos_data_domain()` signature to remove the `config` parameter and resolve it from `data['pos.config']` directly - Adapt `data_service.js` patch: merge `loadFieldsAndRelations()` into `loadInitialData()` using `getFieldsAndRelations()`/`initFieldsAndRelations()`; simplify `initData()` pos_settle_due: - Remove `ir_ui_view.py` override that manually exposed two view IDs — these are now handled by the generic `ir.ui.view` loading in community - Inject the two view IDs (`customer_due_pos_order_list_view`, `due_account_move_list_view`) directly into `pos.config` read data via `_load_pos_data_read()` instead l10n_br_edi_pos, l10n_cl_edi_pos, l10n_ec_edi_pos, pos_appointment, pos_blackbox_be, pos_enterprise, pos_event_iot, pos_iot, pos_planning, pos_settle_due, pos_self_order_restaurant_appointment: - Update `_load_pos_data_domain()` signatures to drop the `config` argument in line with the new mixin interface task-id: 4982785 Community PR: https://github.com/odoo/odoo/pull/225341
This update replaces a deprecated internal frontend mechanism with the newer standard approach across several Odoo apps. It helps keep the interface code easier to maintain and reduces upgrade risk, without changing visible business workflows.
Original PR description
- community: https://github.com/odoo/odoo/pull/281946 See commit messages for details. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
The mail system now uses one internal change-tracking approach instead of two overlapping mechanisms. This reduces unnecessary presence update messages during user activity and makes the messaging code easier to maintain without changing expected user-facing behavior.
Original PR description
Before this commit, a record reacts to what it reads in two ways: `Record.onChange`, and a raw owl effect. Such an effect only sees a relation change when it reads the record as its proxy, which only `static new` has, so three models override `static new` for nothing else. There is no need for the raw effect, as `Record.onChange` runs the same body on a change and compares the values its dependencies return, so a bus channel that comes back equal keeps its subscription. This commit registers the four raw effects as onChange, from `setup`. An onChange resolves the proxy when its two functions run instead of when they are registered, which is what a `setup` registration needs. `effectWithCleanup` has no caller left, so it goes. Note that the self user or guest sends one `update_presence` when its status changes, where the effect sent one on every click and keystroke while the server still reported away or offline.
Several internal text field length limits were removed across accounting, documents, payroll, expense, and localization modules. This reduces avoidable data-entry constraints and keeps enterprise modules aligned with the related core platform cleanup, with minimal expected business impact.
Original PR description
https://github.com/odoo/odoo/pull/284290
The Point of Sale number entry logic has been reorganized into a plugin, making it easier to maintain and extend across related POS features. This is an internal cleanup with little expected day-to-day impact for users, but it should support more reliable future updates.
Original PR description
Convert the number buffer service into a plugin