Friday, August 28, 2026
31 changes · saas-19.4
Enhancements to existing features
Accounting now uses a more efficient way to find the latest relevant journal entry by date. This significantly reduces delays in affected accounting views, turning a previously very slow read operation into a near-instant response in the reported case.
Original PR description
This commit adds an index on `(journal_id, date)` to speed up `_get_last_sequence_domain`. The slow part is `.search(domain, order='date [desc|asc]', limit=1)` Because of the order by date,…
This commit adds an index on `(journal_id, date)` to speed up `_get_last_sequence_domain`. The slow part is `.search(domain, order='date [desc|asc]', limit=1)` Because of the order by date, postgresql scans the index on `date`, assuming a row will be quickly matched. If this assumption is wrong though, it'll scan a large part of the index or all of it, which is slow. This is the case with account move 17102258 on odoo.com at the time of writing this. - before - 1st query https://explain.dalibo.com/plan/6b8ff62b1572ah1a - 2nd query https://explain.dalibo.com/plan/41243cgccg5798gb - after - 1st query https://explain.dalibo.com/plan/4fd2c7ed28g7b138 - 2nd query https://explain.dalibo.com/plan/2heh6c6965c88h71 - ~~1st query https://explain.dalibo.com/plan/f874a31f4b07hdeb~~ - ~~2nd query https://explain.dalibo.com/plan/e8h09725gfh4c2e9~~ full `web_read` - before ~1min 10s - after ~650ms task-6481393 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283159
Companies sharing the same French SIREN number in one database can now reuse a successful identity verification from another company. This reduces repeated manual KYC work for organizations managing many branches or related companies.
Original PR description
We have some clients that have several hundreds of companies/branches on the same db, with the same SIREN (incubateur or the like). They will need to do the kyc (that will be identical, as it's the same SIREN) for all the companies. It's especially cumbersome if it needs manual intervention So, if one company on that database, with the same SIREN, managed to register, then it means it has succeded the kyc. Meaning we can bypass the kyc for the other identifiers as well. task-6515315 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#285236
Resolved issues and error corrections
The point of sale refund screen now ignores barcode scanner input when staff are entering refund quantities. This prevents scanned product codes from being mistaken for refund amounts, avoiding incorrect refund quantities and confusing error messages.
Original PR description
Steps to reproduce: - Have a paid order with a product ordered once - Open the ticket screen, select that order and its line - Scan a product barcode with a keyboard-wedge scanner Issue: "Maximum…
Steps to reproduce: - Have a paid order with a product ordered once - Open the ticket screen, select that order and its line - Scan a product barcode with a keyboard-wedge scanner Issue: "Maximum Exceeded - The requested quantity to be refunded is higher than the ordered quantity. 6 is requested while only 1 can be refunded." When the line holds enough quantity no dialog is shown at all and a refund quantity taken from the barcode is silently set. Cause: A keyboard-wedge scanner types the barcode as a burst of keystrokes. The number buffer discards such bursts by waiting barcodeService.maxTimeBetweenKeysInMs before handling the keys it collected and dropping any batch of more than two, but only when its holder asks for it with `useWithBarcode`. TicketScreen never set the flag, so its buffer handled every keystroke on its own and the digits of the barcode reached _setToRefundDetail as the refund quantity. ProductScreen, OrderSummary and PaymentScreen all set it. Fix: Set `useWithBarcode: true` on the ticket screen number buffer. Since the keys are now handled with a delay, capture the buffer before the selected order or orderline changes, so that a keystroke is applied to the line that was selected when it was typed and not to the next one. opw-6465148 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283993 Forward-Port-Of: odoo/odoo#281962
Tax returns will no longer be incorrectly marked as Paid when users reply to or send normal chatter messages. Payment finalization now only happens from the intended tax payment instructions flow, reducing accidental status changes and improving reporting accuracy.
Original PR description
Before this fix: Replying to or sending a message from the chatter of a tax return could incorrectly change its state to Paid. This happened because action_send_mail() automatically called _action_finalize_payment() for account.return records. After this fix: Payment finalization only happens when the composer is opened from the tax payment instructions flow. Normal chatter messages and replies will no longer change the tax return state to Paid. task-6469536 Forward-Port-Of: odoo/enterprise#129161
This update prevents PDF Quote generation from failing when Quote Builder documents include dynamic fields. It improves reliability for sales teams using quote templates, especially in newer Python and PDF library environments.
Original PR description
Issue: --- Due to this issue, generating PDF Quote using Quote Builder with dynamic fields leads to a traceback. This was partially fixed by: e16edc7b0d9f56cb7769068ecf7d64ff8b0f6359 Steps: --- 1-…
Issue: --- Due to this issue, generating PDF Quote using Quote Builder with dynamic fields leads to a traceback. This was partially fixed by: e16edc7b0d9f56cb7769068ecf7d64ff8b0f6359 Steps: --- 1- Using a python 3.13 env, install requirements.txt. (You could instead uninstall pypdf2 and install pypdf==5.4.0) 2- Enable Quote Builder. 3- Create a SO and in quote builder tab, select a document. This document should have dynamic fields. e.g. you could use`Office Furnitures Header` document. 4- Print -> PDF Quote. Cause: --- In previous fix, we fixed the traceback when no dynamic field is set. However, if you have a dynamic field, then inside `PdfWriter._update_field_annotation()`, the font is get from `DR` dict inside acroform: https://github.com/py-pdf/pypdf/blob/f20954f2241640feb484800e191373f8fbdfa44b/pypdf/_writer.py#L917-L929 Even if we are not setting a font, we need to have an empty `DR` dict inside acroform, in order to avoid calling `get` on a none object, which is leading to the traceback. Also in previous fix we were losing `NeedAppearances` inside `AcroForm` by creating new dict which wasn't right. opw-6392001 Forward-Port-Of: odoo/odoo#278881
This fixes an issue where checks created from check templates no longer appeared on matching tax returns. Accounting users can again see the expected compliance checks when generating and reviewing tax returns.
Original PR description
commit introducing the issue: https://github.com/odoo/enterprise/commit/034e0157eed0f19471879ca133faa40b7f0aabf3 Since this commit, it's no longer possible to see a check defined from a check template in a tax return. Steps to reproduce: - Go to Accounting / Configuration / Checks - Create a new one, give it a name, a random cycle and assign it a to a tax return - Open the tax returns view, generate them, and open a tax return of the same type -> The check should be visible Forward-Port-Of: odoo/enterprise#129707
New onsite learning events created from the Onsite view or an employee resume now remain visible immediately after they are saved. This prevents users from thinking their newly created training events disappeared and ensures the current employee is registered automatically where needed.
Original PR description
Onsite events created from the "Onsite" view or the employee resume selector do not appear immediatlely after creation This occurs because currently the domain for onsite events requires that the…
Onsite events created from the "Onsite" view or the employee resume selector do not appear immediatlely after creation This occurs because currently the domain for onsite events requires that the event to have multiple slots as well as to have at least one employee registered to it. Therefore, newly created records often fail these criteria and remain hidden. In further versions, this pr: https://github.com/odoo/odoo/pull/246285/ changes the domain of the event selector in the employee resume by removing the dependency on the multiple slots and filtering by the specific employee for registration. This change is not stable to backport as it indroduces the `employee_id` field as an invisible field in the xml to be able to compare in the domain. This commit partly changes both domains to not require the multiple slots anymore, while still showing all events for which an employee is registered. This commit also ensures that when an event is created from the Onsite view or selector, the current user's employee will be registered to it. Steps to reproduce - Go to employees->Learning->Onsite - Select New and create an event - Go back to Onsite Courses - You will not see the created event (unless it is multi_slot and an employee was registered) opw-5915686 Forward-Port-Of: odoo/odoo#284200 Forward-Port-Of: odoo/odoo#258952
This fix prevents Odoo Inventory from replacing a delivery operation's custom customer destination location with the generic Customers location when a contact is selected. Businesses using specific customer stock locations keep the intended transfer destination, reducing manual corrections and delivery errors.
Original PR description
### Steps to reproduce: - In the settings: Enable Storage Locations - Create a customer location "Customer stock" with "Customers" as its parent location - Create a delivery operation type "Deliver…
### Steps to reproduce: - In the settings: Enable Storage Locations - Create a customer location "Customer stock" with "Customers" as its parent location - Create a delivery operation type "Deliver Super Customer" and set its default destination location to "Customer stock" - Go to Inventory > Overview > Deliver Super Customer > New - Set a contact on the transfer #### > The destination location switches from "Customer stock" to "Customers" ### Cause of the issue: The `location_dest_id` of `stock.picking` depends on its `partner_id`. So that changing the partner recomputes the locations of the transfer. However, as soon as the destination of the operation type has a `customer` usage, the `property_stock_customer` of the contact replaces it unconditionally: https://github.com/odoo/odoo/blob/04f3a7bca99d0144a4ea871be9625db368b196ca/addons/stock/models/stock_picking.py#L949-L963 However, the `property_stock_customer` falls back to an `ir.default` pointing at the default `Customers` location when nothing is set on the contact: https://github.com/odoo/odoo/blob/1c40fab04b71def8f3645c4c4bb0c1441057f307/addons/stock/data/stock_data.xml#L71-L72vs The override comes from 8a0775aa1dd9, which replaced an `elif` fallback on the contact by an "unconditional" substitution as this fallback had become unreachable once `default_location_src_id` and `default_location_dest_id` were made required: https://github.com/odoo/odoo/blob/04f3a7bca99d0144a4ea871be9625db368b196ca/addons/stock/models/stock_picking.py#L34-L41 opw-6421090 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283886 Forward-Port-Of: odoo/odoo#280016
This fixes an issue where confirming a sales order again after cancellation could fail to create the expected event registration. Businesses can now rely on event attendee records being recreated correctly when an order is returned to quotation and confirmed again, including portal confirmation flows.
Original PR description
**Steps to reproduce:** - Create a SO and add the Event Registration - Standard product and specify an event - Confirm the SO then select "Create/Update registrations" - Cancel the SO, then "Set to…
**Steps to reproduce:** - Create a SO and add the Event Registration - Standard product and specify an event - Confirm the SO then select "Create/Update registrations" - Cancel the SO, then "Set to Quotation" - Select "Preview" and confirm the Sale Order again - There will not be any new registration created when there should be one. **Behavior:** Usually when a sale order is confirmed the `action_sale_order_event_registration` form will be opened which when filled correctly creates registrations. However in certain cases: confirming from the customer portal, or simply closing the form when it is opened, will not trigger `action_make_registration` which creates registrations if it is not already the case `action_confirm()` should be creating the registrations correctly on its own anyway by calling ´init_registrations()´ : https://github.com/odoo/odoo/blob/beed378cde592bc96c1e79a976ac775264b843ed/addons/event_sale/models/sale_order_line.py#L49-L66 This function tries to create each missing registrations by looking at the amount in the so_line and deducting the already created registrations, however since some of them can be cancelled, this computation is wrong. And leads to registration not being created when they should. opw-6444127 Forward-Port-Of: odoo/odoo#280426
This fixes an issue in Documents where clicking inside the "Search More..." selection window could unexpectedly close it. Users can now sort, resize columns, and select contacts or other related records from the popup without losing their current document selection.
Original PR description
Steps to reproduce: 1. Install Documents 2. In the Documents list view, select a document to display the inspector. 3. Edit a field such as Owner or Customer which uses a Many2one widget. 4. In the…
Steps to reproduce: 1. Install Documents 2. In the Documents list view, select a document to display the inspector. 3. Edit a field such as Owner or Customer which uses a Many2one widget. 4. In the field dropdown, click "Search More..." to open a modal dialog. 5. Click inside the "Search More..." modal (e.g., to sort columns or resize headers). Issue: - The modal dialog immediately closes, and the contact cannot be selected. Root cause: - When an inspector field is edited, the record row is put into edit mode. While in edit mode, the documents list renderer listens for global clicks. Clicking inside the "Search More..." modal dialog targets elements that have `.o_list_renderer` (since the modal dialog renders a list view). Because the click target is within a list renderer but is not a document row, `DocumentsListRenderer.onGlobalClick` executes and clears the selection of the main list view. Clearing the selection unmounts the edited field in the inspector, thereby destroying the modal dialog stack. Solution: - Modify DocumentsListRenderer.onGlobalClick to scope click handling to the current Documents list renderer. Ignore clicks outside this.root.el, so interactions in nested UI such as Search More... do not clear the main selection and destroy the inspector field. opw-6253360 Forward-Port-Of: odoo/enterprise#128812 Forward-Port-Of: odoo/enterprise#119262
Fixed an issue where confirmed manufacturing orders in warehouses using a 3-step process were not counted in stock forecasts. This helps planners see incoming finished goods correctly and avoid unnecessary replenishment or purchasing decisions.
Original PR description
### Steps to reproduce: - In the settings enable Multi-Steps Routes - Put your warehouse in manufacture in 3 steps - Create a storable product P - Create and confirm an MO for 1 unit of P - Go to…
### Steps to reproduce: - In the settings enable Multi-Steps Routes - Put your warehouse in manufacture in 3 steps - Create a storable product P - Create and confirm an MO for 1 unit of P - Go to Inventory > Operations > Procurement > Replenishment - Create a new one for P in WH/stock #### > The forecasted quantity in stock is still 0 but should be at 1 ### Cause of the issue: This is the exact use case already fixed in 85dd3369ed17b98b2ce485be04f140cf4cfa8aa3, which stamped the finished move with a `location_final_id` pointing at WH/Stock so that the move contributes to the forecast there even though its `location_dest_id` is the intermediate WH/Post-Production. That fix was reverted in practice by 42275f83dc5350822a625e19d65148e8b41ab1d4, which replaced the value with `mo.location_dest_id`: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_move.py#L466-L467 Its reasoning was that in a single-warehouse setup `location_dest_id` equals the warehouse stock location, so the behaviour would be unchanged. That holds in 1 and 2 steps, where the extra step is on the component side and only moves `default_location_src_id` to the pre-production location. It breaks in 3 steps, the only mode that also moves `default_location_dest_id`, to the post-production location: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L246-L247 and `_compute_locations` propagates it to the MO: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/mrp_production.py#L334-L341 WH/Post-Production is a sibling of WH/Stock under the warehouse view location, not a child of it. Since `location_final_id` takes precedence over `location_dest_id` for the non-done part of the move chain: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L331-L334 the finished move stopped being counted in the WH/Stock forecast. Why the test did not catch it: `test_3_steps_manufacturing_forecast` stayed green through the whole regression, because it scoped `virtual_available` with a `location_id` context key. `_get_domain_locations` only reads `location` and `warehouse_id`; `location_id` is silently ignored: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L284-L287 The call therefore fell through to the branch scoping the forecast to every warehouse view location: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/product.py#L303-L309 and the warehouse view location is the common parent of both WH/Stock and WH/Post-Production. The assertion held regardless of where `location_final_id` pointed, so the test was a false positive from the start: it also passes with 85dd3369ed17b98b2ce485be04f140cf4cfa8aa3 fully reverted. Using the `location` key makes it fail without the fix and pass with it. ### Fix: Neither fix proposition was right on its own; each one was correct only in its own scenario. The`mo.warehouse_id.lot_stock_id` resolves the warehouse from the components, so it points at the wrong warehouse as soon as the finished product is produced for another one. `mo.location_dest_id` is the post-production location as soon as the warehouse manufactures in 3 steps, so it drops the quantity from the forecast of the manufacturing warehouse itself. What separates the two is not the warehouse but whether the destination is a transit step. In 3 steps the finished product only reaches the stock through the post-production push rule: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L57 so the final location is the stock of the warehouse owning that destination. Any other destination is already final and is kept as is, which leaves cross-warehouse MOs and destinations set to a sub-location of the stock untouched. The rule's destination is read rather than `warehouse.lot_stock_id` because a push move takes its destination from the rule and not from the operation type: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/stock_rule.py#L256-L260 so the forecast stays correct when the store step is reconfigured to land somewhere else than the warehouse stock. The rule is looked up on `pbm_route_id` by its `picking_type_id` instead of through `warehouse.sam_rule_id`, because that field is no longer set. It used to be an entry of `_generate_global_route_rules_values`, and it is that entry which made the generic warehouse machinery create the rule and store it back on the warehouse: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/stock/models/stock_warehouse.py#L403-L410 11e69870db1c49d9a6af79ffd263e4e162b34b6b removed it when the post-production step stopped being a pull rule on the Manufacture route and became a push rule generated from `get_rules_dict`. Only the field declaration was left behind, and nothing writes it any more: https://github.com/odoo/odoo/blob/6a56908e5febfdb4e6e0eacfb8655b092319819f/addons/mrp/models/stock_warehouse.py#L21-L22 so reading it would silently give an empty recordset. The lookup is not delegated to `_get_push_rule` to avoid a search per finished move. opw-4882390 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283952 Forward-Port-Of: odoo/odoo#283234
Fixed an issue where AI-related dialogs, popovers, and other overlays could appear behind the Mass Mailing fullscreen editor. This keeps AI tools usable while editing mailings in fullscreen mode.
Original PR description
When using AI features from the Mass Mailing fullscreen editor, dialogs, popovers, and other overlays are displayed behind the editor, making them unusable. This regression is caused by the AI module overriding the z-index of the shared overlay classes with values lower than those required by the Mass Mailing fullscreen editor. Since the AI assets are loaded after the Mass Mailing assets, their CSS rules take precedence, causing all overlays using these shared classes to be rendered below the fullscreen editor. Raise the AI z-index values from the sticky layer to the offcanvas layer so the shared overlay classes preserve the correct stacking order when the Mass Mailing editor is displayed in fullscreen mode. task-6412411
Standard timesheet users can now access Assistant Rules when the assistant feature is enabled, even if Billing Rate Indicators are disabled. This prevents users from needing debug mode or administrator rights to reach configuration options required for their work.
Original PR description
Steps to reproduce --- 1. Install sale_timesheet_enterprise. 2. Go to Timesheets > Configuration > Settings (as Admin). 3. Disable the "Billing Rate Indicators" setting and enable timesheet…
Steps to reproduce --- 1. Install sale_timesheet_enterprise. 2. Go to Timesheets > Configuration > Settings (as Admin). 3. Disable the "Billing Rate Indicators" setting and enable timesheet assistant. 4. Log in as a normal user with "Timesheets > Own timesheet" access. 5. Open the Timesheets app. Issue --- The "Configuration" menu is completely hidden. The regular user cannot access the "Assistant Rules" menu unless they turn on debug mode. Cause --- When the billing rates feature is turned off, the _load_menus_blacklist function hides the Enterprise Configuration menu. The logic used an and condition, meaning the menu was only kept visible if the user was an Assistant AND had Manager/Admin rights. This locked out standard users. Fix --- Change the blacklist condition. Allow the Enterprise Configuration menu to stay visible if the user needs it for the Assistant feature (when UoM is not Days), OR if the user is a Manager/Admin. References to check other issues https://github.com/odoo/enterprise/pull/120609 https://github.com/odoo/enterprise/pull/117984 task - 6470183 Forward-Port-Of: odoo/enterprise#128096
This fixes an issue where manually adjusted prices on optional quotation items were overwritten when customers changed quantities in the portal preview. Sales teams can now rely on custom negotiated prices remaining unchanged unless intentionally updated.
Original PR description
When a user manually sets a price on an optional line (overriding the pricelist), and then changes the quantity in the portal preview, the manual price is lost and gets reset to the pricelist price.…
When a user manually sets a price on an optional line (overriding the pricelist), and then changes the quantity in the portal preview, the manual price is lost and gets reset to the pricelist price. Steps to reproduce: --- - Install Sales module and enable Pricelists. - Create a product with qty-based pricelist rules: - min qty: 1 → price: 100 - min qty: 10 → price: 80 - Create a quotation with an optional section containing this product. - Manually change the product price to 150 (overriding pricelist). - Mark the section as optional and preview the quotation. - Change the quantity to 10 in portal preview. Issue: --- - The manually set price (150) is incorrectly reset to the pricelist price (80). Root cause: --- - After [commit], if there is no config parameter set and if there is an active pricelist, we simply call `_reset_price_unit()` without checking whether the price was manually set or not. - Additionally, `_reset_price_unit()` calls `update()` which writes `price_unit` and `technical_price_unit` one by one as separate `write()` calls. The `write()` method has a guard([1]) that strips a lone `technical_price_unit` write unless `sale_write_from_compute` is set in context. Without this flag, `technical_price_unit` is silently discarded, causing it to drift from `price_unit`. On the next qty change, this mismatch is detected as a manual price, permanently blocking further pricelist updates. Solution: --- - Check whether the price was manually set before calling `_reset_price_unit()`, and pass `sale_write_from_compute=True` in context so both `price_unit` and `technical_price_unit` are written correctly. [commit]: https://github.com/odoo/odoo/commit/93b6bdd6a4909bc0b45b90ab6a2d0734a218292d [1]https://github.com/odoo/odoo/blob/fffd987cc98d1ea0cd04e24dda2ed8b64a219cdc/addons/sale/models/sale_order_line.py#L1391-L1399 opw-6426634 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281107
Closed or renewed subscriptions will no longer appear in Orders to Invoice just because prepaid recurring lines were still marked for billing. This prevents misleading invoice prompts while still allowing billing for postpaid items that were already delivered before closure.
Original PR description
When a closed subscription could still show up in the Orders to Invoice because its recurring lines kept their "to invoice" status. This was misleading since no further period should be billed. When a subscription is churned (or renewed), prepaid lines are now flagged as nothing to invoice. Postpaid lines are left as is so that already delivered products can still be billed after closing. task-6227787 Forward-Port-Of: odoo/enterprise#117797
Customers can no longer set optional products on sales orders to negative quantities through the portal. This prevents confusing or invalid order changes and keeps exceptional quantity adjustments under salesperson control.
Original PR description
Since the fusion of `sale.order.option` model into `sale.order.line` model, the optional products (editable from portal) lines are not deleted when reaching a quantity of 0 or below. This could allow some customers to set negative quantities, which makes no sense as it's only something that should be set by the salesman if necessary. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#284493
Belgian employee departures now avoid unintended archiving when no archive date is set. Future leave requests are refused rather than deleted, and company car assignments remain recorded, preserving important HR history and reducing accidental data loss.
Original PR description
Before: * Belgian employees without an "Archive Employee On" date were automatically archived after their departure. * Future leaves were deleted when applying an employee departure. * The employee's company car was automatically unassigned on departure. After: * Belgian employees without an "Archive Employee On" date are no longer automatically archived. * Future leaves are refused instead of deleted. * Company car assignments are kept when applying a Belgian employee departure. * Display "Don't archive" when no archive date is set. Impact: * Prevents unintended archiving and preserves future leave and company car information for Belgian employees. Task: 6453718 Forward-Port-Of: odoo/enterprise#127414
Belgian payroll now correctly applies an employee's default private fuel card use to payslips when eligible. This ensures the related benefit in kind is categorized properly for social security and withholding calculations, reducing payroll calculation errors.
Original PR description
FUEL_CARD_PRIV never fired: nothing copied the employee's fuel_card_personal_use default into the payslip's property input. Fixed by seeding it in _compute_input_line_ids(), gated on fuel_card set, no company car, no mobility budget. FUEL_CARD_PRIV is also a Benefit in Kind (ONSS + withholding), like ATN.INT, but was missing the BIK category. Added it. Task 6469003 Forward-Port-Of: odoo/enterprise#128789
Submitting a tax report opened from a return now targets the exact return shown on screen, including Dutch VAT corrections. This prevents users from accidentally submitting or marking the original return instead of the intended correction, improving compliance accuracy.
Original PR description
Opening a tax report from a return and submitting it could act on a different return than the one on screen. _get_return_from_report_options searches by company, period and report with limit=1, but…
Opening a tax report from a return and submitting it could act on a different return than the one on screen. _get_return_from_report_options searches by company, period and report with limit=1, but that combination is not unique: l10n_nl declares two return types on l10n_nl.tax_report, nl_tax_return_type and nl_tax_correction_return_type, so a VAT return and its correction both match. Which one is returned is then decided by _order (is_completed, date_deadline, name, id). For a Dutch VAT correction it resolves to the original VAT return of the same quarter, so send_xbrl submits and flags that record instead of the correction. The options already carry the return type they were built for, in the return_periodicity filter, so restrict the search to it when it is set. l10n_nl_reports kept a return_id option for the same reason when computing the already declared amount of a suppletie; it can use _get_return_from_report_options now. opw-6421300 Forward-Port-Of: odoo/enterprise#129278 Forward-Port-Of: odoo/enterprise#127073
Employee departures no longer have to erase future leave requests when local rules require those records to remain available. Approved leave that overlaps the departure date is still cancelled, while confirmed future leave can be refused instead of deleted, preserving history without changing the default behavior for other cases.
Original PR description
Before: * Future leaves were deleted when an employee departure was applied. * There was no way for localizations to preserve these leaves when they should remain in the employee's history. After: * Add `refuse_future_leaves` to `_cleanup_employee_departure_leaves()`. * Approved leaves are still cancelled when they extend beyond the departure date. * Future confirmed leaves can now be refused instead of deleted when requested by a localization. * Keep the existing deletion behavior by default for other localizations. Impact: * Allows localizations to preserve future leave records while keeping the existing generic departure behavior unchanged. Task: 6453718 Forward-Port-Of: odoo/odoo#281493
This fixes an issue where choosing “Do not ask me again” for IoT Box printing caused the next report to download as a PDF instead of printing. Users can now rely on their saved preference to send documents directly to the intended printer.
Original PR description
Since odoo/enterprise#113128, when a user prints a report through the IoT Box and enables the "Do not ask me again" checkbox, the next print: - before: it downloads the pdf instead of printing it, - after: it correctly prints the document. Forward-Port-Of: odoo/enterprise#129658
Fixes an accounting issue where undoing a payment could place the reversing cash basis tax entry in the current period instead of the original tax period. This keeps tax reports balanced in the intended period, avoiding misleading amounts across reporting months.
Original PR description
When unreconciling a payment from an invoice with a cash basis tax, the tax cash basis (CABA) entry is reversed. The reversal is supposed to land in the same period as the origin entry so the tax…
When unreconciling a payment from an invoice with a cash basis tax, the tax cash basis (CABA) entry is reversed. The reversal is supposed to land in the same period as the origin entry so the tax report nets to zero for that period. Steps to reproduce: - Enable cash basis and create a cash basis tax (exigibility on payment) - Post an invoice dated in the past with that tax - Reconcile a bank statement line to the invoice - Resequence the cash basis entry so the month is dropped from the name (CABA/08/2026/0001 -> CABA/2026/0001) - Unreconcile the statement line Issue: The reversal CABA entry created on the last unreconcile is dated today instead of the origin entry's month. In the tax report the original tax amount stays in the statement's month while the reversal amount appears in the current month, so the two no longer cancel out. Analysis: While under a monthly journal sequence a past date returns the last day of that month, under a yearly sequence a past date within the current year returns the latter between the move date and today, moving the reversal out of the origin period. opw-6301553 Forward-Port-Of: odoo/odoo#284840 Forward-Port-Of: odoo/odoo#281250
Invoice sequence gap warnings now check each suffix-based sequence separately. This prevents invoices from being incorrectly marked as having missing numbers when similarly numbered invoices with different suffixes exist, improving accounting clarity and audit confidence.
Original PR description
### Issue: When moves share the same `sequence_number` in a journal but have different suffixes (e.g. `INV/2026/00010` and `INV/2026/00010A`), the `made_sequence_gap` flag was incorrectly set ###…
### Issue: When moves share the same `sequence_number` in a journal but have different suffixes (e.g. `INV/2026/00010` and `INV/2026/00010A`), the `made_sequence_gap` flag was incorrectly set ### Cause: `_update_sequence_made_gap`, introduced in commit https://github.com/odoo/odoo/commit/17893089e8b21c0ecab5e61ed8e2c33f3731b3ac selects the previous and next moves ordered by `sequence_number` without filtering by suffix This causes two issues: - Moves from different suffix sequences are used as neighbors, leading to incorrect gap detection - Duplicate `sequence_number` values across suffixes are not accounted for, so only one move is considered per number ### Steps to reproduce: - Install `account` - Post 12 invoices to get a sequence up to `INV/2026/00012` - Reset `INV/2026/00012` to draft, rename it to `INV/2026/00010A` - Reset `INV/2026/00010A` to draft, rename it to `INV/2026/00009A` and confirm Before the fix: `INV/2026/00011` is red Expected: `INV/2026/00011` should not be red because `INV/2026/00010` exists - Delete `INV/2026/00009` Before the fix: `INV/2026/00010` is red Expected: `INV/2026/00010` should be red (gap in no-suffix sequence) - Reset `INV/2026/00009A` to draft and confirm it again Before the fix: `INV/2026/00010` is not red Expected: `INV/2026/00010` should still be red (different suffix) ### Notes: Suffix changes are treated as distinct sequences following the same gap rules as any other sequence This was agreed with R&D — the gap flag is meant to signal inconsistencies within a sequence, not across suffixes opw-6454823 Forward-Port-Of: odoo/odoo#282503
This fix ensures costs linked to projects are matched to the right sales order for reinvoicing, even when projects share accounting links or multiple analytic accounts are involved. This helps prevent missed reinvoiceable items and improves billing accuracy.
Original PR description
### Before this fix --- The `_get_so_mapping_from_project()` method returns a mapping where the key is the move line ID and the value is a `sale.order` record (or `None`). Because of the issues…
### Before this fix
---
The `_get_so_mapping_from_project()` method returns a mapping where the key is
the move line ID and the value is a `sale.order` record (or `None`).
Because of the issues described below, a valid `sale.order` could be available
for reinvoicing, but the corresponding move line might still not be mapped to
that sale order. As a result, the move line is not added to the reinvoiceable
sale order.
However, the implementation has two issues:
#### 1. Projects are overwritten when they share the same analytic account
`project_per_accounts` is built as a dictionary mapping an analytic account ID
to a single project. If multiple projects reference the same analytic account,
each new assignment replaces the previous one. As a result, only the last
project associated with a given analytic account is retained.
**Example:**
* Analytic Account **AA1** is linked to **Project A** and **Project B**.
* The dictionary becomes `{AA1: Project B}`.
* **Project A** is lost, even though it also references **AA1**.
**Steps to reproduce:**
1. Create an analytic account **AA1**.
2. Create **Project A** and **Project B**, both linked to **AA1**.
3. Create **Sale Order SO1** linked only to **Project A**.
4. Create a vendor bill (or expense) that generates an AML using **AA1** for a
product configured with **Reinvoice Costs = At Sales Price**.
5. Validate the document.
**Expected behavior:**
The product should be added to **SO1** for reinvoicing.
**Actual behavior:**
The move line is not mapped to **SO1**, so no sale order line is created.
#### 2. Previously found projects are overwritten during iteration
The `project` variable is reassigned on every iteration of the loop. After the
loop completes, it only contains the project (or lack of one) corresponding to
the last processed analytic account. This can cause valid projects found earlier
in the loop to be discarded.
**Example:**
* Move line has analytic accounts **AA1** and **AA2**.
* **AA1** maps to **Project A**.
* **AA2** has no linked project.
* After the loop, `project` is `None`, even though **Project A** was found.
**Steps to reproduce:**
1. Create analytic accounts **AA1** and **AA2**.
2. Create **Project A** linked to **AA1** only.
3. Create **Sale Order SO1** linked to **Project A**.
4. Create a vendor bill (or expense) whose AML is distributed between **AA1**
and **AA2**, where **AA2** is processed after **AA1**.
5. Validate the document.
**Expected behavior:**
The move line should still be mapped to **SO1** because **AA1** references
**Project A**.
**Actual behavior:**
The last processed analytic account (**AA2**) overwrites the previously found
project, causing the move line not to be linked to **SO1**.
### After this fix
---
* `project_per_accounts` stores **all** projects associated with each analytic
account instead of keeping only the last one.
* The project lookup preserves all valid project candidates instead of
overwriting previously found results during iteration.
* As a result, the method can resolve the related `sale.order` in more cases,
improving the overall accuracy of the mapping.
> **Note:** This change prevents valid project associations from being lost
> when multiple projects share an analytic account or when multiple analytic
> accounts are processed for the same move line.
**OPW:** 6294615
Forward-Port-Of: odoo/odoo#277110Shipping costs are now recalculated when a returning shopper confirms a cart after product prices have changed. This prevents orders from incorrectly keeping free shipping when the updated cart total no longer qualifies, while preserving pickup location selections for click-and-collect orders.
Original PR description
Steps to reproduce ================== 1. Configure a delivery method with free shipping above a threshold 2. Add a product to the cart above that threshold, select the delivery method and leave the…
Steps to reproduce ================== 1. Configure a delivery method with free shipping above a threshold 2. Add a product to the cart above that threshold, select the delivery method and leave the cart unfinished 3. Lower the product price below the threshold 4. Recover the cart and confirm the order from /shop/checkout => The product prices are refreshed, but shipping stays free although the new total is below the threshold. Root cause ========== Since [1], nothing re-rates the carrier after /shop/confirm_order refreshes the cart prices: the delivery method is selected before the confirmation. In 17.0, the payment page auto-clicked the selected carrier on load, which re-rated the shipping cost and masked the issue. Fix === Re-rate the selected delivery method in `shop_confirm_order` after the prices have been recomputed, as `_cart_update` already does. [1]: https://github.com/odoo/odoo/commit/8e2b6cede55b51f7ccdbe7601aa7e6035fd6f9fe opw-6383849 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#283684 Forward-Port-Of: odoo/odoo#276905
This fix prevents errors when retrying rejected Saudi simplified consumer invoices with ZATCA. The system now regenerates the required QR code from the invoice submission data during retry, helping businesses resubmit invoices without manual intervention while keeping existing QR display rules unchanged.
Original PR description
- When a simplified (B2C) invoice is rejected by ZATCA and subsequently retried, its state remains rejected. The QR code computation therefore returns an empty value for the rejected invoice, as the computation is primarily intended for the post-EDI state. During the retry, this results in a traceback when the QR code is applied to the XML. - Regenerate the QR code directly from the submission data when preparing a new B2C XML, instead of relying on the state-dependent QR code field. This preserves the existing QR visibility rules for the final invoice. task-6485555 Forward-Port-Of: odoo/odoo#283534
Invoices created from Chilean Point of Sale orders are now automatically submitted to SII as expected. This prevents businesses from having valid PoS invoices left unsent after closing the register, reducing manual follow-up and compliance risk.
Original PR description
Issue: Invoices from PoS orders are not automatically send to SII. Steps to reproduce: - Open PoS - create an order - add a company as customer - pay - close register - go to invoice Current behavior: Invoice is created but not sent Expected behavior: Invoice is created and send to SII. Cause: Before 19.2 invoices were sent using a cron. Starting from 19.2, invoices are sent using the Send button of the invoice form. In order to get a perf improvement, PDF generation was deactivated for l10n_cl PoS invoices at creation. However, the same method used to generate the PDF is used to send the invoice to SII. Therefore, invoices from PoS were not sent to SII. opw-6423528 Forward-Port-Of: odoo/enterprise#126623
Unreconciling one bank statement line from an invoice or bill now only removes that specific reconciliation instead of clearing all related reconciliations. This prevents invoices and bills from being accidentally fully unreconciled when only one bank transaction needs correction.
Original PR description
**STEP TO REPRODUCE** 1. Create a bill or an invoice. 2. Create multiples bank statement. 3. Reconciles those bank statements to the invoice/bill. 4. Unreconciles one of those bank statement on the invoice/bill. 5. Notice the invoice/bill is completely unreconciled. Expected behavior: only the unreconciled line should be unreconciled. **CAUSE** When unreconciling a partial linked to a bank statement, we call `delete_reconciled_line()` on both `partial.credit_move_id` and `partial.debit_move_id`. One on those is the the payment_term line of the invoice/bill the bank statement line is reconciled with. This payment_term line is also linked to all partial reconcilliation line on the invoice/bill, so calling `delete_renconciled_line()` delete all the reconciled line of the invoice/bill. **FIX** We should call `delete_renconciled_line()` only on the bank statement move line, not on the payment term line. opw-6465096 Forward-Port-Of: odoo/enterprise#128126
This fix ensures Sendcloud shipping label requests keep the selected label format, such as ZPL or PDF, instead of being unintentionally reset. Businesses using non-PDF label printers should receive the correct label type again, reducing manual work and printing issues.
Original PR description
We send the label type we want to get (zpl, pdf, ...) in the request headers. However, since odoo/enterprise#115999, we also send the partner ID in the headers. This was overriding the headers passed to the method, making Sendcloud always return a PDF label. Forward-Port-Of: odoo/enterprise#129542
Fixed an issue where AI chat image generation could fail because the system looked for missing provider information. The provider is now determined from the selected model, allowing image requests in chat to complete reliably.
Original PR description
In the changes introduced in [this forward port](https://github.com/odoo/enterprise/pull/128330), `_ai_tool_generate_image` tool call reads `tool_context['provider']`, but that key is never set when `tools_context` is built. Any request to the agent chat that triggered image generation raised `KeyError('provider')`.
Derive the provider from the model name instead, the same way the rest of the codebase does in saas-19.3.
related PR: odoo/enterprise#128330
Forward-Port-Of: odoo/enterprise#129562POS managers can now open and manage payment method settings without running into an access error. The change gives them the needed read-only access to online payment provider information while keeping the field hidden from users who should not use it.
Original PR description
Only admin users have read access to the `payment.provider` model. Opening the PoS payment method form as a non-admin would raise an access error because the `online_payment_provider_ids` many2many field tries to fetch `payment.provider` records on form load. Grant read-only access on `payment.provider` to `group_pos_manager` so POS admins can use the field. Restrict the field's group in the form view to `point_of_sale.group_pos_manager,base.group_system` so it is not rendered for users without either role. opw-6208656 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#277256 Forward-Port-Of: odoo/odoo#263837