Friday, September 11, 2026
21 changes · saas-19.4
Resolved issues and error corrections
Payslips now only include public holidays that belong to the employee's payroll company. This prevents holidays from another selected company or country from appearing in worked day lines, reducing payroll confusion and incorrect calculations.
Original PR description
Issue: - Create a public holiday in France, and generate a payslip in Belgium, with both companies selected. - French public holidays will appear in the belgian worked day lines. Fix: replace `self.env.companies.ids` -which returns all selected companies- in `_get_leave_domain` with `self.company_id.ids` to match the company of the current version in the payslip. task-id: 6425963 Forward-Port-Of: odoo/odoo#287638
The Point of Sale customer list now loads faster when many customers are present. Menu controls for each customer are only prepared when needed, reducing freezes and improving responsiveness on slower devices.
Original PR description
Opening the customer list with a few hundred partners freezes the UI for several seconds on a slow device (~300ms on a desktop, 5.6s with a 6x CPU throttle in the Chrome profiler). Each `PartnerLine`…
Opening the customer list with a few hundred partners freezes the UI for several seconds on a slow device (~300ms on a desktop, 5.6s with a 6x CPU throttle in the Chrome profiler). Each `PartnerLine` instantiates a full `Dropdown` for its "≡" menu. Setting up a `Dropdown` registers about ten lifecycle hooks (`useDropdownNesting`, `useNavigation`, `useDropdownGroup`, `usePopover`, `useEffect`, ...), and each hook registration eagerly allocates two `OwlError` objects to keep a stack trace. Multiplied by hundreds of rows, this dominates the render: ~60% of the click task is spent constructing errors for menus that will never be opened. Render a plain button per row instead and only mount the `Dropdown`, driven by a `useDropdownState`, once that button is clicked. The `Dropdown` opens its popover on mount when its state is already open and is unmounted again when it closes. The placeholder button keeps the classes the `Dropdown` would add to its toggler so the DOM, the styling and the tour selectors (`button.dropdown`) are unchanged. opw-6453848 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#287564 Forward-Port-Of: odoo/odoo#285974
Point of Sale sessions with stock entries now correctly show the Force Close Session wizard when a closing accounting entry is unbalanced. This prevents sessions from appearing closed without the related accounting entry, helping ensure cash differences are reviewed and posted properly.
Original PR description
When the closing journal entry of a session is unbalanced, `_process_session_validation` in point_of_sale rolls back the transaction and returns the "Force Close Session" wizard action, which…
When the closing journal entry of a session is unbalanced, `_process_session_validation` in point_of_sale rolls back the transaction and returns the "Force Close Session" wizard action, which `_validate_session` returns to the user so the difference can be posted on a chosen account. The pos_stock override of `_process_session_validation` calls `super()` without returning its result. The action is dropped, `_validate_session` carries on, and the session is written as `closed` while the rolled-back entry no longer exists: `move_id` is empty, the orders stay in `paid`, `cash_real_transaction` stays at 0 and the cash difference is never posted. The user sees a successful close and nothing in accounting. Steps to reproduce: - Use a stock configuration whose costs yield a rounding difference on the closing entry (e.g. AVCO products with a sale and a return whose unrounded costs round differently when split between the expense and valuation lines). - Close the session from the PoS. - The session is closed, no journal entry exists, and no wizard was shown. Return the result of `super()` so the wizard is shown as in 19.0. opw-6543863 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287618
The customer list in Point of Sale now loads faster when many customers are available. Customer balances are calculated once per row and matched using the correct linked company record, reducing delays and avoiding mistakes with similarly named customers.
Original PR description
Opening the customer list with a few hundred partners is slow; part of the time is spent in `getPartnerCredit`. The `PartnerLine` template reads `this.partnerInfos` eight times per row, and each call runs `getPartnerCredit` again. For a contact, that method resolves the partner carrying the due by scanning every loaded partner for one named like `parent_name`, so the cost grows with the square of the number of partners. Cache the getter in a template variable so it is evaluated once per row, and resolve the due partner through the loaded `commercial_partner_id` instead of the name scan. This is also what the server sums the dues by, and it no longer picks a wrong homonym. `refreshTotalDueOfPartner` used the same lookup and now shares it. opw-6453848 Forward-Port-Of: odoo/enterprise#131079 Forward-Port-Of: odoo/enterprise#130015
The Point of Sale product configurator now hides option price badges when a fixed pricelist means those extras will not actually be charged. This prevents customers and staff from seeing misleading add-on prices and keeps displayed pricing aligned with the final order total.
Original PR description
Steps to reproduce: - Create a pricelist "TAKEAWAY" with a fixed price of 10.00 on a product whose sales price is 20.00 - Create a preset "TAKEOUT" and set its pricelist to "TAKEAWAY" - Add to that…
Steps to reproduce: - Create a pricelist "TAKEAWAY" with a fixed price of 10.00 on a product whose sales price is 20.00 - Create a preset "TAKEOUT" and set its pricelist to "TAKEAWAY" - Add to that product an attribute with variant creation "Never", with a value carrying an extra price of 1.00 - In the POS, switch to the preset "TAKEOUT" and click the product Issue: The configurator advertises the attribute value with a "+ $ 1.00" badge, but that extra is charged nowhere: the title of the popup and the resulting order line both stay at the 10.00 of the pricelist. Cause: A fixed pricelist rule replaces the whole price of the product, the attribute extra prices included: _compute_price on product.pricelist.item returns fixed_price and never reaches _compute_base_price, the only place where _get_attributes_extra_price is taken into account. getPrice is a faithful port of that and overwrites `basePrice + price_extra` with rule.fixed_price. The configurator, however, rendered its badges out of value.price_extra alone, without ever asking what the pricelist of the order does with it. Fix: Only advertise an extra price when the price of the product actually reflects it, the way website_sale already does with the show_extra_price of _get_additionnal_combination_info. Asking getPrice covers more than a fixed rule: a rule based on another pricelist recurses with no extra either, and a full discount leaves nothing of it. Combo items keep their badges, since computeComboItems adds their extras on top of the combo price, like _get_combo_item_display_price does. opw-6528187 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287740 Forward-Port-Of: odoo/odoo#286475
Point of Sale register closing messages now correctly include cash in/out movements made by managers who do not have accounting access. This prevents false cash differences from appearing after a session is closed, improving trust in cash control reports.
Original PR description
Steps to reproduce: - Give a user PoS Administrator rights and no Accounting rights. - As that user, open a session with cash control, sell something paid in cash and do a Cash Out. - Close the…
Steps to reproduce: - Give a user PoS Administrator rights and no Accounting rights. - As that user, open a session with cash control, sell something paid in cash and do a Cash Out. - Close the register. The closing popup shows the expected cash (opening + cash payments - cash out); enter exactly that amount, the popup shows no difference. - Open the closed session: its chatter says "Closing difference: -X", X being the cash out amount, and "Closing expected" is the amount before the cash out. Issue: The closing control data agrees with the user, but the closing message records a cash difference equal to the cash out. Cause: `_compute_cash_balance` sums `statement_line_ids` with the rights of the current user. Cash moves are `account.bank.statement.line` records, which `_inherits` `account.move`, so the account.move record rules apply to them too. For a PoS user the only such rule is `rule_invoice_pos_user` (`pos_order_ids != False`), and a cash move has no PoS order: unless the user is in `account.group_account_invoice`, their own cash moves are filtered out and `cash_register_balance_end` ignores them, while `get_closing_control_data` sums the lines in sudo. Since da8be203ec32 PoS managers can create cash moves without any accounting group, which made this visible: in 18.0 cash in/out required `account.group_account_invoice`, whose `account_move_see_all` rule makes every move readable. The values stored at closing are right by accident: `_validate_session` reads the lines in sudo just before, and the one2many cache is shared between the sudo and non-sudo environments of the same transaction. The message posted by `update_closing_control_state_session` runs in its own transaction and gets the filtered sum. Fix: Read the cash lines in sudo in `_compute_cash_balance`, as `get_closing_control_data`, `get_cash_in_out_list` and `_validate_session` already do. Users with accounting rights see all lines already, so nothing changes for them. opw-6521591 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287741 Forward-Port-Of: odoo/odoo#286308
French PDP Flow 10 responses from the public portal are now processed instead of only being stored. Rejected reports are correctly marked, users can see the rejection details, and corrected reports can be resent.
Original PR description
Flow 10 PPF responses were stored as attachments without being processed. Consequently, rejected reports remained marked as sent, the rejection reason was not shown to users, and corrected reports could not be submitted again. Process the PPF response codes, update the flow state, and log the returned details in the chatter. Keep response attachments separate from the outgoing payload and allow rejected reports to be manually resent with their original moves and a new transmission identifier. no task id Forward-Port-Of: odoo/odoo#287737 Forward-Port-Of: odoo/odoo#287338
This fixes a compatibility issue where some previously exported custom tours could stop Odoo web assets from loading because they contained an extra saved field. Odoo now accepts those existing tours while still checking the important tour steps, helping avoid disruptions for customers using older generated tours.
Original PR description
Tours exported between 37b35f18 and d2ee747d carry a stray `url` key. `validate()` throws on any unknown key at `registry.addValidation()` time, which aborts asset loading for those pre-existing custom tours. Add a `"*"` wildcard to the schema: unknown keys are ignored, `steps` is still validated. 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#287617
The WhatsApp Disconnect button is now only shown to administrators for accounts set up through the onboarding flow. This prevents manually configured accounts from appearing connected while incoming messages have silently stopped, and avoids access errors for regular internal users opening the account form.
Original PR description
`show_disconnect` was true for any account holding a token, so the Disconnect button showed on manually configured accounts as well. Pressing it unsubscribes the webhook on Meta first, then clears the token, and that write is rejected on a manual account since the credentials are required there. Odoo rolls back, Meta does not, so the account keeps looking configured while incoming messages stop. The compute also read `token`, restricted to WhatsApp administrators, while every internal user has read access on `whatsapp.account`, so opening the account form as a regular user raised an AccessError. The button is now shown only for administrators on onboarded accounts, and `button_disconnect` checks write access, since hiding a button does not stop an RPC call and the request to Meta happens before the write that would have been refused. Task-6544628 Forward-Port-Of: odoo/enterprise#130759
Signed quotation documents created through the customer portal are now protected from deletion. This preserves the signed record while quotations are still awaiting payment, reducing the risk of losing important sales documentation.
Original PR description
Issue: A customer who signs a quotation can delete the chatter entry containing the generated signed PDF. For quotations awaiting a wire transfer, this can remove the only retrievable snapshot of the…
Issue: A customer who signs a quotation can delete the chatter entry containing the generated signed PDF. For quotations awaiting a wire transfer, this can remove the only retrievable snapshot of the signed quotation before the order is confirmed. Steps to reproduce: - Create a quotation requiring an online signature and payment. - Sign it from the customer portal and select wire transfer. - Delete the "Order signed by ..." entry from the portal communication history. Cause: The signing endpoint posts the generated PDF as a regular customer authored comment. Portal chatter allows customers to update their own comments, and deleting one clears its attachments, so the signed PDF is physically removed. https://github.com/odoo/odoo/blob/e479111294b11058038defc31505cc81ac1f1821/addons/sale/controllers/portal.py#L348-L358 Solution: Classify the signed document entry as an automated comment. This keeps it visible and attributed to the customer while ensuring that both the portal interface and backend message rules treat its content and attachment as immutable. Regular customer comments keep their existing behavior. opw-6466029 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286578 Forward-Port-Of: odoo/odoo#283595
The HTML editor now correctly reloads the latest saved version when it detects outdated content. This prevents users from being stuck with stale text after overlapping edits, improving confidence that the displayed document matches what is saved on the server.
Original PR description
Before this commit, an editor recovering from a stale document could stop on this error and keep the stale content:
```
Error: Concurency detected while recovering from a stale document. The
last history id of the server is different from the history id received
by the html_field_write event.
at CollaborationOdooPlugin.resetFromServerAndResyncWithPeers
```
This happens because the recovery compares the history id of the record with the one the html_field_write event carried. A write keeps only the last step id in the field, so an event handled after a later write names an id the record no longer holds. As a result, the recovery stops there and the document stays stale.
This commit fixes the issue by taking the history id read from the record as the new server reference, so the editor converges on the document the server holds.
https://runbot.odoo.com/odoo/error/944595
Forward-Port-Of: odoo/odoo#287408German PoS sessions can now close successfully when an order uses an address-type customer contact without its own name. The system uses the displayed customer name as a fallback, preventing blocking errors while keeping required fiscal export data complete.
Original PR description
Steps to reproduce: - German PoS with Fiskaly TSS configured - Set a customer that is an address contact (type invoice/delivery/other) on an order; such a contact has no name of its own, it is…
Steps to reproduce: - German PoS with Fiskaly TSS configured - Set a customer that is an address contact (type invoice/delivery/other) on an order; such a contact has no name of its own, it is displayed with its parent's - Close the session Issue: Closing crashes with "TypeError: 'bool' object is not subscriptable" and the session cannot be closed at all. Cause: _get_dsfinvk_cash_point_closing_data builds the DSFinV-K buyer block from partner.name, which is only guarded by "if partner := o.partner_id". res.partner.name is not required: the res_partner_check_name constraint only enforces it for type='contact', so an address contact stores NULL there and partner.name reads as False, which cannot be sliced. Fix: Fall back on complete_name - what the UI displays for those contacts, "Parent Company, Delivery" - and on a literal when even that is empty, since the buyer name is mandatory in the export. Reading name first leaves the exported value untouched for every partner that has one. opw-6518336 Forward-Port-Of: odoo/enterprise#129709
Kit products now correctly exclude deliveries completed after the selected accrual date when calculating quantities to invoice. This prevents orders from appearing as invoiceable too early, improving revenue cutoff accuracy for delivered-quantity invoicing.
Original PR description
When selling a kit, the qty_delivered_at_date was not ignoring moves that were done after the accrual_entry_date. Steps to reproduce: ------------------- * Create a kit with any component and make it's invoice policy "Delivered quantities" * Create a sale order for this kit and confirm it, change the order date to any date in the past * Validate the picking * Go check the "Invoiced to be issued" * Change the accrual_entry_date to a date before the picking was validated > Observation: The order still appears opw-6290222 Forward-Port-Of: odoo/odoo#285932 Forward-Port-Of: odoo/odoo#274745
Users can now correct descriptions on sale order lines while an order remains open, even after a line has been delivered or invoiced. This removes confusing behavior where editability depended on which columns were visible, while still preventing product changes on processed lines.
Original PR description
Descriptions on order lines become impossible to change after the line is delivered or invoiced, even though the order is still open. Interestingly, hiding the product column makes the description editable again. This shows that editing descriptions is already technically allowed, but currently depends on the column layout, which is confusing for users. Keep descriptions editable until the order is locked or cancelled. This lets users correct text without allowing changes to products on processed lines. Desired behavior after PR is merged: After a sale order is confirmed, users can modify descriptions while the order remains unlocked, regardless of the column layout. Products on processed lines remain protected. @moduon MT-15454 opw-6432243 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286621 Forward-Port-Of: odoo/odoo#285946
This fix makes sale order availability forecasts accurate when products are sold or purchased in different units of measure, such as packs versus individual units. It prevents misleading green availability indicators and helps sales teams make more reliable delivery commitments.
Original PR description
Issue: --- The ministock feature is not showing correct forecast on sale order lines due to miscalculation from different uom. Steps: --- 1- Install `Sale` and `Purchase` apps without `stock`. 2-…
Issue: --- The ministock feature is not showing correct forecast on sale order lines due to miscalculation from different uom. Steps: --- 1- Install `Sale` and `Purchase` apps without `stock`. 2- Create a product. Enable the track inventory. 3- Add `Pack of 6` to product's sale packagings. 4- Create a purchase order with 12 units. Confirm and receive. 5- Create a SO. Add a line with quantity = 3 and UOM = `Pack of 6`. As you see the availability icon is green, which is not correct. Also, by setting different uom for the product, the forecast quantities are calculated wrongly. Cause and Fix: --- 1- Inside purchase implementation of `_compute_forecasted_without_stock()` all quantities are converted to `product.uom_id`. However `product_uom_qty` is already in product uom and shouldn't be converted again to product uom. This causes wrong forecast if product has a uom set and purhcase line use different. 2- In `QtyAtDateWidget.initCalcData()` `leftToDeliver` is based on line uom, however, `virtual_available_at_date` is based on product uom itself, causing wrong `forecasted_issue` calculation. 3- Also in the template, the uom name is not fetched correctly, which should be product uom name. opw-6384812
This fix ensures that automatic accounting entries created when changing periods are properly matched and cleared. It prevents leftover non-neutral journal items from cluttering accounting records, helping keep financial data cleaner and more reliable.
Original PR description
The previous code wasn't reconciling the destination and accrual moves, leaving potentially non-neutral journal items and their counterpart polluting the DB. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#287524 Forward-Port-Of: odoo/odoo#279766
Scheduled financial reports now use the intended company instead of including all companies by default. This prevents recipients from seeing report figures mixed across multiple companies when automated reports are sent.
Original PR description
This commit https://github.com/odoo/odoo/commit/94ef1127dfa8af17795f39590c8a37edfa117994 changed how with_company() works to ensure it only adds a new company to the allowed companies. This made the reports options include all companies when sent through the cron (since crons have every company enabled by default). This commit fixes this issue by relying on the context key allowed_company_ids instead of with_company(). task-6505248 Forward-Port-Of: odoo/enterprise#129728
This fixes a reconciliation issue where Odoo could create an exchange difference entry even when the selected account did not allow reconciliation. The check now happens in the reconciliation wizard, helping prevent unintended accounting entries and keeping financial records more accurate.
Original PR description
Repro steps: 1) Create two misc entries with different foreign currency amount and different company currency amounts, on an account with "Allow Reconciliation" disabled (one debit, one credit). 2) Go to Journal Items, select both lines and click Reconcile. Issue: An exchange difference entry is generated. Fix: Move the control of the exchange difference moves creation to the account.reconcile.wizard instead of controling it inside action_reconcile of account.move Related PRs: https://github.com/odoo/enterprise/pull/130541 https://github.com/odoo/enterprise/pull/108362 Forward-Port-Of: odoo/enterprise#131145
X/Z reports now use a dedicated paid total so category totals match the order lines shown to users, whether prices are tax included or tax excluded. Daily sales reports keep their existing tax-excluded totals, avoiding changes to established reporting behavior.
Original PR description
The per category totals on X/Z reports would use the same total computation that we use for the daily sales report, but the order lines are shown tax included or excluded based on the config for the database. So we could have a category showing a total which is different from the sum of the order lines on that category. This only shows on X/Z reports, but the daily sale report uses the same data, and always displays the tax excluded prices So here I added an extra 'total_paid' field on the report data, which is used on X/Z reports generation, so the category total matches the order lines on X/Z reports, while keeping the original total which is used for daily reports Task-[6538575](https://www.odoo.com/odoo/project/1737/tasks/6538575) Community PR-[#286954](https://github.com/odoo/odoo/pull/286954) Forward-Port-Of: odoo/enterprise#131110
Time off requests now keep the correct duration when an automation rule creates an activity during request creation or editing. This prevents one-day requests from being incorrectly saved as zero days, improving reliability for HR workflows that use automated reminders or follow-ups.
Original PR description
Problem: A time off request is saved with a duration of 0 days instead of 1 day as soon as an automation rule on Time Off has an action that creates an activity. Cause: `_compute_field_value` runs…
Problem: A time off request is saved with a duration of 0 days instead of 1 day as soon as an automation rule on Time Off has an action that creates an activity. Cause: `_compute_field_value` runs the actions while the fields of the computation it wraps are still protected and not written yet. On `hr.leave` the actions run from a computation nested in `_compute_date_from_to`, so `date_from` is empty when the activity notification reads `display_name`, and `_compute_duration` stores 0. Setting `date_from` afterwards does not mark `number_of_days` to compute again since it is protected. Solution: Extract the snapshot `_filter_pre` already does into `_keep_to_compute` and wrap the post filter and the actions with it, so the fields depending on the running computation stay to compute. Same treatment as 488419a5ca49 (odoo/odoo#243611) on the pre filter. Steps to reproduce: - Enable the developer mode. - Go to Settings > Technical > Automation Rules and create a rule on the Time Off model with the trigger On create and edit. - Add an action of type Create Activity, set its Responsible to another user, and save. - Go to Time Off > New, pick an employee and a time off type, and request one working day. - Observe that the request shows a duration of 0 days. Ticket [link](https://www.odoo.com/odoo/project.task/6498783) opw-6498783 Forward-Port-Of: odoo/odoo#287536 Forward-Port-Of: odoo/odoo#286791
Child contacts linked to Colombian companies are now kept as contacts instead of being treated as separate companies. This restores the expected "Company, Contact" display and lets users find related contacts when searching by the parent company name.
Original PR description
Issue: Colombian child contacts linked to a NIT company are displayed with their standalone name instead of "Company, Contact". Searching Contacts by the company name consequently returns the company…
Issue: Colombian child contacts linked to a NIT company are displayed with their standalone name instead of "Company, Contact". Searching Contacts by the company name consequently returns the company but not its child contacts. Steps to reproduce: - Create a Colombian company with NIT - Add a child contact or address to that company - Filter Contacts by the company name - Observe that the child is displayed separately and is not returned Cause: The Colombian `is_company` computation classifies every partner with a Colombian NIT and qualifying obligations as a company: https://github.com/odoo/enterprise/blob/911686a31d5d3c1539d0dff7d16edf1ce9b101ca/l10n_co_edi/models/res_partner.py#L43-L53 Those fiscal values are commercial fields and are propagated to child contacts. Without checking that the partner is its own commercial entity, children are therefore classified as companies, preventing the standard display name logic from prefixing their parent name. Solution: We need to restrict the Colombian company classification to partners that are their own commercial partner. This preserves the NIT and obligation rules for actual companies while keeping their inherited child records as contacts, restoring both the combined display name and parent name search behavior. opw-6470958 Forward-Port-Of: odoo/enterprise#130373 Forward-Port-Of: odoo/enterprise#128986