Wednesday, September 16, 2026
28 changes · saas-19.4
Resolved issues and error corrections
This fix keeps website builder automated tests reliable after a Chrome browser change altered how background image sizing values may be reported. It helps prevent false test failures during development and release validation, without changing the user-facing website builder experience.
Original PR description
In Chrome 152, single-value `background-size` properties may be serialized or expanded to include implicit dimensions (e.g., appending `auto` like `100px auto`), causing strict exact-string test assertions to fail. This commit updates `website` builder test expectations to use regex prefix matching or substring inclusion so tests remain reliable across different Chrome versions. Note: this is a followup of https://github.com/odoo/odoo/pull/285591 where I missed one occurence during forward-port. My bad. runbot-946570 Forward-Port-Of: odoo/odoo#288519
The mail recipient list now only counts followers who are subscribed to receive message updates. This prevents people who opted out of message notifications from appearing in the To line, making recipient information more accurate for users.
Original PR description
Before this commit, a follower not subscribed to "Messages" message subtype would still show up in the recipients list (the `To: ` line). This happens since [1] introduced the followers badge on the recipient list, without taking into account the subscription of the followers. This commit fixes the issue by using the `recipientsCount` field, which holds the count of followers subscribed to the "Messages" subtype (except self). [1] https://github.com/odoo/odoo/pull/255498 task-6545679
Users can now open the Attachments menu even when expenses with attachments belong to a company that is not currently active. The fix prevents inaccessible expense records from being read, reducing disruption in multi-company setups.
Original PR description
**Problem:** When any expense belonging to a given company has an attachment on it, an Access Error will occur when attempting to enter the Attachments Menu when that company is not active. **Cause:** In the `hr_expense` override of `_inaccessible_comodel_records`, all `hr.expense` records are browsed, then have their values read, even if they are not currently accessible. https://github.com/odoo/odoo/blob/f28aa8e7aa0e2c6fb97c841711800410a1da3b72/addons/hr_expense/models/ir_attachment.py#L22-L23 **Purpose:** Use `_filtered_access` to ensure that only currently accessible records are read. **Steps to Reproduce in Runbot:** 1. Add an attachment to an `hr.expense` record in the My Company (San Francisco) Company. 2. Activate a different company and disable My Company (San Francisco), then attempt to open the Attachments menu in the Settings App. opw-6517249 Forward-Port-Of: odoo/odoo#286845
This update prevents product information popups from appearing accidentally when users scroll through products on touch devices. It improves the mobile Point of Sale experience by avoiding interruptions during normal browsing.
Original PR description
Steps to reproduce: - Open the PoS in mobile mode (touch device) - Swipe the product list up and down a few times Issue: The product info popup sometimes opens while scrolling, as if a product had been long pressed. Cause: Since the product card long press listens to pointerdown/pointerup, a touch that turns into a scroll ends with a pointercancel and never a pointerup, so the long press timer survives the gesture. The debounced onScroll handler is the only other canceller, but when the list is still scrolling from a previous swipe the debounce is already armed, its leading call is skipped and the continuous scroll events keep delaying the trailing call past the long press duration. Fix: Cancel the long press on pointercancel, like pointerup. opw-6514079 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287835
The call settings menu now shows the correct name for the selected audio output device when input and output devices share the same browser device ID. This prevents confusion for users choosing microphones and speakers in Odoo calls, especially in Chrome setups where default devices can overlap.
Original PR description
Steps to reproduce: - Have an input audio device and an output audio device with different names but same device ID. Using Chrome, if the OS only sees one of each, both should have "default" as device ID. (alternatively, modify the code at `updateDevicesList` to simulate having devices of that kind). - Select those devices in the call settings UI, then open the settings UI dropdown again. => The "input" device is properly shown but the "output" device shows the "input" device names (while in fact this is the right one selected in the inner dropdown). This happens since [1]. Before that there were native `<select>` nodes that filtered devices by kind before rendering each option, and the "default" value of Chrome was not really handled. [1]: https://github.com/odoo/odoo/commit/93d0931fba1f467de265200b6a0463cf7edf9534 Related to task-6533808 Forward-Port-Of: odoo/odoo#288434 Forward-Port-Of: odoo/odoo#288285
The equipment section on planning slots now opens in a card-style view on mobile devices instead of a list. This makes it easier for field service teams to review equipment details on smaller screens.
Original PR description
This commit makes sure that the kanban view is used in mobile (over the list view) for the equipment notebook on planning slots. task-6492979
This update restores the previous way Mexican electronic payment complements calculate and report payment amounts, following updated guidance from the certification provider after consultation with authorities. It helps reduce validation issues by using the maximum allowed decimal precision for these tax documents.
Original PR description
Quadrum reverted their changes because > Derived from a consultation with the government we reverted to our previous behavior, we recommend using the maximum number of decimals allowed Reverts commit https://github.com/odoo-dev/enterprise/commit/f13d204dacf9eae98c78de26e9f2e54387a0eaee as well opw-6561617 Forward-Port-Of: odoo/enterprise#131640 Forward-Port-Of: odoo/enterprise#131581
This fix ensures separate physical gift cards, e-wallet items, and discount lines stay as separate checkout lines when required. It prevents duplicate-code errors that could block order validation and leave point-of-sale orders unsynced.
Original PR description
Steps to reproduce: - Create a gift card program (several programs sharing the same gift card product show the same issue) - In the PoS, sell a physical gift card: click the gift card product and set…
Steps to reproduce:
- Create a gift card program (several programs sharing the same gift card product show the same issue)
- In the PoS, sell a physical gift card: click the gift card product and set a code through "Sell physical gift card?"
- Click the gift card product again to sell a second physical card of the same value
- Validate the order
Issue:
The second unit is merged into the already coded orderline (one line, qty 2, one code), so the "Sell physical gift card?" link is no longer displayed and the second code cannot be entered. Validating the order then fails with "The operation cannot be completed: A coupon/loyalty card must have a unique code." and the order stays unsynced: the qty 2 line is split into two point entries both carrying the same gift_code, so two loyalty.card records are created with the same code.
Cause:
_setupGiftCardOptions() (and setupEWalletOptions()) pass merge=false so that a gift card line is never merged, and until 17.0 add_product() honored it ("options.merge !== false"). The 18.0 store refactoring dropped it: addLineToOrder() decides merging from a local variable that is only set to false when a price_unit is given in vals, and never reads opts.merge. The option became dead code, in point_of_sale's addLineToOrder as well as for the pos_discount caller.
Fix:
Honor opts.merge === false in addLineToOrder(). Callers that do not pass the option are unaffected, so the default merging behaviour is byte-for-byte the same; only the callers explicitly forbidding a merge (gift card, ewallet and discount lines) get their pre-18.0 behaviour back.
opw-6466324
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#287486
Forward-Port-Of: odoo/odoo#286237Invoices for customers in the Canary Islands, Ceuta, or Melilla are now reported with the correct Spanish special regime code instead of a generic one. This helps businesses improve compliance with Spanish electronic invoicing and tax reporting requirements.
Original PR description
… key 8 When the customer is located in Canary Islands, Ceuta or Melilla, the invoice falls under a different tax territory and the ClaveRegimenEspecialOTrascendencia should be 08. Add the regime key 08 to the clave_regimen_selection in verifactu Previously this case was not checked and invoices were reported with the generic refime code. The regime key 08 is not available in verifactu. task-6372837 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287114 Forward-Port-Of: odoo/odoo#279435
Sitemap generation now loads only the page data it needs instead of extra website page content. This reduces memory usage and helps prevent failures on websites with many pages.
Original PR description
- Before this commit: All fields of ir.ui.view were prefetched, including arch_db and arch_prev. This caused an out-of-memory issue when dealing with many pages. - After this commit: Only required fields are fetched, avoiding unnecessary memory consumption from view architecture data. opw-6470593 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284945
The website product carousel now scrolls correctly one item at a time in right-to-left languages such as Arabic. This prevents empty gaps and distorted product images, improving the shopping experience for customers using RTL websites.
Original PR description
**Description of the issue/feature this PR addresses:** When a dynamic product carousel is set to single-image scrolling mode in an RTL language (e.g., Arabic), sliding between items behaves…
**Description of the issue/feature this PR addresses:**
When a dynamic product carousel is set to single-image scrolling mode in an RTL language (e.g., Arabic), sliding between items behaves inconsistently, resulting in empty spots or temporarily distorted images.
This occurs because the DOM manipulation creating the infinite scroll was coded for LTR physical movements. In LTR, the first DOM element is visually on the left. In RTL, the visual layout is mirrored, with the first DOM element visually on the right. When we then trigger a "left" slide in RTL, the LTR-based JavaScript doesn't appropriately move the last element to the first index prior to the animation, resulting in an empty gap, among other issues.
This commit resolves the issue by checking the document's text direction. By swapping the target direction ("left" or "right") in onSlideSingleScroll and onSlidSingleScroll when RTL is active, the underlying array operations now correctly align with the animation.
**Steps to reproduce:**
- Website > Edit > add Product Carousel > Customize > change Scrolling Mode to Single
- Website > Edit > Theme > Website > Language > change to Arabic
- Click the arrows in the product carousel > observe inconsistent scrolling with visual glitches
**Current behavior before PR:**
- Visual glitches when sliding carousels in single-image scrolling mode for RTL languages
**Desired behavior after PR is merged:**
- No visual glitches when sliding carousels in single-image scrolling mode for RTL languages
opw-6490277
Forward-Port-Of: odoo/odoo#286042Backend refunds for Point of Sale orders now check that the refunded quantity does not exceed the original sale quantity. This keeps backend refund behavior aligned with the frontend and helps prevent accidental over-refunds.
Original PR description
Currently, if you refund an order fro the backend it is possible to modify the qty as if to refund more than the original order qty. Steps to reproduce: ------------------- * Make an order from the…
Currently, if you refund an order fro the backend it is possible to modify the qty as if to refund more than the original order qty. Steps to reproduce: ------------------- * Make an order from the shop (1 product, qty 1) * Validate the order * Go backend * Find the order and select the refund button * Change qty from -1 to -3 * Save and continue the refund process > No problem refunding more than the original quantity Why the fix: ------------ In the frontend we cannot refund more than the original quantity, we assume the same should be in the backend process. The most simple way to do this is by doing a difference between the quantity from the original order and all the refund lines linked. From `self.refunded_orderline_id.refund_orderline_ids` we need to exclude the line that represents self as it holds the quantity before the onchange and we care about the quantity we're trying to write not the previous (allegedly correct). opw-6328635 Forward-Port-Of: odoo/odoo#287209 Forward-Port-Of: odoo/odoo#281405
Automation rules that run when a field reaches a specific value now avoid running the same actions twice during record creation. This prevents duplicate emails, activities, or similar automated follow-ups when values are filled in automatically, improving reliability for customer workflows.
Original PR description
An automation rule triggered when a field reaches a specific value can run its actions twice when a record is created without that field provided explicitly. For example, a rule that sends one email…
An automation rule triggered when a field reaches a specific value can run its actions twice when a record is created without that field provided explicitly. For example, a rule that sends one email and schedules two activities can send two emails and schedule four activities for one record. ### Reproduction Steps Create an automation on `crm.lead` that triggers when the stage is set to "New", with one email action and one activity action. Create an opportunity through the website contact form, which leaves the stage unset. The lead is assigned to the "New" stage, but both actions run twice. ### Cause The lead stage is computed and read during creation. That computation can trigger the automation before the creation hook processes the same record, causing both paths to run the actions. The processing code tracks records already handled during nested computations. The feedback flag used for this tracking is attached to the records, but the code checked the automation context instead. When the computation was triggered during creation, the flag was therefore missed. ### Fix Read the feedback flag from the records so nested computations update the shared tracking state. The creation hook then skips records already processed by the computation. opw-6331134 Forward-Port-Of: odoo/odoo#288222
This fix prevents an error when users open sale or purchase receipt records from Invoice Analysis. It ensures receipt data is handled consistently in reporting, so teams can review confirmed receipts without interruption.
Original PR description
When enabling Sale/Purchase Receipts and trying to open the form view from Invoice Analysis, an error occurs. The issue is caused by the `move_type` used in `_where()`, which includes a type that is not defined in the `move_type` field selection. Steps to reproduce: - Enable Sale/Purchase Receipts. - Create a Sale/Purchase Receipt for partner A and confirm it - Go to Invoice Analysis and open the Pivot view - Click on a cell to open partner A's receipt, then try to open the form view - Error Ticket [link](https://www.odoo.com/odoo/project.task/6462153) opw-6462153 Forward-Port-Of: odoo/odoo#288077 Forward-Port-Of: odoo/odoo#282922
Fixed checkout behavior when customers switch from in-store pickup to a country-specific delivery option. The store now checks delivery availability against the customer's address instead of the pickup location, ensuring the correct shipping price is applied and avoiding misleading free delivery totals.
Original PR description
Steps to reproduce: - Install eCommerce and Click & Collect. - Publish Pick up in store with a warehouse whose address is in country A (e.g. United States). - Publish a country-specific delivery…
Steps to reproduce: - Install eCommerce and Click & Collect. - Publish Pick up in store with a warehouse whose address is in country A (e.g. United States). - Publish a country-specific delivery method (Fixed Price, non-zero) limited to country B (e.g. Spain). - On the shop, checkout with a delivery address in country B. - Select Pick up in store, then select the country-specific method. Issue: Selecting the country-specific method keeps Delivery at 0 (Free). The same happens with country-restricted connectors: `/shop/set_delivery_method` never applies the method, so no rate is requested for that switch. Cause: Since saas-19.3, a pickup location replaces `partner_shipping_id` with a generated pickup address. `/shop/set_delivery_method` only calls `_set_delivery_method` if the method is in `_get_delivery_methods()`, which matches on the shipping address. The pickup country makes the country-specific method look unavailable, so it is never applied. The order still has the pickup method (0 delivery), and the summary reports the selected method as Free. Match availability on the customer when shipping is a pickup address, and restore that address before rating the method that is being set. Keep the Based on Rules check so a method with no applicable price rule is not listed after pickup. opw-6493956 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285068
This fix removes leftover component records created when users manually change components during subcontracting production. It prevents invalid inventory records from later disrupting stock reservations or transfer validation.
Original PR description
#### Issue When manually adding component lines in the subcontracting Record Production flow, extra stock move lines can remain in the database without a linked stock move. Those lines have no…
#### Issue When manually adding component lines in the subcontracting Record Production flow, extra stock move lines can remain in the database without a linked stock move. Those lines have no move_id, so their related state is empty. They can later be selected by stock reservation code and cause validation errors when processing the transfer. #### Steps to reproduce 1. Create a subcontracted product. 2. Confirm a subcontracting receipt/purchase flow for that product. 3. Open the subcontracting Record Production popup. 4. Remove an existing component line. 5. Add a new component line for another storable product available at the subcontractor location. 6. Save/record the production. 7. Check stock.move.line records , Inventory > History > Group By Status. An extra stock move line is left with no move_id. #### Root cause The inverse of `mrp.production.move_line_raw_ids` already collects and deletes move lines detached from existing raw moves. However, when the user adds a new component product, the inverse creates a new additional raw move. Creating that raw move can also create reserved move lines. The inverse then replaces `move.move_line_ids` with the user-entered lines, but it did not collect the newly created reserved lines before replacing them. Those reserved lines were detached from the move and left in the database as orphan stock move lines. #### Fix Apply the same cleanup logic to newly created additional raw moves: collect their auto-created move lines before replacing `move_line_ids`, then unlink those detached lines at the end of the inverse. opw-6304649 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281864 Forward-Port-Of: odoo/odoo#276235
This fixes an issue where Australian payroll users could not register a super payment when they opened it from a pay run grouped by status. Super payments can now be registered consistently regardless of the navigation path, reducing payroll processing interruptions.
Original PR description
Current behavior: -- Registering a super payment from a pay run opened out of the Pay Runs kanban fails with "Wrong value for account.payment.state. The same super contribution registers without…
Current behavior: -- Registering a super payment from a pay run opened out of the Pay Runs kanban fails with "Wrong value for account.payment.state. The same super contribution registers without error when opened from Payroll > Reporting > Australia > Super Contributions. Expected behavior: -- Register Super Payment should work regardless of how the super contribution record was reached. Steps to reproduce: -- - Take an Australian pay run through to Paid - Open Payroll > Payslips > Pay Runs, kanban grouped by Status (default) - Open the pay run from the Paid column, then Super Submissions - Lock the super contribution and click Register Super Payment Cause of the issue: -- The Pay Runs kanban is grouped by state, so the web client adds default_state to the context of records opened from a column. That context follows the smart button into action_register_super_payment, where account.payment is created without an explicit state. The ORM applies default_state from the context which is a hr.payslip.run value that account.payment.state does not accept. The same leak reaches account.move through action_post, which creates the payment journal entry. Fix: -- apply with_context(clean_context(self.env.context)) to the recordset to remove the default_state key opw-6478084 Forward-Port-Of: odoo/enterprise#128724
Instagram posts now get more time to complete when Instagram needs to download images from Odoo servers. This reduces posting failures caused by timeouts for customers using Instagram social publishing.
Original PR description
Bug === On some database on the saas, timeout issue happen for Instagram. Because Instagram downloads the image on our server, it needs more timeout than other social media. Task-6547753 Forward-Port-Of: odoo/enterprise#131632 Forward-Port-Of: odoo/enterprise#131069
This fixes an issue in Manufacturing where users could trigger an error by reopening the shop floor gear menu while a manufacturing order was still loading. The gear menu is now temporarily disabled during that navigation, reducing interruptions for shop floor operators.
Original PR description
**Steps to reproduce:** * Install the **Manufacturing** module with **Work Orders** enabled. * Create and confirm a Manufacturing Order with at least one Work Order. * Open the **Shop Floor** view. *…
**Steps to reproduce:** * Install the **Manufacturing** module with **Work Orders** enabled. * Create and confirm a Manufacturing Order with at least one Work Order. * Open the **Shop Floor** view. * On a work order card, click the **gear** icon to open the menu dialog. * Click **Open Manufacturing Order** on a slow network connection. * Before the MO form view finishes loading, quickly click the **gear** icon again and open another dialog (e.g. Log Note). * The MO form view loads, destroying the shop floor component. * Close the Log Note dialog. **Observed behavior:** * An `UncaughtPromiseError: Component is destroyed` error is thrown because the dialog tries to interact with the shop floor component that has already been destroyed by the navigation to the MO form view. **Cause:** * When the user clicks "Open Manufacturing Order", `doAction` is called to navigate to the MO form view, and `props.close()` immediately closes the menu dialog. However, the shop floor component is still visible while the new view is loading. * During this gap, the gear button remains clickable. If the user opens another dialog (e.g. Log Note), that dialog holds a reference to the shop floor component. When the MO form view finishes mounting, the shop floor is destroyed, and closing the stale dialog triggers operations on the destroyed component. **Fix:** * Add an `actionPending` state flag to `MrpDisplayRecord`. When the user selects "Open Manufacturing Order" from the menu dialog, an `onSelect` callback sets `actionPending` to `true`, which disables the gear button and prevents any new dialog from being opened. * The flag is only set for `openMO` (which navigates away and destroys the component), not for other menu actions like Scrap, Add Component, or Log Note which open wizard dialogs and return to the shop floor. opw-6107579 Forward-Port-Of: odoo/enterprise#131448 Forward-Port-Of: odoo/enterprise#123490
Subscription invoices now use the earliest deferred start date across invoice lines when setting subscription or upsell log dates. This prevents later invoice lines from incorrectly overriding the effective date, improving accuracy for subscription records and related reporting.
Original PR description
When posting a subscription invoice, each invoice line overwrites the effective date of the subscription or upsell logs. The last line therefore determines the date, even when another line has an earlier deferred start. Use the earliest deferred start date on the invoice, falling back to the invoice date when no deferred start date is set. Forward-Port-Of: odoo/enterprise#131512
This fixes Dominican Republic electronic invoicing so customer invoice numbers no longer continue from vendor bill numbers when both use the same document type. It helps prevent incorrect official invoice sequences and reduces accounting compliance risks.
Original PR description
### Issue before this commit: When users enabled the "Use Documents" option on a Purchase journal and created a vendor bill with a specific document type (e.g., type 31) and number (e.g., E3100056),…
### Issue before this commit: When users enabled the "Use Documents" option on a Purchase journal and created a vendor bill with a specific document type (e.g., type 31) and number (e.g., E3100056), creating a new customer invoice with the same document type would incorrectly continue the sequence from the vendor bill's number (e.g., E3100057). ### Steps to reproduce the issue: 1. Download Accounting and l10n_do_edi 2. Go to invoices and vendor bills and delete all of them 3. Go to Journals and tick Use Documents options for Sales and Purchases journals 4. Create a vendor bill setting the document type as for example (31) Electronic Tax Credit Invoice and the document number as for example E3100056 5. Create an invoice and set the same document type 6. See that the sequence will increase by 1 but starting from the last number setted (so it will be E3100057) when you create a new invoice ### Cause of the issue: https://github.com/odoo/enterprise/blob/d4da04a036486818c12712a200a3482a66921abf/l10n_do_edi/models/account_move.py#L346-L354 The _get_last_sequence_domain override is fetching the last sequence across the company based on the document type, but it did not filter by move_type. Consequently, it was catching vendor bill numbers (in_invoice) to compute the next sequence for customer invoices (out_invoice). ### Reason to introduce the fix: Restrict the sequence lookup to sales documents will ensures that vendor bill references do not interfere with the official sequential numbering of customer invoices. opw-6430906 Forward-Port-Of: odoo/enterprise#128178
Corrects how Mexican electronic payment documents calculate fixed-rate taxes on partial payments, preventing mismatches that can cause official tax documents to be rejected. This helps businesses using Cuota taxes issue compliant payment complements more reliably.
Original PR description
When generating a payment complement, tax base and importe coming from the related invoice are prorated by the percentage actually paid, each rounded independently to the currency precision. The…
When generating a payment complement, tax base and importe coming from the related invoice are prorated by the percentage actually paid, each rounded independently to the currency precision. The post-fix step that restores the SAT invariant uses a Tasa-only formula (`base = total / (1 + rate)`), so Cuota (fixed amount per unit) taxes keep mismatched values, ending up with `ImporteDR != round(BaseDR * TasaOCuotaDR)`. This leads to CFDIs rejected by the PAC/SAT. Steps to reproduce: - Create a customer invoice with a Cuota IEPS tax (e.g. 26.2569). - Register a partial payment whose amount is not an exact divisor of the invoice total (e.g. one third). - Send the payment CFDI: the resulting Cuota TrasladoDR has an ImporteDR that does not match BaseDR * TasaOCuotaDR, leading to a rejected CFDI. This commit recomputes `importe` from the prorated `base` for Cuota taxes (bypassing the Tasa post-fix) opw-6087564 Forward-Port-Of: odoo/enterprise#131436 Forward-Port-Of: odoo/enterprise#113395
Fixed company-paid expenses created from a project so project cost tracking is applied only to the actual expense line. This prevents the same project allocation from appearing on offsetting accounting lines, giving businesses accurate project expense reporting and analytic balances.
Original PR description
### Current behavior: Creating a company-paid expense from the Project overview posts a journal entry with the project analytic on both the expense and outstanding lines, so the analytic balance nets to zero ### Expected behavior: Analytic distribution should only be on the P&L (expense) line ### Steps to reproduce: 1. Open a project overview and create a company-paid expense 2. Submit, approve, and post it 3. Open the journal entry: analytic is on debit and credit lines ### Cause of the issue: `project_id` stays in the context after `clean_context` during `_create_company_paid_moves`. With `sale_project`, AML analytic compute then applies the project distribution to outstanding/tax lines as well ### Fix: Removed `project_id` from the context when creating company-paid moves opw-6368848 Forward-Port-Of: odoo/odoo#287299 Forward-Port-Of: odoo/odoo#280256
The accounting dashboard payment button now excludes payments that have already been reconciled. This helps users focus only on payments that still need action and avoids confusion from showing completed items as ready to pay.
Original PR description
In the accounting dashboard, we have a button to show payments ready to be paid, but we don't exclude the reconciled payments from this view. We should as reconciled payment are supposed to be paid. task-6564159 Forward-Port-Of: odoo/enterprise#131143
Fixed an issue where Save and Discard buttons could remain visible after a user retyped a value that looked different but was treated as unchanged. This ensures forms correctly show when there is nothing left to save, reducing confusion and unnecessary page reloads.
Original PR description
**Description of the issue/feature this PR addresses:** Fixes #287978 In `useInputField` (`input_field_hook.js`), `onChange` and `commitChanges` only trigger the `FIELD_IS_DIRTY` bus event with…
**Description of the issue/feature this PR addresses:** Fixes #287978 In `useInputField` (`input_field_hook.js`), `onChange` and `commitChanges` only trigger the `FIELD_IS_DIRTY` bus event with `false` when the newly typed value actually differs from the value already stored on the record. When the typed text parses to the *same* value as what is already on the record (e.g. retyping `6,250` after the field already holds `6,25`), the code takes the `else` branch, which only resets the displayed text and never notifies the bus that the field is no longer dirty. `form_status_indicator.js` keeps whatever `fieldIsDirty` state it last received from that bus event, and shows the Save/Discard buttons whenever `root.dirty || fieldIsDirty`. Since nothing ever fires `FIELD_IS_DIRTY(false)` in that `else` branch, `fieldIsDirty` stays stuck at `true`. `Discard` correctly resets `root.dirty`, but has no way to reset `fieldIsDirty`, so the Save/Discard buttons keep showing even though the record is clean. **Current behavior before PR:** 1. Edit a field to a new value, click away (value committed, indicator shows Save/Discard as expected). 2. Edit the same field again, this time typing different text that parses to the *same* value (e.g. `1.20` when the field already holds `1.2`), click away. 3. Click **Discard**. The record is reverted, but the Save/Discard buttons remain visible. They stay stuck until the page is reloaded. **Desired behavior after PR is merged:** Retyping different text that parses to the same value clears the field's dirty state like any other case where the field ends up unchanged, so Discard (or any other action) correctly hides the Save/Discard buttons once there is nothing left to save. Forward-Port-Of: odoo/odoo#288030
Salespeople now receive the expected upsell warning when multiple timesheet entries together exceed a service product's prepaid hours threshold. This helps teams spot additional billing opportunities reliably, even when work is logged over several entries instead of all at once.
Original PR description
Steps to reproduce: -------------------------------------------- 1. Install `sale_timesheet` module 2. Create a product with: * Type: Service * Invoicing Policy: Prepaid/Fixed Price * Create on…
Steps to reproduce:
--------------------------------------------
1. Install `sale_timesheet` module
2. Create a product with:
* Type: Service
* Invoicing Policy: Prepaid/Fixed Price
* Create on Order: Project & Task
* Upsell Threshold: Set some value (e,g. 600%)
3. Create and confirm the sale order with that product
4. Create and confirm an invoice for the sale order
5. From Recorder Hours Smart button:
* Add two timesheets for hours 3 & 4 (Combined h > Threshold Percentage/100)
Observation:
--------------------------------------------
* When adding a single 7h timesheet, an upsell warning activity is correctly created for the salesperson in the chatter.
* When adding multiple incremental timesheets whose combined duration exceeds the threshold, no upsell warning activity is created.
Issue:
--------------------------------------------
* The `_compute_field_value` method filters on `invoice_status != 'upselling'` before recomputing the new invoice status.
* After the first incremental timesheet is added, the sales order already has the upselling status from the previous computation.
* As a result, subsequent computations skip the upsell activity logic, even though `has_displayed_warning_upsell` is still `False` and no warning activity has yet been created.
Solution:
--------------------------------------------
* Removes the `so.invoice_status != 'upselling'` condition from the filter.
* The order-level `invoice_status` was never the right place to control upsell activity creation. An order can have `invoice_status == 'upselling'` for various reasons (delivered > invoiced), but that doesn't mean all lines have already triggered their upsell warnings.
opw-6117754
Forward-Port-Of: odoo/odoo#287642
Forward-Port-Of: odoo/odoo#266275This fixes Belgian payroll calculations for employees under Joint Committee 302 so train pass reimbursements are no longer reduced twice. From February 2026, the system will use the official sector table values at 100%, helping avoid underpayment of eligible employee transport costs.
Original PR description
Previously, the system was applying an additional 80% reduction ratio on top of the CP 302 train allowance values. However, the rates listed in the official CP 302 sectoral table already incorporate the legally applicable 71.8% calculation. Applying an extra 80% factor resulted in an under-reimbursement of train transport costs for employees under Joint Committee 302. **What:** - Added the missing rule parameter value record _`rule_parameter_cp302_train_reimbursement_ratio_2026`_ setting the ratio factor to 1 (100%) effective from February 1, 2026. task-6532036 Forward-Port-Of: odoo/enterprise#130291
This fixes an automated Point of Sale test for QFPay payments so it follows the updated checkout flow and clicks the Send button before waiting for card payment. It helps ensure QFPay terminal payment flows are properly tested and avoids false test failures.
Original PR description
Commit 7a208335fa84 ("[IMP] point_of_sale: avoid fast payments") removed the automatic sending of the transaction to the terminal, so the user now has to click on "Send" to trigger the payment request.
The tours of the other payment terminals (adyen, razorpay, safaricom, viva_com) were adapted accordingly, but pos_qfpay was missed. As a result, the payment line never reaches the 'waitingCard' status and the tour times out waiting for the "Waiting for card" step.
Add the missing PaymentScreen.clickSendButton() step to fix the tour.
runbot-940270
Forward-Port-Of: odoo/odoo#288301
Forward-Port-Of: odoo/odoo#275204