Wednesday, September 16, 2026
21 changes · saas-19.2
Resolved issues and error corrections
Warehouse users processing multi-step deliveries can now create backorders without being blocked by sales order access restrictions. This prevents validation errors when stock transfers originate from sales orders owned by another user, keeping delivery operations moving smoothly.
Original PR description
Multi-step deliveries backorders may create a new picking when processed by a user who only has access to their own sales orders. But, `_key_assign_picking` reads from the related sales order that they don’t have access to. Previously, these moves were confirmed with superuser rights, so this read did not trigger the sales order record rule. Since 19.2, the `sudo()` was removed. The fix would be to add this back in so the access rights error would not be raised. Steps to reproduce on Runbot: 1. Log in as User A (Admin) Turn on Multi-Step Routes Set the warehouse to use 2-step delivery (Pick then Deliver) 2. Create a Sales Order for a product with demand more than stock available and confirm it. 3. Log in as User B with (Demo): Sales: User: Own Documents Only Inventory: User 4. Open the picking transfer generated from User A’s Sales Order. 5. Click Validate and choose Create Backorder. Related: opw-6509032
Colombian PoS receipts no longer fail when a company has more than one DIAN obligation type. This prevents paid orders from getting stuck during sync and allows receipts to print correctly for common Colombian tax setups.
Original PR description
Steps to reproduce: - Colombian company with DIAN electronic invoicing enabled in PoS - On the company contact, set more than one obligation type (e.g. O-13, O-15 and O-23) - Open a PoS session, sell a product and pay Issue: The order fails to sync with "Expected singleton: l10n_co_edi.type_code(x, y, z)" as soon as the DIAN document is accepted, and the receipt cannot be printed. On 18.0 to saas-19.1 the same crash happens when the receipt data is generated for printing. Cause: `_compute_l10n_co_edi_pos_receipt_data` fills `obligation_type_description` by reading `description` directly on `l10n_co_edi_obligation_type_ids`, which is a many2many. The read only works when the company carries exactly one obligation type, while a Colombian company commonly has several (they are all sent to the DIAN in `TaxLevelCode`). Fix: Join the descriptions of all obligation types, the same way the DIAN invoice PDF report already does. opw-6576523
Fixed an issue where search field choices in Point of Sale orders were hidden on small screens, preventing users from searching by date, customer, or other fields. Mobile users can now use the full order search functionality consistently, reducing friction when finding past orders.
Original PR description
Steps to reproduce: - Open a PoS session on a small screen (mobile app or mobile browser) - Register at least one order, so the order list is not empty - Go to the Orders screen and type a term in…
Steps to reproduce: - Open a PoS session on a small screen (mobile app or mobile browser) - Register at least one order, so the order list is not empty - Go to the Orders screen and type a term in the search bar Issue: On a desktop the search bar drops down the list of fields to search on (Reference, Receipt Number, Invoice Number, Date, Customer). On a small screen that list never shows up, so the search silently falls back to the first field and there is no way to search by Date or by Customer. Cause: The list is rendered, but painted behind the order list. Under the media-breakpoint-down(sm) block of ticket_screen.scss the order list becomes `position: sticky; z-index: 1`, so a sibling rule raised `.search .fields` to `z-index: 2` to keep the dropdown on top. The `z-1` utility class put on that dropdown in 07f743843830 compiles to `z-index: 1 !important` and overrides the rule. Both elements end up at `z-index: 1` in the same stacking context, and the order list wins the paint order because it comes later in the DOM. Fix: Drop the `z-1` utility and declare `z-index: 2` on `.fields` in the search bar's own stylesheet, which makes the small-screen override in ticket_screen.scss redundant. The stacking of the dropdown now lives in a single place, next to the rest of its styling, so a utility class added to that element cannot silently disable it again. A tour clicks its target element directly and so cannot see a purely visual overlap, which is why the existing MobileTestUi runs of TicketScreen.search() never caught this. The added assertion checks that a suggestion is the topmost element at its own center. opw-6540466 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287534 Forward-Port-Of: odoo/odoo#286983
Website sitemap generation now loads only the page data it needs instead of pulling large hidden view contents into memory. This reduces the risk of failures on websites with many pages and helps sitemap creation complete more reliably.
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
Changing an employee's working schedule no longer accidentally shifts the dates on existing time off requests, especially for employees in time zones ahead of UTC. The request dates remain unchanged while the time off duration is still recalculated to match the new schedule, improving payroll and absence accuracy.
Original PR description
## Description Changing an employee version's working schedule recomputes the internal dates of subsequent time off requests. The `hr.leave` write override then mirrors these recomputed UTC values…
## Description Changing an employee version's working schedule recomputes the internal dates of subsequent time off requests. The `hr.leave` write override then mirrors these recomputed UTC values back to the request dates. For timezones ahead of UTC, midnight on a non-working day can therefore shift the requested date to the previous day. This fix marks the schedule-triggered re-computation as an internal fast update, ensuring that the dates selected on the time off request remain the source of truth while still allowing the leave duration to be recomputed according to the new working schedule. Regression coverage is added to ensure that changing an employee's working schedule: * preserves the dates selected on existing time off requests; * correctly recomputes the leave duration according to the new schedule. ## Steps to reproduce 1. Create an employee with timezone Europe/Brussels and a Monday–Friday working schedule. Before creating any leave, set the employment record’s effective date and contract start date to 3 March 2025. 2. Create a full-day time off request from 23 to 26 February 2026. Its duration is initially four working days. 3. Change only the employee’s working schedule to a duration-based Tuesday–Friday schedule, with 7.6 hours per working day and Monday off. Leave the effective date and contract dates unchanged, then save. **Before the fix:** the request’s start date incorrectly shifts to 22 February. **After the fix:** the dates remain 23–26 February, while the duration correctly decreases to three working days. opw-6456107
The website configurator now handles outages of Odoo's external website recommendation service without crashing. This lets users continue creating a new website even if that service is temporarily unreachable.
Original PR description
bug: configurator_missing_industry raised an uncaught RPC_ERROR when the IAP website API was unreachable, breaking the color palette step of the website configurator. steps: - Go in settings and set in the system parameters `website.website_api_endpoint` to anything that won't work - Create a new website - Traceback at the palette step fix: Wrap the IAP calls in a try/except for AccessError. task-6325919 Forward-Port-Of: odoo/odoo#280675
This update restores the previous way Mexican electronic payment complements calculate amounts, using the maximum allowed decimals as recommended after consultation with the tax authority. It helps prevent rounding discrepancies and rejected payment documents for businesses using Mexican electronic invoicing.
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 fixes a problem that could block Dutch VAT returns and ICP declarations when a Digipoort certificate key was protected by a password. The system now reads the key in the expected format, restoring successful submissions and status checks.
Original PR description
Steps to reproduce: 1. Set a Digipoort certificate whose private key has a password 2. Submit a VAT return or an ICP declaration 3. It fails with 'bad password read' Analysis: Before 19.0 the PEM key was in an unencrypted format. Since f88f8258ead, env['certificate.key'].pem_key is encrypted whenever the key has a password. Every consumer in this module loads it without a passphrase: the VAT wizard, the ICP wizard and the status polling cron. Same cause as (odoo/enterprise#96562), which fixed it for l10n_mx_edi. Solution: Decrypt the key when reading it, as was already the case before 19.0. opw-6421300 Forward-Port-Of: odoo/enterprise#127528
Fixes an issue where some PDF quote header or footer form fields could appear blank after generating a quotation PDF. This ensures customer-facing quotes preserve the intended information when using PDFs with structured form fields.
Original PR description
Issue: --- When a quotation header/footer PDF has a hierarchical AcroForm field, the value set for that field is silently dropped from the generated PDF Quote when running on pypdf 5.4.0. Steps to…
Issue: --- When a quotation header/footer PDF has a hierarchical AcroForm field, the value set for that field is silently dropped from the generated PDF Quote when running on pypdf 5.4.0. Steps to reproduce: 1- Using a python 3.13 env, install requirements.txt (or otherwise run with pypdf==5.4.0 instead of PyPDF2). 2- Upload a header/footer PDF whose form field is a hierarchical field (`/T`/`/FT`/`/V` on the parent, not on the widget itself). The attachment from the ticket can be used as a sample to reproduce the bug. 3- Create a SO and select that document in the Quote Builder tab. 4- Print -> PDF Quote. 5- The value bound to that field does not appear in the printed PDF. Cause: --- After https://github.com/odoo/odoo/commit/4b02fbd717f62dd5345dad3ffb8d428c1c180007 `_add_pages_to_writer` renames the parent `/Field` object's `/T` when the widget itself has none. PyPDF2's `addPage` inserted the reader's page as is, keeping the widget's `/Parent`. pypdf 5.4.0 deep clones the page instead and ignores `/Parent` at every depth, so the widget loses the link to that field. It ends up with neither `/T` nor `/FT`, hence nothing matches the value mapping. Fix: --- Merge the field into its widget annotations instead: copy the prefixed `/T` and the inheritable keys onto each of them, then drop `/Parent`. The field's own `/T` is left untouched, so a field owning several widgets is filled on all of them. opw-6508728 Forward-Port-Of: odoo/odoo#287676 Forward-Port-Of: odoo/odoo#286186
Users signing in while a live websocket connection was open could be sent back to the login page because an old session cookie was resent. The session is now saved during the connection handshake without overwriting the browser cookie, making login more reliable in affected flows such as live chat.
Original PR description
Before this commit, signing in from a page holding a websocket connection could land the user back on the login form. This happens because the websocket handshake marks the session dirty to get it written on disk, and _save_session sends a "session_id" cookie back for every dirty session. A handshake answered between the login response and the request that follows it therefore puts the pre-login session back in the browser. Note that the failure was seen on master, where a livechat tour signs in with the bus connected, but every version since 17.0 answers the handshake the same way. This commit saves the session from the handshake itself, so that the response carries no session cookie. https://runbot.odoo.com/odoo/error/947173 Forward-Port-Of: odoo/odoo#288151 Forward-Port-Of: odoo/odoo#287960
Point of Sale now keeps separate lines when selling multiple physical gift cards or e-wallet items that each need their own code. This prevents checkout failures, duplicate code errors, and unsynced orders when customers buy more than one gift card of the same value.
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#286237Multi-day time off requests for fully flexible employees now show the correct number of days instead of always showing one day. The calculation also accounts for public holidays and half-day boundaries, helping payroll and absence records stay accurate.
Original PR description
Steps to reproduce: ------------------------------------ 1. Install Time off module 2. Create New Fully Flexible Employee 3. Click on the Time off smart button 4. Create time off for multiple days…
Steps to reproduce: ------------------------------------ 1. Install Time off module 2. Create New Fully Flexible Employee 3. Click on the Time off smart button 4. Create time off for multiple days (eg. Mon - Friday) Observation: ------------------------------------ Number of days still shows 1 Days. Issue: ------------------------------------ Issue occurs because `work_time_per_day_mapped` returns one interval per day for standard and flexible schedules in multi-day time off requests, so the interval count correctly matches the number of leave days. https://github.com/odoo/odoo/blob/d73e5662a0af7c549008661f743ba5d51f765339/addons/hr_holidays/models/hr_leave.py#L460-L461 However, for fully flexible schedules, it returns a single interval containing the total hours across all days, causing the leave duration to always be computed as 1 day regardless of the actual number of days requested. Solution: ------------------------------------ For fully flexible employees, count the actual calendar days and subtract public holidays when applicable. opw-6060552 Forward-Port-Of: odoo/odoo#255826
This fixes a data cleanup issue when users manually change component lines during subcontracted production recording. It prevents leftover inventory records from causing later transfer validation errors, improving reliability in subcontracting workflows.
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
Australian payroll users can now register super payments successfully even when opening a pay run from the status-based Pay Runs view. This prevents an incorrect pay run status from being applied to the payment, avoiding an error and keeping the payment workflow consistent across entry points.
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
This fix prevents sale orders from failing when a user quickly saves after rearranging order lines. It temporarily pauses saving while the line reorder is being processed, making quotation and sales order editing more reliable.
Original PR description
When we save a sale order just after reordering its sale order lines, there's a chance that an error is thrown Steps to reproduce: 1. Install Sales 2. Go to Sales and open any quotation that has…
When we save a sale order just after reordering its sale order lines, there's a chance that an error is thrown Steps to reproduce: 1. Install Sales 2. Go to Sales and open any quotation that has multiple sale order lines 3. Reorder a sale order line and immediately save (there's more chance to reproduce if you have a lot of sol and if you use the shortcut `ALT + S`) 4. An error is thrown Issue: It is possible that the save takes place during the execution of super.sortDrop. This reassigns the id of all the records in this.props.list.records Therefore, calling `_handleQuantityAdjustment` with the recordMap computed before the save uses an id that has disappeard from this.props.list.records so we cannot find it at https://github.com/odoo/odoo/blob/4973903252865a6a7a2da235bc3a01675dbbff4d/addons/sale_management/static/src/fields/sale_order_line_field/sale_order_line_field.js#L263 which eventually throws an error Solution: Suspend any save mechanism when sortDrop starts and resume it when sortDrop has finished opw-6483206 Forward-Port-Of: odoo/odoo#286787
Product carousels now slide correctly when websites use right-to-left languages such as Arabic. This prevents empty gaps and distorted product images, improving the shopping experience for those visitors.
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#286042Automation rules that react to default or computed field values now correctly recognize when a new record has already been processed. This prevents duplicate emails, activities, or other automated actions when records are created through flows such as website contact forms.
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
Auto-completing a vendor bill no longer replaces a manually selected recipient bank account when the currency has not changed. This prevents payment details from being silently changed for vendors with multiple bank accounts, reducing the risk of user confusion or payment mistakes.
Original PR description
### Issue: When a bank account is manually set on a bill for a partner with multiple bank accounts, using the Auto-Complete feature could reset the manual selection even without a currency change ###…
### Issue: When a bank account is manually set on a bill for a partner with multiple bank accounts, using the Auto-Complete feature could reset the manual selection even without a currency change ### Cause: When `invoice_vendor_bill_id` is set, an onchange assigns `currency_id` unconditionally, even when it is the same value This triggers `_compute_partner_bank_id`, which always recomputes the best matching bank account from scratch without considering the currently set value If the currency did not change, this recompute is unnecessary and silently overrides the manual selection ### Steps to reproduce: - Install `account` - Create a Vendor with 2 bank accounts (keep default values to have equal priority on all accounts) - Create and post a Bill for this vendor with at least one line - Create a new Bill for the same vendor - Set the Recipient Bank to the second account in the list - In Auto-Complete, select the first Bill Before the fix, the Recipient Bank is reset to the first account opw-6210414 Forward-Port-Of: odoo/odoo#283479
This fix stops Odoo from processing a file upload result after the related form or dialog has already been closed. It prevents confusing client error popups and avoids leaving uploaded attachments disconnected from their records.
Original PR description
Description of the issue/feature this PR addresses: A file upload through `FileInput` (and therefore every `many2many_binary` field) still calls `onUpload` after the component has been destroyed.…
Description of the issue/feature this PR addresses:
A file upload through `FileInput` (and therefore every `many2many_binary` field) still calls `onUpload` after the component has been destroyed. When the form or dialog holding the field goes away while the upload request is in flight, the response crashes the web client with two "Odoo Client Error" popups and the uploaded attachment is orphaned.
Steps to reproduce on a stock database (19.0, and 17.0 carries the same code):
1. Open an Email Template form (Settings > Technical > Email Templates), page *Options*, field *Attachments*. Any `target="new"` wizard with a `many2many_binary` field shows the same thing, e.g. the Recruitment *Refuse* wizard.
2. Pick a file that takes a moment to upload (a few MB, or a slow connection).
3. Before the file tile appears, leave the form through the breadcrumb, or close the wizard with Escape, its close button, or its action button. Nothing in the field shows that an upload is still running, so users do this routinely.
Current behavior before PR:
When the upload response arrives, two errors are raised:
```
UncaughtPromiseError > Component is destroyed
at webRead <- _loadRecords <- _applyCommands <- addAndRemove
TypeError: Cannot set properties of null (setting 'value')
at FileInput.onFileInputChange
```
Cause: `FileInput` posts the file through the `http` service, which is not protected against a destroyed component (unlike `orm`), so the response still reaches `onFileInputChange` after the component, the field and the form controller are gone. It then calls `onUpload`, which for `Many2ManyBinaryField` links the attachment through the form controller's protected ORM (hence the rejection), and it clears a file input ref that no longer exists (hence the TypeError).
We hit this in production on three different wizards over a few months. The client error logs users saved all carry the same trace, and the audit log shows the dialog's action completing a fraction of a second before the upload landed.
Desired behavior after PR is merged:
After the upload resolves, `onFileInputChange` stops when the component is destroyed: nobody is left to receive the uploaded files, and this matches how the protected services already treat a destroyed component. No error is raised, and `onUpload` is not called.
A Hoot test in `file_input.test.js` mounts a `FileInput` behind a `t-if`, starts an upload, unmounts the input while the upload is pending, resolves the upload, and asserts that `onUpload` was not called. It fails before the fix with the two errors above and passes after.
`master` already guards the ref write through its signal-style refs, but still calls `onUpload` on the destroyed component, so the first error remains there.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#286831Sales teams will now receive the expected upsell warning when multiple timesheet entries together exceed a prepaid service threshold. This helps prevent missed sales follow-up opportunities when extra work accumulates over time instead of being logged 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#266275Opening sale or purchase receipts from Invoice Analysis no longer triggers an error. This helps users reliably review receipt details from reporting views when receipt features are enabled.
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