Friday, September 18, 2026
24 changes · saas-19.4
Enhancements to existing features
Stock availability searches now run much faster when filtering products by free quantity, especially in large inventories. This reduces wait times and database load for users working with product and stock lists.
Original PR description
**Problem:** Searching on free_qty can be slow when there are many stock.move and stock.quant. The search method uses _compute_quantities_dict() to compute the free_qty based on stock.move and…
**Problem:** Searching on free_qty can be slow when there are many stock.move and stock.quant. The search method uses _compute_quantities_dict() to compute the free_qty based on stock.move and stock.quant, which is expensive. **Solution:** A similar field, qty_available, has a faster search method based on stock.quant only. This method can be adapted to support fast searching on free_qty by also aggregating the reserved_qty and subtracting it from the qty_available. **Perf Table:** Record: product.template, with ~20k stock.picking and most products w/o moves. |Record count|Time before|Queries before|Time after|Queries after| |------------|-----------|--------------|----------|-------------| |10k |2.14s |214 |370ms |45 | |50k |6.06s |644 |829ms |50 | |100k |11.42s |1196 |1.05s |46 | opw-6266096 Forward-Port-Of: odoo/odoo#287363 Forward-Port-Of: odoo/odoo#270434
Customers with unpaid invoices can now still add money to their account at the point of sale. This makes account deposits more flexible and avoids forcing customers to settle existing balances before adding funds.
Original PR description
"Deposit money" was only offered in the partner list when the customer had no due, so a customer with open invoices could not put money on their account without settling those invoices. Always offer it when a customer account payment method exists. opw-6514079 Forward-Port-Of: odoo/enterprise#131268
Resolved issues and error corrections
This fixes an issue where some values in quotation header or footer PDFs could disappear when generating a PDF Quote. Businesses using PDF quote templates with structured form fields will now see the expected information preserved in the final document.
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#287910 Forward-Port-Of: odoo/odoo#286186
Fixed an issue where opening Point of Sale orders for a company could show a blank screen if one of its delivery addresses had no name. Orders linked to unnamed delivery contacts now display correctly using the parent company name, helping staff access customer order history without disruption.
Original PR description
Steps to reproduce: - Create a company contact with a delivery address that has no name - Make a PoS order for the delivery address and pay it - Open the PoS, open the customer list, and click…
Steps to reproduce: - Create a company contact with a delivery address that has no name - Make a PoS order for the delivery address and pay it - Open the PoS, open the customer list, and click "Orders" on the company Issue: The screen goes blank. Cause: The ticket screen is opened with the company name as search term. The server domain searches on `partner_id.complete_name`, so the orders of the address contact are returned too. They are then fuzzy matched on `getPartnerName()`, which returns `false` for a partner without a name, and `fuzzyLookup` calls a string method on it. The error is raised during rendering, so the whole app is destroyed. Fix: Make `getPartnerName()` always return a string, and fall back on the parent name, like the complete name does. This way the orders of the address contact are still listed when searching on the company name, instead of being filtered out by an empty name. opw-6578541 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#288880
This fix ensures accounting entries are recalculated when external tax data changes, even if the invoice lines themselves are otherwise unchanged. It prevents mismatches between product lines and tax lines, helping keep tax amounts and totals accurate.
Original PR description
The account_external_tax module can modify extra_tax_data without modifying anything else on the lines. This results in: - a desync between the product lines and tax lines, - incorrect totals This makes sure we regenerate the appropriate tax lines and recompute the totals fields. opw-6547387 Forward-Port-Of: odoo/odoo#288717
This update refreshes the spreadsheet component with fixes that improve chart display, pivot table totals, and printing behavior. Users should see more accurate spreadsheet reports and fewer visual or output issues when working with charts, pivot data, and printed sheets.
Original PR description
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/a32d26bb0f [REL] 19.4.13 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0)…
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/a32d26bb0f [REL] 19.4.13 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/bd46cf2e0e [FIX] Package: saas-19.4 is no longer the latest stable branch [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/e921f97fdd [IMP] demo: debounce xml template build [Task: 6574005](https://www.odoo.com/odoo/2328/tasks/6574005) https://github.com/odoo/o-spreadsheet/commit/282235eaa2 [FIX] demo: fix scorecard demo definition [Task: 6573109](https://www.odoo.com/odoo/2328/tasks/6573109) https://github.com/odoo/o-spreadsheet/commit/f6c4679482 [FIX] Charts: Bubble chart background depends on color scheme [Task: 6448882](https://www.odoo.com/odoo/2328/tasks/6448882) https://github.com/odoo/o-spreadsheet/commit/ea1397ad84 [FIX] chart annotation: text input style [Task: 6533642](https://www.odoo.com/odoo/2328/tasks/6533642) https://github.com/odoo/o-spreadsheet/commit/651ee544aa [FIX] pivot: wrong running total with collapsed/hidden dimensions [Task: 6569607](https://www.odoo.com/odoo/2328/tasks/6569607) https://github.com/odoo/o-spreadsheet/commit/6332d219dd [FIX] pivot: fix scope of `pivot_html_renderer` css [Task: 6523521](https://www.odoo.com/odoo/2328/tasks/6523521) https://github.com/odoo/o-spreadsheet/commit/6e2465286d [FIX] print: empty pages when printing [Task: 6559815](https://www.odoo.com/odoo/2328/tasks/6559815) Co-authored-by: Florian Damhaut (flda) <flda@odoo.com> Co-authored-by: Anthony Hendrickx (anhe) <anhe@odoo.com> Co-authored-by: Alexis Lacroix (laa) <laa@odoo.com> Co-authored-by: Lucas Lefèvre (lul) <lul@odoo.com> Co-authored-by: Adrien Minne (adrm) <adrm@odoo.com> Co-authored-by: Ronak Mukeshbhai Bharadiya (rmbh) <rmbh@odoo.com> Co-authored-by: Dhrutik Patel (dhrp) <dhrp@odoo.com> Co-authored-by: Rémi Rahir (rar) <rar@odoo.com> Co-authored-by: Pierre Rousseau (pro) <pro@odoo.com> Co-authored-by: Vincent Schippefilt (vsc) <vsc@odoo.com> Co-authored-by: Marceline Thomas (matho) <matho@odoo.com>
Manufactured products now follow the correct storage rules even after components are unreserved and re-reserved. This prevents finished goods, such as lot-tracked products, from being placed in the wrong warehouse location.
Original PR description
Currently, when the user re-reserves a component and produces an MO for a lot-tracked product, putaway rules stop working. ## Steps to produce: - Install the Manufacturing application. - Go to…
Currently, when the user re-reserves a component and produces an MO for a lot-tracked product, putaway rules stop working. ## Steps to produce: - Install the Manufacturing application. - Go to Settings and enable Lots & Serial Numbers and Storage Locations. - Create a product named 'Laptop' and set it to be tracked by Lots. - Create a product named 'Graphics card' with some quantity on hand. - Create a Bill of Materials (BoM) for the Laptop with Graphics card as a component. - Create a location named 'Laptop Shelf' with 'WH/Stock' as its parent location. - Create a putaway rule: - When a product arrives in: WH/Stock - Product: Laptop - Store to: WH/Stock/Laptop Shelf - Create and confirm a Manufacturing Order (MO) for the Laptop. - Unreserve the components, open Details, and re-add the Graphics card. - Click Produce All and check the on-hand quantity list view of the Laptop. ## Issue: The Laptop is stored in WH/Stock instead of WH/Stock/Laptop Shelf. ## Root cause: When the user presses the unreserve button the finished move is passed to `_do_unreserve` https://github.com/odoo/odoo/blob/157874aad3aebef5bc9268de6e17530641107e31/addons/mrp/models/mrp_production.py#L2407 which unlinks all the move lines on finished product moves if they are not picked: https://github.com/odoo/odoo/blob/157874aad3aebef5bc9268de6e17530641107e31/addons/stock/models/stock_move.py#L1043 Now when the user presses the Produce All button, `button_mark_done` is called, which calls `_post_inventory` at: https://github.com/odoo/odoo/blob/a51ca77c825d2dba326dd540e2b4c04ef6c2385d/addons/mrp/models/mrp_production.py#L2236 `_post_inventory` assigns `lot_ids` to the finished product moves at: https://github.com/odoo/odoo/blob/157874aad3aebef5bc9268de6e17530641107e31/addons/mrp/models/mrp_production.py#L1928-L1930 This calls `_set_lot_ids` which creates a move line with quantity 1 at: https://github.com/odoo/odoo/blob/157874aad3aebef5bc9268de6e17530641107e31/addons/stock/models/stock_move.py#L679-L683 After that, `_post_inventory` sets the quantity for the finished product moves at: https://github.com/odoo/odoo/blob/157874aad3aebef5bc9268de6e17530641107e31/addons/mrp/models/mrp_production.py#L1935 Which calls `_set_quantity` and the delta quantity is now zero at: https://github.com/odoo/odoo/blob/157874aad3aebef5bc9268de6e17530641107e31/addons/stock/models/stock_move.py#L477-L483 Because `_set_lot_ids` has already created a move line that satisfies the move quantity, `_process_increase` is not called. Since `_process_increase` is not called, `_set_quantity_done` is never called at: https://github.com/odoo/odoo/blob/157874aad3aebef5bc9268de6e17530641107e31/addons/stock/models/stock_move.py#L463-L465 Since `_set_quantity_done` is not called, `_apply_putaway_strategy` is never called within that function at: https://github.com/odoo/odoo/blob/157874aad3aebef5bc9268de6e17530641107e31/addons/stock/models/stock_move.py#L2565 As a result, the laptop ends up in stock instead of on the shelf. This issue did not occur in lower versions because the feature to produce multiple lots was introduced in version 19.0 by the following commit: https://github.com/odoo/odoo/commit/4bb4e08066449177f89382718ceadd840ce90d0e Before this commit, only `_set_quantity` was called. Since no move line was created because lot_ids were not set in `_post_inventory`, `_process_increase` was called, which then called `_apply_putaway_strategy`, so the putaway rules were applied correctly. ## Solution: Apply putaway rules in the post-inventory function after the user manufactures a product, ensuring the items are placed in the correct locations and preventing products from being misplaced even when putaway rules are defined. opw-6517324 Forward-Port-Of: odoo/odoo#288739 Forward-Port-Of: odoo/odoo#287390
This fixes an issue where Point of Sale users on mobile could not see the list of search options when looking up orders. Mobile cashiers can now search orders by customer, date, receipt number, invoice number, or reference as intended, reducing friction when finding previous sales.
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#288671 Forward-Port-Of: odoo/odoo#286983
Sales order product selection now shows only the units of measure and packaging options that apply to the chosen product variant. This prevents sales users from accidentally selecting packaging that belongs to a different variant, improving order accuracy.
Original PR description
Issue Before This Commit: ======================== Currently, when a product variant has multiple UoMs, those UoMs appear for all variants when selecting a product in a Sale Order Line, even though…
Issue Before This Commit: ======================== Currently, when a product variant has multiple UoMs, those UoMs appear for all variants when selecting a product in a Sale Order Line, even though they are only available for a specific variant. Steps to Reproduce: ========================= 1: Install the sale and sale_management modules. 2: Enable Units of Measure and Packagings. 3: Create two variants, V1 and V2, and set an Extra Packaging of 'Pack of 6' on V2. 4: Add the product to a Sale Order Line. A product configuration wizard will open. 5: Select V2. The Unit and 'Pack of 6' UoMs are correctly displayed. 6: Select V1. Both UoMs are still displayed, even though 'Pack of 6' is not available for V1. Cause of the issue: ========================= When the variant is changed to V2 in the wizard, _get_basic_product_information is called. At that point, the condition is true, so available_uoms is updated with the UoMs available for V2. However, when the variant is changed back from V2 to V1, the method is called again, but V1 does not have multiple UoMs. Therefore, product_or_template._has_multiple_uoms() returns False, and available_uoms is not updated. As a result, the UoMs from V2 remain in available_uoms and are incorrectly displayed for V1. After This Commit: ======================== Update _get_basic_product_information to ensure that the available UoMs are updated whenever the variant changes. This ensures that only the UoMs available for the selected variant are displayed. Forward-Port-Of: odoo/odoo#288515
This fix prevents on-site payment options from being offered in checkout scenarios that involve gift cards or eWallets where they are not supported. It helps avoid customer payment flows that could fail or create incorrect order handling.
Original PR description
We don't want to support gift cards and eWallets in certain conditions opw-6483282 Forward-Port-Of: odoo/odoo#288180 Forward-Port-Of: odoo/odoo#286946
Bank synchronization no longer removes payment methods that users have already configured on a bank journal. This preserves custom incoming and outgoing payment options while still adding any missing defaults, reducing setup loss and manual rework after connecting a bank.
Original PR description
**Steps to reproduce:** - Install Accounting - Configure "Bank" journal: * Add several incoming payments * Add several outgoing payments - From Accounting dashboard, connect Bank (e.g. Odoo Bank Sync Demo) **Issue:** After bank sync, all the incoming/outgoing payments added on the Bank journal are removed. Only the default ones are re-created. **Solution:** Keep all the existing incoming/outgoing payments (i.e. payment method lines) and only create the default ones for the payment method types that don't exist. opw-6424407 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281658
This fixes an error that could crash Odoo when users checked the processing status of Romanian e-Factura documents. Businesses can now reliably retrieve invoice status from the Romanian SPV portal without interruption.
Original PR description
Steps to reproduce: - Send an invoice to the SPV (Romanian e-Factura). - Once the SPV finished processing it, click "Fetch Status" on the e-Factura document. - Odoo crashes with binascii.Error:…
Steps to reproduce: - Send an invoice to the SPV (Romanian e-Factura). - Once the SPV finished processing it, click "Fetch Status" on the e-Factura document. - Odoo crashes with binascii.Error: Incorrect padding Cause of the issue: _request_ciusro_download_answer extracts the signature file from the SPV zip answer and re-serializes it with etree.tostring(...), which gives plain XML bytes, not base64 But _l10n_ro_edi_fetch_invoice_sent_documents and the 3 other callers that persist that signature still ran it through base64.b64decode() before wrapping it in BinaryBytes (which already expects raw bytes, not base64). Decoding real XML as base64 only "works" by accident when the XML happens to contain a number of base64-alphabet characters that's a multiple of 4 after the invalid ones get silently stripped - otherwise it blows up with "Incorrect padding". This got introduced by 41fe2ebdb9cc (fields.Binary return BinaryValue), which flipped a base64.b64encode() call to base64.b64decode() at these 4 spots instead of just dropping the base64 call entirely. A previous fix (0750ee145ae2) already fixed the sibling issue on the invoice's own attachment_raw but missed these. Solution: Drop the erroneous base64.b64decode around the signature's attachment_raw at the 4 call sites, and fix the tests that were mocking attachment_raw as base64-encoded instead of raw bytes opw-6562123 Forward-Port-Of: odoo/odoo#288140
Users of the mail plugin can now stay signed in for a configurable period instead of needing to log in every day. The default token duration is seven days, reducing repeated login interruptions while keeping access limited to Outlook authentication endpoints.
Original PR description
Purpose ======= Allow customizing the expiration time of tokens, so users don't need to login everyday in the plugin. This is customized with a system parameter, with a default of 7 days. Those tokens are limited to endpoints `auth="outlook"`. Task-6466253 Forward-Port-Of: odoo/odoo#288572 Forward-Port-Of: odoo/odoo#287767
Call activities now display and use the properly formatted phone number for VoIP calls instead of falling back to the raw saved value. This improves calling reliability and clarity across contacts and other records, including cases where the phone field has a different name.
Original PR description
Steps to reproduce: - Create a new partner - Set the country address to Belgium - Set as phone number: 0498123456 - Focus-out/save => It gets formatted as +32 498 12 34 56 - Create a call activity =>…
Steps to reproduce: - Create a new partner - Set the country address to Belgium - Set as phone number: 0498123456 - Focus-out/save => It gets formatted as +32 498 12 34 56 - Create a call activity => Bug: the "tel:" link displays 0498123456 instead of using the formatted version. The issue is there since forever, but it got really noticeable since [1] which changed the way we save numbers. Before that, the "phone" field of partners indirectly was saved with the formatted value, meaning the formatted value would be used for call activities "by chance". Of course, it was still a problem for imported values or values forced through ORM code... but that was acceptable. Since [1], the formatted value is saved separately and that is the one that should be used by call activities. Actually, more than formatting and even before [1], it would have made sense to try and use the `phone_sanitized` one coming from the `mail.thread.phone` mixin, as it automatically finds the right "primary" phone field the record uses, which might not be "phone" as call activities assumed. `phone_sanitized` might not have been formatted though so it would not have been perfect anyways. An example of an improvement this fix commit does indirectly: - Install hr_recruitment (and voip) - Go to hr.applicant list - Create a new one - Just set a name and a phone, save - Create a call activity on that new record => There is no phone number displayed with it in the chatter (as the record field is not named "phone" but "partner_phone" and the record is not (yet) linked to a partner record). With this commit, we easily fix/improve 19.4 and future versions, using the new `phone_formatted` field from the `mail.thread.phone` mixin when available, otherwise fallback to any registered phone field of the record (see `_phone_get_number_fields`), not assuming "phone" is the only field to check anymore. This thus allows to ensure to use the right and formatted phone field. JS-side, this commit also ensures to "clean" the formatted version for technical uses (`tel:` and SIP uses), even though it might not really be necessary as most browsers support a fully formatted format. [1]: https://github.com/odoo/odoo/commit/262cb9dc8bde2deb39aa126ee0d8c14c0abc7301 task-6537285
Customer payments in Mexican localization now use the payment method configured on the selected bank journal instead of always defaulting to electronic transfer. This helps ensure payment complements sent to SAT carry the correct payment method and match the company’s journal setup.
Original PR description
Currently, payment way configured on a bank journal is not taken into account, payments will default to "03 Transferencia electrónica de fondos". Steps to reproduce: - Set the "Payment Way" of a bank journal to "04 Tarjeta de Crédito". - Manually create a customer payment in that journal - Look at the "Payment Way" of the payment, then send the payment complement (REP) to the SAT. Issue: The payment way is "03 Transferencia electrónica de fondos" and the REP carries FormaDePagoP="03" The one set on the journal is ignored. Analysis: We should evaluate the journal before the transferencia default so a payment inherits the payment way, and keep transferencia as the last possible value. opw-6493514 Forward-Port-Of: odoo/enterprise#130863 Forward-Port-Of: odoo/enterprise#130133
Accounting reports now use the desktop-only layout wrapper only where it is needed. This prevents the scroll indicator from being hidden behind long reports on iPhone and iPad, making reports easier to read and navigate on mobile devices.
Original PR description
### Issue: On iPhone and iPad (reduced window), the scrollbar is hidden behind the report on long scrollable pages This affects all browsers on iOS and iPadOS — Apple forces the use of WebKit (its…
### Issue: On iPhone and iPad (reduced window), the scrollbar is hidden behind the report on long scrollable pages This affects all browsers on iOS and iPadOS — Apple forces the use of WebKit (its rendering engine) on all of them, including Chrome and Firefox ### Steps to reproduce (on iPhone): - Install `l10n_es` (contains scrollable reports by default) - Switch to the ES company - Open the Tax Report and try to scroll Before the fix, the scrollbar renders behind the report content ### Cause: `o_content` was added unconditionally in commit https://github.com/odoo/enterprise/commit/d60ea6e0f3245e394fd0cee5f22f52aff2a79e2e But its role differs between desktop and mobile, and its presence on mobile triggers a WebKit compositor bug On desktop, `o_content` is required: `.o_action` stays `overflow: hidden`, so only `.o_content` (`overflow: auto`) can scroll the report Per the CSS flexbox spec, a flex item won't shrink below its content size unless its `overflow` is not `visible` Without `o_content`, the div keeps `overflow: visible`, refuses to shrink, overflows `.o_action`, and the excess is silently clipped — content becomes unreachable, not just visually different On mobile, the framework flips scroll responsibility to `.o_action` (`overflow: auto`) and forces `.o_content` back to `overflow: initial` `o_content` is therefore not needed on mobile On WebKit (iOS/iPadOS), keeping `o_content` on mobile is harmful: it sits as a non-scrolling `position: relative` node between the real scroll ancestor (`.o_action`) and descendants that require special compositing — the sticky `thead` and the fixed-position mobile chatter This configuration causes WebKit's compositor to miscalculate the Root layer bounding box This geometry mismatch is the most likely explanation for why the native scroll indicator renders behind the report instead of on top of it Removing `o_content` on mobile avoids this node entirely and restores correct compositor geometry ### Notes: Tested on Android (Blink) with and without `o_content`: no visual difference and identical compositor layer geometry confirmed via Chrome DevTools — no regression introduced The `padding-bottom` on `.o_account_report_scroll_container` is unrelated — it is applied unconditionally and exists for a separate bug (last row clipped on scroll) opw-6191827 Forward-Port-Of: odoo/enterprise#132034 Forward-Port-Of: odoo/enterprise#128528
Payroll payment reports for India now stop when an employee has no bank account on file. This prevents reports from being generated with missing payment details, helping payroll teams catch setup issues before processing payments.
Original PR description
The payment report check looked at existing Indian bank accounts with an invalid IFSC. An employee withno bank account added nothing to it, so they passed the check and the report was generated with no account for them. task-6580632
Belgian payroll now handles senior dependents needing care through the main senior dependent field, matching the intended future behavior. This prevents confusion in the employee family section and helps payroll calculations use the correct dependent information.
Original PR description
In the 19.2 branch, we changed the UI to make it look like the number of other disabled senior dependents was included in the "other senior dependent" field, but this is not the case. Since the `other_disabled_senior` field will be deleted in the 19.5 branch, instead of just fixing the UI in 19.2, this commit updates the logic to match the 19.5 behavior. We will now handle this using only the remaining field. Task that fixes it in 19.5: https://www.odoo.com/odoo/project/1251/tasks/6133227 Task ID: 6526570 Forward-Port-Of: odoo/enterprise#131424 Forward-Port-Of: odoo/enterprise#130319
Planning now shows each employee's own allocated hours when a shift is assigned to multiple resources, instead of repeating the total for everyone. Field service planning also calculates break time correctly when resources with different schedules are added or removed, helping keep allocated hours accurate.
Original PR description
# [FIX] planning: split gantt progress bar hours per resource Issue: ---------------------------------------- On the planning gantt view, when a shift is assigned to several resources, the progress…
# [FIX] planning: split gantt progress bar hours per resource Issue: ---------------------------------------- On the planning gantt view, when a shift is assigned to several resources, the progress bar of each individual resource shows the sum of all resources' allocated hours instead of that resource's own hours. Steps to reproduce: ---------------------------------------- - Give two employees working calendars with a different number of daily hours (e.g. 8h and 4h) - Create one shift covering both calendars' full working hours and assign both employees to it - Open the gantt view and check the progress bar of each employee for that shift Cause: ---------------------------------------- `_gantt_progress_bar_group_by_field()` computes one duration per shift via `_get_duration_over_period()`, which already sums the working hours of every resource assigned to the shift. That same total is then added to the progress bar of each resource. Solution: ---------------------------------------- Add `_get_working_hours_over_period_per_resource()` and `_get_duration_over_period_per_resource()`, which returns the results into a dict per resource. `_get_working_hours_over_period()` then calls `_get_working_hours_over_period_per_resource()` and sums the values. `_gantt_progress_bar_group_by_field()` now calls this per-resource breakdown when grouping by `resource_ids`. # [FIX] planning_field_service: fix break_time computation Issue: ---------------------------------------- When adding multiple resources to a slot, we can break the Steps to reproduce first issue: ---------------------------------------- - Create a slot for a resource working 8 hours per day - Add another resource working 4 hours per day - Remove the resource working 4 hours - The allocated hours show "9h 36m" instead of 8h Cause: ---------------------------------------- `_get_in_schedule_break_time()` can return negative values, so it breaks the computation of `allocated_percentage` which then impacts the future allocated percentages. Solution: ---------------------------------------- Add a `max(..., 0)` to `_get_in_schedule_break_time()`. Steps to reproduce second issue: ---------------------------------------- - Create a slot for a resource working 8 hours per day - Add another resource working 4 hours per day - The break time is 0, it should be 4h Cause: ---------------------------------------- The calculation of `break_time` with multiple resources was supposed to be fixed in 583ccb226970787b9dbd4cb03ccbfd43849fb8c7 but the value is never used. Using it fixes the case for multiple resources having the same work schedule but not all case: In our case it will output 12 allocated hours and 2 hours of breaktime instead of 4. This is because the breaktime hours are only from one resources but they still get divided by the number of resources. Solution: ---------------------------------------- We should instead multiply the duration by the number of resources to get the correct number of break hours. This ensures that `allocated_hours + break_time` equals the total duration of the shift. Same in `_get_in_schedule_break_time()`. opw-6500685 Forward-Port-Of: odoo/enterprise#130429
The Ecuador ATS tax export now consolidates data from a company and its branches that share the same RUC. This ensures submitted XML reports match the consolidated tax dashboard and avoids missing branch invoices or totals.
Original PR description
In Ecuador, a company and its branches file under a single RUC, so the ATS is expected to consolidate all of them. However, currently the system exports only the active company report. Steps to…
In Ecuador, a company and its branches file under a single RUC, so the ATS is expected to consolidate all of them. However, currently the system exports only the active company report. Steps to reproduce: - Install the Ecuadorian localization - Create a parent company and a branch, assign the RUC of the parent to the branch, and set the legal name of the branch in Settings - Post a customer invoice with taxes in the parent (e.g. 750) and another one in the branch (e.g. 250) - Activate both companies in the company selector - Go to Accounting > Reporting > Tax return - Select "Report: 104 (EC)", the dashboard shows the consolidated values - Click on the gear icon next to "Tax Return" and select ATS Issue: The exported XML only contains the documents and the totals of the parent company (750). The branch (250) is omitted. Analysis: Currently the ATS export use self.env.company for all searches, which only retrieve the data of the first company set opw-6469807 Forward-Port-Of: odoo/enterprise#128430
This fixes an issue where custom product attribute values entered in a Point of Sale order were lost when the order triggered an inter-company purchase and sale flow. Delivery documents now keep the correct product description, reducing confusion for warehouse teams and customers.
Original PR description
*= sale_purchase_inter_company_rules Step to reproduce: - install `sale_purchase_stock_inter_company_rules` and pos with demo - from setting : - enable Multi-Step Routes - enable all options for…
*= sale_purchase_inter_company_rules
Step to reproduce:
- install `sale_purchase_stock_inter_company_rules` and pos with demo
- from setting :
- enable Multi-Step Routes
- enable all options for Inter-Company Transactions (for both companies)
- go to routes, un-archive MTO route
- create a product "A" with routes "MTO" and "buy"
- make the product available for pos (add into `Misc` category)
- create a attribute with custom attribute value and link it to "A"
- in vendor list, add "My Company (Chicago)" as vendor
- switch to Chicago company, for same product add some other vendor
- switch back to "My Company (San Francisco)"
- for pos, in setting enable "Allow Ship Later"
- open Furniture pos, create a order with Product "A"
- go to payment page and select "Ship Later" and pay
- a RFQ is created for this pos order, open and confirm it
- switch to "My Company (Chicago)"
- open sale order (remove "my quotation" filter)
- notice a quotation created from company 1, confirm it.
- open linked delivery slip
Observation:
- the delivery slip will not have description (custom value for attribute for A
is lost)
Cause:
- when preparing vals for SO for inter-company transaction from
`_prepare_sale_order_line_data` we never considered order from pos
- hence in this case, order lines never had custom attributed linked to them
- the description is computed from the attributes from SO
- SO never had them to begin with
Fix
- we introduced a method `_get_pcavs_from_pos_order` which fetch such attributes from pos order too
- to accomplish this we introduce a new module As there is no dependency with purchase and pos
opw-6213076
Forward-Port-Of: odoo/enterprise#131781
Forward-Port-Of: odoo/enterprise#119233One-time purchases of recurring products are no longer treated like active subscriptions in stock forecasts and replenishment planning. This prevents misleading endless future demand, helping businesses plan inventory more accurately.
Original PR description
When a recurring product (with `allow_one_time_sale = True`) is sold as a one-time purchase (no subscription plan), the stock forecast report and replenishment logic incorrectly treat it as an active…
When a recurring product (with `allow_one_time_sale = True`) is sold as a one-time purchase (no subscription plan), the stock forecast report and replenishment logic incorrectly treat it as an active subscription. This results in infinite projected future outgoing moves for standard sales. This occurs because the logic only checks if `recurring_invoice` is True on the product, ignoring whether the parent order actually has a `plan_id`. This commit fixes the issue by: 1. Updating `_get_stock_subscription_lines` in `sale.order.line` to filter out lines using `_subscription_is_one_time_sale()`. 2. Updating the domains in `stock.forecasted_product_product` to require `order_id.plan_id != False` for subscription forecasts, while correctly routing one-time sales (`order_id.plan_id == False`) back to the standard sale domain. 3. Adapting existing tests to verify that one-time sales do not generate future subscription stock forecasts. Task-6193648 Forward-Port-Of: odoo/enterprise#128018 Forward-Port-Of: odoo/enterprise#116889
Fixes an issue that could prevent users from submitting draft Denmark VAT reports. The report now uses the needed prior settings when calculating VAT lines, helping Danish VAT filing proceed reliably.
Original PR description
Currently, an error occurs when the user submits the draft Denmark VAT report. ``` TypeError: AccountReport.get_options() missing 1 required positional argument: 'previous_options' ``` When the user submits the draft Denmark VAT report, it gets the calculated lines of the current report by calling get_options method. However, get_options() requires the previous_options argument [1]. Since this argument is not passed [2], it raises the error. This commit ensures that an empty dictionary is passed as the previous_options argument when getting the report lines. [1]- https://github.com/odoo/enterprise/blob/e2aa3073dafa9377654c761e371a660415db1e88/account_reports/models/account_report.py#L2126 [2]- https://github.com/odoo/enterprise/blob/e2aa3073dafa9377654c761e371a660415db1e88/l10n_dk_reports/wizard/tax_report_wizard.py#L216 sentry-7380293202 Forward-Port-Of: odoo/enterprise#130320
This fix prevents Odoo from recreating deleted standard Bulgarian accounts or taxes during setup or upgrades when their original identifiers are missing. It helps avoid incorrect accounting records and related installation or migration failures for Bulgarian SAF-T users.
Original PR description
If a client deletes a standard account or tax, its XMLID can no longer be resolved during the post-init hook. In that case, _load_data() may create a new record using only the provided data, e.g.:…
If a client deletes a standard account or tax, its XMLID can no longer be resolved during the post-init hook. In that case, _load_data() may create a new record using only the provided data, e.g.:
```
('l10n_bg_691001', {'l10n_bg_saft_account_code': '624'})
```
This can lead to incorrect records.
Only update records whose XMLIDs still exist, avoiding the creation of new records when the corresponding standard record has been deleted.
```
File "/home/odoo/src/enterprise/19.0/l10n_bg_saft/__init__.py", line 10, in _add_account_saft_code
Template._load_data({'account.account': Template._get_bg_saft_account_code()})
File "/tmp/tmpbu1ntr1j/migrations/account/0.0.0/pre-ensure-deferred-accounts.py", line 37, in _load_data
return super()._load_data(data, *args, **kwargs)
File "/home/odoo/src/odoo/19.0/addons/account/models/chart_template.py", line 697, in _load_data
created_records[model] = self.with_context(lang='en_US').env[model]._load_records(all_records_vals)
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 5196, in _load_records
records = self._load_records_create([data['values'] for data in to_create])
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 5103, in _load_records_create
records = self.create(vals_list)
File "/home/odoo/src/odoo/19.0/odoo/orm/decorators.py", line 369, in create
return method(self, vals_list)
File "/home/odoo/src/odoo/19.0/addons/account/models/account_account.py", line 1055, in create
)).create(vals_list_for_company)
File "/home/odoo/src/odoo/19.0/odoo/orm/decorators.py", line 369, in create
return method(self, vals_list)
File "/home/odoo/src/odoo/19.0/addons/mail/models/mail_thread.py", line 329, in create
threads = super(MailThread, self).create(vals_list)
File "/home/odoo/src/odoo/19.0/odoo/orm/decorators.py", line 369, in create
return method(self, vals_list)
File "/tmp/tmpbu1ntr1j/migrations/util/orm.py", line 267, in wrapper
return f(*args, **kwargs)
File "/tmp/tmpbu1ntr1j/migrations/base/0.0.0/pre-models-match_uniq.py", line 25, in create
return super().create(vals_list)
File "/home/odoo/src/odoo/19.0/odoo/orm/decorators.py", line 369, in create
return method(self, vals_list)
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 4711, in create
records = self._create(data_list)
File "/home/odoo/src/odoo/19.0/odoo/orm/models.py", line 4887, in _create
cr.execute(SQL(
File "/home/odoo/src/odoo/19.0/odoo/sql_db.py", line 440, in execute
self._obj.execute(query, params)
psycopg2.errors.NotNullViolation: null value in column "account_type" of relation "account_account" violates not-null constraint
DETAIL: Failing row contains (734, null, 1, 1, null, null, null, t, f, f, 2026-08-25 08:18:15.751937, 2026-08-25 08:18:15.751937, no, f, null, null, null, null, 411).
```
upg-4611390
tbg-2902
Forward-Port-Of: odoo/enterprise#129145