Wednesday, August 26, 2026
69 changes · saas-19.3
Security fixes and vulnerability patches
This update prevents database API keys from being read in clear text when creating a database from a template. It closes a security gap while keeping database creation working as expected.
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
The Indian localization stock demo data now creates a warehouse for the Indian demo company. This makes demonstrations and testing involving e-waybills easier because users no longer need to manually set up a warehouse first.
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
Resolved issues and error corrections
The overtime rules screen now shows the related employee versions button only to HR managers. This prevents HR officers from seeing an access error caused by data behind the button that requires manager-level permissions, 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
Product pages now include the product internal reference as SKU information in their structured data when it is available. This helps search engines and other services better identify products, improving consistency in product listings and integrations.
Original PR description
Include default_code as the Schema.org sku property in the product structured data when it is set. @Tecnativa TT64220 **Description of the issue/feature this PR addresses:** Product structured data does not expose the product internal reference as a SKU. **Current behavior before PR:** The sku property is absent even when default_code is set. **Desired behavior after PR is merged:** The product structured data includes default_code as the Schema.org sku property when available. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284342 Forward-Port-Of: odoo/odoo#282911
Bank journals now automatically receive the right outstanding payment accounts for Moroccan companies, and the shared account setup is also reused for India. This helps payments be reflected correctly in cash-basis tax reporting even when no bank synchronization or reconciliation transaction is 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
Belgian point-of-sale systems now save errors from fiscal device checks and signatures in the backend, instead of relying on cashier browser logs. This makes it easier for support teams to diagnose blackbox problems with full context while automatically clearing old entries after 30 days.
Original PR description
The FDM is only reachable from the browser, so an error occurring during a signature left no trace in the backend: diagnosing a blackbox issue on a client database meant asking the cashier for their console logs. Errors caught while signing or pinging the FDM are now stored as `ir.logging` entries, holding the whole context of the error, the data sent to the FDM and its answer. Logging is asynchronous and entries are dropped after 30 days when a session is closed Forward-port of PR: https://github.com/odoo/enterprise/pull/129016
Fixed an issue where section or note lines from quotation templates could be linked to active planning sessions as if they were real service lines. This prevents errors in field service planning workflows when quotations include organizational text lines.
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#125056
Pricing rules created from a product variant now remain linked to that specific variant instead of being applied to the broader product template. This prevents unintended pricing changes across multiple variants and keeps price configuration accurate.
Original PR description
Issue: --- When you apply pricing on product variant form, pricing is instead applied on product template. Steps to reproduce: 1- Open a product variant. 2- From prices tab, add a pricelist rule. Save the variant. 3- Re-open pricelist rule. As you see, the variant is not set. Cause & Fix: --- This is because `applied_on` is changed to `1_product` when `display_applied_on` is set to `1_product`. However, `display_applied_on` is also set to `1_product` when item is created from variant. We can check that case using `default_product_id`. opw-6421193 Forward-Port-Of: odoo/odoo#282376 Forward-Port-Of: odoo/odoo#280303
This fix makes an internal accounting reports test run consistently by ensuring journal filter groups are checked in a stable order. It reduces random test failures, helping updates move through quality checks more reliably without changing customer-facing behavior.
Original PR description
The journal groups created in the test share the default sequence (10), so _get_journal_groups' order='sequence' has no tiebreaker and it may be returned in any order, making the assertions randomly fail. Assign an incrementing sequence per created group so the order is deterministic. runbot-941251 Forward-Port-Of: odoo/enterprise#125145
Belgian payroll now applies the home-work transport withholding tax exemption only to transportation amounts that are actually taxable on the payslip. This prevents employees from being overtaxed on fuel cards, avoids double benefits on private car reimbursements, and correctly taxes bike allowances above the legal exempt rate.
Original PR description
The withholding tax exemption for the employer intervention in home-work transportation costs (500 EUR/year - 41.70 EUR/month for income year 2026) was estimated from contract fields (car_atn,…
The withholding tax exemption for the employer intervention in home-work transportation costs (500 EUR/year - 41.70 EUR/month for income year 2026) was estimated from contract fields (car_atn, private_car_employee_kilometer) instead of the amounts actually taxed on the payslip. As a consequence: - The fuel card for daily commute was fully taxed: the employee never received the exemption they are legally entitled to. - The private car reimbursement was paid net, never added to the taxable base, yet it still granted the exemption on the yearly taxable: a double benefit. - A bike allowance paid above the exempt rate per km (0.37 EUR/km in 2026) was never taxed. The exemption is now computed from a new TRANSPORTATION_BASE salary rule category accumulating the taxable transportation amounts of the month: - ATN.CAR and FUEL_CARD_COMMUTE (already taxed) are tagged with the category. - New hidden rules CAR.PRIV.TAX and CYCLE.TAX fictively add the private car reimbursement and the above-rate bike excess to the withholding base, after ONSS and before the gross totals, while the reimbursements themselves remain paid net. - TRANSPORT_TAX_DED becomes -min(yearly taxable, yearly threshold, 12 x monthly transportation base), including the transport lines of already validated payslips of the same month so that non-periodic (regularisation) payslips preserve the monthly totals. The termination fees variant uses the category without x12, its base being annual. The obsolete helper _get_be_withholding_taxes_transport_deduction is removed, and the affected payslip validation expectations are updated to the corrected amounts. task-6388103
Gantt charts using a weekly view now place tasks in the correct week based on the user's locale, including regions where weeks start on Sunday. This prevents extra empty columns and keeps schedules aligned without changing existing day, month, or year views.
Original PR description
In Gantt views, for a given focus date, the appropriate time interval is displayed by finding its start and end date, based on the scale. For example, if I focus on 18 may 2026 with a month scale,…
In Gantt views, for a given focus date, the appropriate time interval is displayed by finding its start and end date, based on the scale. For example, if I focus on 18 may 2026 with a month scale, then the whole month of may is displayed. The behaviour was as expected in standard code, because all localisations agree on the beginning of the available scales (day, month, year). In custom code, however, some customer requires to see the gantt charts with a weekly scale. The differences in start of the week based on the localisations and the inconsistencies of use of localStartOf breaks the view. For example, if the localization has the start of the week on a sunday, and a task on the first column starts on a sunday as well, it will get assigned to column before (because it considers sunday as the last day of the previous week). The column before the first column does not exist, so one empty column is created to put the task in it. This commit fixes these inconsistencies so that GanttRenderer behaves as expected with weekly scales, without changing the standard behaviour. Tests are written to check both that the task is assigned to the proper localized week (starting on Sunday) and column (1, not 0). Forward-Port-Of: odoo/enterprise#118625
This fixes an issue where sales that were delivered, returned, credited, and then delivered again could record an inflated cost of goods sold. Credit notes are now included in the calculation so earlier invoices and refunds offset correctly, improving inventory and margin accuracy.
Original PR description
**Steps to reproduce:** - create a storable product with a positive quantity a cost of 10 and average perpetual category - confirm a SO for 1 quantity, validate delivery - confirm invoice for 1 (COGS…
**Steps to reproduce:** - create a storable product with a positive quantity a cost of 10 and average perpetual category - confirm a SO for 1 quantity, validate delivery - confirm invoice for 1 (COGS should be 10) - return the delivery and validate - create a credit note from the invoice for 1 and confirm (COGS should be 10) - return the return and validate - change the standard price to 100 - create an invoice from the SO for 1 and confirm **Current behavior:** cogs are 190 **Expected behavior:** cogs should be 100 **Cause of the issue:** _get_posted_cogs_value doesn't take into account the credit notes (only the account moves with type 'out_invoice' are taken into account in the sum) https://github.com/odoo/odoo/blob/0824dc24665de6bfa805d540e756cdcb006edba6/addons/sale_stock/models/account_move.py#L185-L186 So in our case the first invoice and the credit note don't cancel out each other. The same goes for _get_cogs_qty (which returns the total cogs past + current), in the past cogs it doesn't take into account the quantities of the credit note. https://github.com/odoo/odoo/blob/0824dc24665de6bfa805d540e756cdcb006edba6/addons/sale_stock/models/account_move.py#L172-L174 So the quantity of the first invoice and the one of the credit note don't cancel out each other. As a result, the return value from _get_cogs_value() for the second invoice is : price unit = 100 returned by _get_cogs_price_unit() https://github.com/odoo/odoo/blob/0824dc24665de6bfa805d540e756cdcb006edba6/addons/stock_account/models/account_move_line.py#L68 which returned the standard price because the product has an average cost method https://github.com/odoo/odoo/blob/0824dc24665de6bfa805d540e756cdcb006edba6/addons/stock_account/models/stock_move.py#L275-L280 cogs_qty = 2 (instead of 1 if credit was taken into account as -1 in the sum) self._get_posted_cogs_value() = 10 (instead of 0 if credit note cogs were taken into account in the sum as -10) return value = (100 * 2 -10)/1 = 190 https://github.com/odoo/odoo/blob/0824dc24665de6bfa805d540e756cdcb006edba6/addons/stock_account/models/account_move_line.py#L75 **fix:** if we take into account the credit note the return value will be : (100 * 1 - 0)/1 = 100 the mechanism of the already posted cogs value is there for cases where we only delivered a part of the quantity and then delivered the rest, but in the case where we delivered and then returned (with credit notes) it shouldn't have an impact. Thus the idea to include the credit note so that it can cancel out the first invoice opw-6426111 Forward-Port-Of: odoo/odoo#282893
The update prevents an error that could occur when sales reporting or tracking data references models that are temporarily out of sync. This helps keep related business workflows stable instead of failing due to missing internal registry entries.
Original PR description
Following 6dedae804748, in case `ir.model` models are out-of-sync with the available models in the registry, trying to compute the target selection model will result in a crash (`KeyError`). This commit ensure the target model is available in the registry to avoid that crash. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284161
Self-service orders with no amount due now bypass the payment page, removing an unnecessary checkout step. This makes free or fully discounted orders faster and smoother for customers.
Original PR description
Before this commit: -------- - Self-orders with a total amount of zero are still redirected to the payment page, which was unnecessary. After this commit: -------- - The payment step is now skipped for zero-amount self-orders, providing a smoother checkout flow. task-5106938 Forward-Port-Of: odoo/odoo#283658 Forward-Port-Of: odoo/odoo#230218
Sale order PDF reports now keep section and combo rows aligned when users remove columns such as Taxes or Discount through Studio. This prevents confusing blank spaces in customer-facing documents and improves the reliability of customized sales reports.
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
This update corrects how Mail identifies extra attachments on messages so the system consistently treats them as attachment records. It helps ensure attachment lists behave reliably, especially when some images are embedded directly in a message body.
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#284445This fix ensures product standard costs are calculated using complete stock information, even when a user's warehouse access is limited by custom rules. It prevents inflated or incorrect product costs that could affect inventory valuation and financial reporting.
Original PR description
`qty_available` is by default not computed with sudo, while the `total_value` is. This means that in `_update_standard_price`, the customer configuration can have a negative impact on the…
`qty_available` is by default not computed with sudo, while the `total_value` is. This means that in `_update_standard_price`, the customer configuration can have a negative impact on the standard_price compute.
For example, if a user create a custom rule so that specific users have access to specific warehouses only, compute `total_value / qty_available` can actually mean `global_total_value / partial_qty_available`, which creates an aberrant standard price.
To fix this issue, the _update_standard_price must be done in sudo.
OPW-6243363
---
## Test result without fix
```
2026-06-17 12:24:50,810 47572 ERROR oes_test_19 odoo.addons.stock_account.tests.test_stockvaluation: FAIL: TestStockValuation.test_update_standard_price_with_limited_access_users
Traceback (most recent call last):
File "/home/odoo/Odoo/src/19.0/odoo/addons/stock_account/tests/test_stockvaluation.py", line 3624, in test_update_standard_price_with_limited_access_users
self.assertEqual(product.standard_price, 1.0)
AssertionError: 12.11111111111111 != 1.0
```
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#271229
Forward-Port-Of: odoo/odoo#270559The pickup location search no longer pre-fills an imprecise ZIP code based on GeoIP, reducing the chance of showing stores that are not actually nearby. The search prompt is clearer and the country selector is simplified when only one country is available, making pickup selection easier for shoppers.
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#284392This fixes how Belgian payroll handles departures with notice periods, so severance is allocated across the correct quarters. It also preserves valid manual departure date adjustments instead of overwriting them automatically.
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
Appointment invitation emails now generate public calendar links without permission errors. This prevents failed email rendering when attendees receive appointment invites, helping ensure customers can access their calendar details smoothly.
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
This fixes an import failure for Chilean electronic supplier invoices that include foreign-currency line details but omit an optional foreign-currency total. The system now uses the standard total when that optional value is missing, allowing affected invoices to be received by email without errors.
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
Fixes an error that could occur when marking several employee appraisals as done at once. Completion notifications and messages are now handled individually for each appraisal, so HR users can process batches without interruption.
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
The fiscal year validation now catches cases where a newly created period fully contains an existing fiscal year. This helps prevent conflicting accounting periods and reduces the risk of reporting or closing errors.
Original PR description
Before this commit: - The current constraint for overlap check allows if we define a new, larger fiscal year that completely swallows an existing smaller one (e.g., creating Aug 2025 - Nov 2026 when Sept 2025 - Oct 2026 already exists). After this commit: - The constrain domain was changed to consider the above missed case. no task Forward-Port-Of: odoo/enterprise#128942
Intrastat reports now exclude short-term rental orders and only include rentals lasting two years or more. This improves reporting accuracy and helps businesses avoid overstating qualifying cross-border rental activity.
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
This fix ensures French VAT return submissions do not include an express mention unless a real reason has been entered. It prevents submissions from failing when the reason field contains only spaces, improving reliability when sending tax returns to ASPOne.
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
This fixes an issue where rejected Saudi simplified invoices could fail when resubmitted to ZATCA because the QR code was missing during retry. The system now regenerates the QR code from the submission data, helping businesses successfully retry rejected invoices without changing final invoice visibility rules.
Original PR description
- When a simplified (B2C) invoice is rejected by ZATCA and subsequently retried, its state remains rejected. The QR code computation therefore returns an empty value for the rejected invoice, as the computation is primarily intended for the post-EDI state. During the retry, this results in a traceback when the QR code is applied to the XML. - Regenerate the QR code directly from the submission data when preparing a new B2C XML, instead of relying on the state-dependent QR code field. This preserves the existing QR visibility rules for the final invoice. task-6485555
The tour export process no longer adds an outdated URL field to generated JavaScript files. This keeps exported tour files aligned with the current format and avoids unnecessary data in generated assets.
Original PR description
The url was removed from all js file tours[1], but the exporter to JS still add it to the created JS file. This commit removes that part from the exporter. [1]: https://github.com/odoo/odoo/commit/53410a93685880b817d2b6b0ffeb7a28c254ac17
Copied, template-based, and recurring project tasks now preserve the correct dependency order between their sub-tasks. This prevents teams from getting misleading task sequences after reusing existing project work, reducing manual cleanup and planning mistakes.
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#284211 Forward-Port-Of: odoo/odoo#280893
Email notifications for tracked changes now correctly display the arrow and parentheses that show what changed. This makes change summaries in emails easier to read while preserving compatibility with older messages.
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
This fix separates checks for product loading limits from checks for product ordering in Point of Sale and stock-related Point of Sale tests. It helps ensure automated tests remain reliable when stock features are installed alongside Point of Sale, reducing false failures without changing user-facing behavior.
Original PR description
## Context: When all references to stock functionality were extracted from point_of_sale in this PR: #241368, we decoupled the query ordering logic from the rest of our product.template data-fetching…
## Context: When all references to stock functionality were extracted from point_of_sale in this PR: #241368, we decoupled the query ordering logic from the rest of our product.template data-fetching code: https://github.com/odoo/odoo/blob/1170bafbd7d9461d13ba1d6020d6f8487660763c/addons/point_of_sale/models/product_template.py#L190 This allowed us to completely override (not extend) the ordering of the loaded products in our new pos_stock module: https://github.com/odoo/odoo/blob/1170bafbd7d9461d13ba1d6020d6f8487660763c/addons/pos_stock/models/product_template.py#L42-L65 Notice the difference in point_of_sale: https://github.com/odoo/odoo/blob/1170bafbd7d9461d13ba1d6020d6f8487660763c/addons/point_of_sale/models/product_template.py#L403-L416 This was causing problems with our existing test for this feature in point_of_sale because we were explicitly examining the ordering of the products; obviously this ordering would be different when pos_stock is installed alongside point_of_sale. Thus, we must decouple our original test for this feature so that ordering can be examined independently in each module's tests. ## Now: We have one test in point_of_sale that just makes sure our limited products loading feature actually caps the number of products loaded. This test will work correctly whether or not pos_stock is installed. In addition, we have one more test in point_of_sale and pos_stock that explicitly examines the ordering of the products in these limiting conditions when both modules are installed. The point_of_sale test will be skipped when pos_stock is installed because the latter module defines conflicting ordering. runbot-243064
The editor now checks that selected cells belong to the table whose menu is being used before allowing merge or unmerge actions. This prevents users from accidentally changing a different table when editing documents with multiple tables.
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
This update corrects an internal automated test related to mail tracking template changes. It helps keep quality checks reliable so future updates can be validated with fewer false failures.
Original PR description
Task-6424104
Users can now delete multiple email templates at once without the system crashing. This fixes a problem in Field Service Planning where one protected customer rating template check incorrectly blocked batch 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
Uploading a refund from bank transactions now creates the correct vendor credit note instead of a customer credit note. This ensures refunds from vendors are recorded in the right journal and can be reconciled against the correct payable account.
Original PR description
**Steps to reproduce:** * Install **Accounting** module. * Go to **Accounting → Bank → Transactions** (bank reconciliation widget). * Open a transaction with a **positive** amount (e.g. a vendor…
**Steps to reproduce:** * Install **Accounting** module. * Go to **Accounting → Bank → Transactions** (bank reconciliation widget). * Open a transaction with a **positive** amount (e.g. a vendor sending money back). * Click the three-dot menu on the transaction line and choose **Upload a Refund**. * Upload any document (XML or PDF). **Observed behavior:** * A **Customer Credit Note** (`out_refund`) is created in a **sale** journal instead of a **Vendor Credit Note** (`in_refund`) in a **purchase** journal. * The wrong document type means the reconciliation fails to link the refund against the correct payable account. **Cause:** * In `create_document_from_attachment` (`account_bank_statement.py`), the JS widget sends `type='sale'` in context when the transaction amount is positive (JS: `amount > 0 ? "sale" : "purchase"`). * The original code mapped `type='sale'` → `default_move_type='out_refund'` (customer credit note) and searched for a `sale` journal — both wrong. * Uploading from a bank statement is always a **vendor-side** operation: negative amount = vendor bill (`in_invoice`), positive amount = vendor refund (`in_refund`). The `type` context key from JS reflects transaction direction, not the accounting document type. * Additionally, `in_refund` is a purchase document; Odoo's `_check_journal_move_type` constraint raises a `ValidationError` if a purchase document is created in a non-purchase journal, meaning the old code would crash at the ORM level for the refund path. **Fix:** * Map `type='sale'` → `default_move_type='in_refund'` (vendor credit note) instead of `out_refund`. * Always search for a **purchase** journal regardless of the `type` context value, since both `in_invoice` and `in_refund` are purchase-side documents. opw-6468779
Argentinian invoicing now allows users to create invoices for foreign customers even when export journals are unavailable or archived. Instead of stopping the workflow with an error, Odoo falls back to a standard Invoice B document so sales teams can continue billing.
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#282971
This fix prevents an occasional automated test failure in the German POS certification module by waiting for the table order to finish syncing before checking its badge. It improves test stability and reduces false alarms in validation runs, with no expected impact on end users.
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
The salary offer signing page now hides an unnecessary large placeholder when no benefits are available. It also removes duplicate “Review & Sign” and “Feedback” buttons, making the offer review experience clearer for candidates and employees.
Original PR description
This commit fixes the issue of showing a big placeholder in the signing offer page when there was no benefits available, and also fix the duplicated buttons of "Review & Sign" and "Feedback" on the same page. taskid-6486370
UPS return shipments now show the commercial invoice in the delivery chatter, matching outbound international shipments. US ZIP+4 postal codes are also cleaned before being sent to UPS, preventing avoidable shipment rejections.
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
This update fixes incorrect Hungarian tax configuration details used in accounting and electronic invoicing. It helps businesses using the Hungary localization apply taxes more accurately and reduces the risk of reporting or invoicing 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
Sales quotation and pro forma email templates now use separate full 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#283901 Forward-Port-Of: odoo/odoo#283229
UPS shipping rate checks now follow UPS documentation by allowing phone numbers from 1 to 15 digits instead of requiring at least 10. This prevents valid customers in countries with shorter phone numbers, such as Luxembourg, from being blocked when getting 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 export now preserves cents for Kz83 values instead of rounding them down to whole euros. This helps ensure tax XML submissions show accurate monetary amounts, such as 26.40 remaining 26.40.
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
Users can now open links in spreadsheet cell comments with a regular click, instead of needing Ctrl+click or Cmd+click. This restores expected link behavior 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#129002 Forward-Port-Of: odoo/enterprise#127473
The lunch order test now waits for the correct test product to appear after changing locations before selecting it. This prevents the test from accidentally ordering outdated demo products, reducing intermittent failures and improving release reliability.
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#281753
Fixes an issue where filtering sales orders by related project task information could fail with an error. This helps users rely on custom filters involving task stages or other task fields without interruptions.
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#284101This fix ensures that signup and invitation links are created with the required account setup information. It helps prevent failed token generation when users are invited or granted portal/project access.
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
Customers who complete paid self-order purchases now receive receipt emails with the requested receipt image attached. This ensures paid orders include the proof of purchase customers expect, while draft orders remain unaffected.
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
Fixed a POS issue where eWallet or gift card payments could discount an order by one cent less than the amount taken from the card when certain tax settings were used. This keeps the customer charge aligned with the balance consumed from the wallet or gift card.
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#282609 Forward-Port-Of: odoo/odoo#278568
This change makes an automated website editor test more stable by waiting for the interface to finish updating before checking the popup visibility state. It helps reduce false CI failures, improving developer confidence without changing customer-facing behavior.
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
The salary configurator now handles cases where no company bike is configured, instead of showing an error when the bike option is selected. This keeps recruitment offer setup working smoothly and defaults the bike cost to zero when no bike is available.
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
The timesheet timer now excludes archived projects from its project dropdown. This prevents employees from accidentally logging time against projects that are no longer active, while preserving past timesheet history.
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#127374This fixes an error that could block accounting reconciliation when users had both a parent company and a branch company selected. The reconciliation process now uses the relevant company from the journal item, preventing crashes during currency conversion and allowing multi-company accounting workflows to continue 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#123774The website now recognizes more modern search and AI crawler tools, so they can reach default-language pages instead of getting stuck in repeated language redirects. This helps improve search 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
The Point of Sale now automatically chooses a product option when it is the only available choice, except for multi-select attributes. This removes an unnecessary prompt and helps cashiers add products more quickly and reliably.
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
This fix prevents quotation printing from failing for Norwegian customers when no VAT number is entered. Sales users can now print quotations successfully in this case, avoiding disruption during document generation.
Original PR description
**Steps to reproduce:** - Install the `sale_management` module. - Create a new customer and set the country to `Norway`. - Go to Sales and create a new quotation for the newly created customer. - Click the `Print` button. **Error:** `AttributeError: 'bool' object has no attribute 'startswith'` **Root Cause:** At [1], `startswith()` is called on VAT even when the customer's TIN (VAT) is not set, causing an `error`. Fix: This commit prevents the error and allows users to print the report successfully. [1]: https://github.com/odoo/odoo/blob/5c2d5daa13cf582c40bdb44e245e1359d7c410f0/addons/account_edi_ubl_cii/models/account_edi_ubl.py#L924-L928 opw-6469642 Forward-Port-Of: odoo/odoo#282961
This fix prevents an error that could block customers from generating batch payments in the ISO 20022 payment flow. It restores the correct handling of payment address information so users can complete payment batches without encountering a system traceback.
Original PR description
The aim of this commit is to allow customer to make their batch payment without facing a Traceback. Context: odoo/enterprise@35f5341b9b44cc16eaea311295a064189dd802cb introduced bug during a badly…
The aim of this commit is to allow customer to make their batch payment
without facing a Traceback.
Context:
odoo/enterprise@35f5341b9b44cc16eaea311295a064189dd802cb introduced bug
during a badly handled forward port.
The method was removed in saas-18.3 in favor of a function. The forward-port
was half handled and now surfaces to Odoo's own production.
Generating a batch payment could generates the following Traceback:
```py
File "/home/odoo/src/enterprise/saas-19.3/account_iso20022/models/account_journal_sepa_ct.py", line 69, in _get_PstlAdr
return super()._get_PstlAdr(partner_id, payment_method_code)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/odoo/src/enterprise/saas-19.3/account_iso20022/models/account_journal.py", line 501, in _get_PstlAdr
CtrySubDvsn.text = self._sepa_sanitize_communication(partner_address['state'][:35])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'account.journal' object has no attribute '_sepa_sanitize_communication'
```
Task-id: None (internal issue)
Forward-Port-Of: odoo/enterprise#129084
Forward-Port-Of: odoo/enterprise#129006Odoo now keeps tax usage indicators in sync when related accounting, expense, point of sale, or purchase records are changed. This prevents outdated tax status information from appearing and helps users see whether taxes are actively used before making changes.
Original PR description
Currently, `is_used` is computed using queries on `account.move.line`, `account.reconcile.model.line`, etc. As a result, it has no depends and is not automatically updated when records in either model are created, modified, or deleted. This commit reverse M2M fields for respective models and use it as dependency to `_compute_is_used`. It also adds a missing dependency of `is_used` to `_compute_repartition_lines_str`. Forward-Port-Of: odoo/odoo#283406
This fixes an internal manufacturing test cleanup so it only removes rules tied to the intended make-to-order route. It prevents accidental deletion attempts on unrelated company rules, avoiding database errors during test runs with demo data.
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
Users now see a loading spinner while message search results are still being updated. This helps avoid confusion when searches are slow or when early, incomplete results appear before the final results are ready.
Original PR description
Before this commit, when searching messages in a conversation, the search message panel had no UI visual when the search is ongoing. This is a problem because the search can be slow and show intermediate search results from a less specific search. This commit mitigates the issue by replacing the oi-search icon into a spinner when messages search is ongoing. That way, when this is slow or showing intermediate results, there's something on the UI that hints that the search hasn't finished. Before <img width="405" height="95" alt="Screenshot 2026-08-06 at 18 02 24" src="https://github.com/user-attachments/assets/db67b5b8-9563-433a-8367-beb2cac19f26" /> After <img width="404" height="126" alt="Screenshot 2026-08-06 at 18 02 40" src="https://github.com/user-attachments/assets/dc10366a-2102-4fd5-89c9-5b8a9e35b6b5" />
This fixes an issue where changing the manufacturing order for a later subcontracted receipt could incorrectly update the delivered quantity on an earlier receipt. Each subcontracting manufacturing order is now tied only to its own receipt, helping keep purchase and inventory quantities accurate.
Original PR description
**STEP TO REPRODUCE** 1. Create a purchase order for a subcontracted product. 2. validate the picking. 3. Return to the PO, and increase the purchased qty and save, this should create a new picking. 4. On the new picking, click on the smart button to see the subcontracting MO details. 5. Change the product quantity on the MO and save. 6. Return to the first picking, and notice the delivered quantity was changed, this should not be the case. **CAUSE** When creating a new MO, its `move_finished_ids` is linked to the moves of all previous pickings when we create the MO. It should only be linked to the new picking move. opw-6320704 Forward-Port-Of: odoo/odoo#271556
Deleting a draft invoice for timesheet-based services no longer changes which sales order line the related hours belong to. This prevents delivered hours from disappearing from the original order or being moved to another order, keeping billing and sales reporting accurate.
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#281594 Forward-Port-Of: odoo/odoo#279552
Point of Sale now shows the truly available quantity for product variants, excluding stock already reserved by confirmed sales orders. This prevents staff from seeing stock as available when it has already been committed elsewhere, reducing overselling risk.
Original PR description
## Steps to reproduce: - Create another warehouse - Create a product with a variant, like Color, values black and white - Track the product, add a qty on hand of 50 on the black product - Go to the…
## Steps to reproduce: - Create another warehouse - Create a product with a variant, like Color, values black and white - Track the product, add a qty on hand of 50 on the black product - Go to the sales app, make a quotation of 50 for the black product - Confirm the quotation - Go to the PoS, click on the product, check the available qty in the popup - It is still 50, even though the forecasted is correct at 0 ## Why the fix: Having the actual free qty was added in this commit 682bc82 to be able to check the qty that was really free instead of the available qty. This means that we subtract the reserved_qty from the qty_available to get the free_qty. The variant popup was forgotten in this commit, so it was still displaying the qty_available. This is why there was a difference in the qty if we press the product normally or if we long press it, because the variant popup was forgotten in said commit. opw-6382845 Forward-Port-Of: odoo/odoo#283659 Forward-Port-Of: odoo/odoo#280330
This fixes an inventory forecasting issue where transfers recorded with zero demand could incorrectly make past stock forecasts appear negative. Businesses will see more reliable historical and forecasted quantities when products are moved unexpectedly to virtual or production locations.
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
This fixes an issue in HR employee searches for users who do not have access to private employee data. Some advanced searches could return an invalid query, so the update helps keep employee search results reliable for restricted-access users.
Original PR description
The hack to search fields as a user that has no access to private employee data and searching on the `current_version_id` instead of the provided field since we force to wrap searchable fields domains in a Query in odoo/odoo#280373. The hack did not support usage of the 'any!' operator on `current_version_id` leading to a query like: "hr_employee.id in (select id from hr_version ...)". task-6468820 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284271 Forward-Port-Of: odoo/odoo#283811
This fix prevents inactive products from affecting automated website shop filter tests. It improves test reliability 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#283973
This fixes an issue where an abandoned cart could keep free shipping after product prices changed and the order no longer qualified. Shipping costs are recalculated when the customer confirms the cart, helping ensure customers are charged the correct delivery amount while preserving click-and-collect selections.
Original PR description
Steps to reproduce ================== 1. Configure a delivery method with free shipping above a threshold 2. Add a product to the cart above that threshold, select the delivery method and leave the…
Steps to reproduce ================== 1. Configure a delivery method with free shipping above a threshold 2. Add a product to the cart above that threshold, select the delivery method and leave the cart unfinished 3. Lower the product price below the threshold 4. Recover the cart and confirm the order from /shop/checkout => The product prices are refreshed, but shipping stays free although the new total is below the threshold. Root cause ========== Since [1], nothing re-rates the carrier after /shop/confirm_order refreshes the cart prices: the delivery method is selected before the confirmation. In 17.0, the payment page auto-clicked the selected carrier on load, which re-rated the shipping cost and masked the issue. Fix === Re-rate the selected delivery method in `shop_confirm_order` after the prices have been recomputed, as `_cart_update` already does. [1]: https://github.com/odoo/odoo/commit/8e2b6cede55b51f7ccdbe7601aa7e6035fd6f9fe opw-6383849 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#282963 Forward-Port-Of: odoo/odoo#276905
This fix prevents Helpdesk tickets from crashing when incoming emails contain figure elements that do not match the editor's expected image-and-caption format. The editor now skips those unsupported figure layouts instead of raising an error, improving reliability when processing external email content.
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#279981
This fix ensures website pages are refreshed when a visitor changes their cookie preference from denied to accepted. It prevents visitors from seeing an outdated cached version of the page, helping cookie-dependent content behave correctly.
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#283863 Forward-Port-Of: odoo/odoo#282737
This fix ensures pay gap report tests use the correct contract pay field depending on the payroll setup. It helps prevent false test failures and supports more reliable ESG HR reporting validation.
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)