Daily updates from Odoo
Thursday, July 9, 2026
49 changes · saas-19.1
Enhancements to existing features
The Finland localization now includes the new 13.5% tax rate for affected goods and services, replacing situations where 14% previously applied. This helps Finnish businesses keep tax reporting and exports aligned with the latest VAT requirements.
Original PR description
backport of 5324615 As in Finland the tax rate will change from 14 to 13.5 on certain goods and services those taxes and the corresponding tax group were added to the l10n task-6332565 Forward-Port-Of: odoo/enterprise#123098
The bank reconciliation screen now loads less unnecessary data, making it faster to open on very large databases. This improves day-to-day accounting workflows by reducing waiting time without changing the reconciliation process.
Original PR description
When opening the bank rec widget on huge DB's, it takes
a lot of time to load everything.
This commit aims to improve the loading performances by
removing some JS fields:
1 - reconciled_lines_ids: We only use the first element of
this recordset in JS, so we add a new computed field
to only send 1 record to the JS
2 - hasAttachment: replace the long JS computation of
`get hasAttachment` with a python computed field.
3 - Replace matched_credit_ids and matched_debit_ids
with exchange_diff_partial_ids.
Linked:https://github.com/odoo/odoo/pull/269119
task-6275945
Forward-Port-Of: odoo/enterprise#119557The out-of-office banner now keeps the “Back on” status on a single line instead of wrapping. This improves the visual alignment of the header and makes the banner look cleaner and more consistent for users.
Original PR description
Previously, the 'Back on' status in the out-of-office banner could wrap onto multiple lines, making the header appear misaligned. This PR keeps the status on a single line for a cleaner and more consistent layout. <table> <tr> <th>Before</th> <th>After</th> </tr> <tr> <td> <img width="378" height="630" alt="image" src="https://github.com/user-attachments/assets/e4c9f278-9a65-48ad-8841-6ed059bd766d" /> </td> <td> <img width="372" height="631" alt="image" src="https://github.com/user-attachments/assets/f3dd860b-eba5-4233-a4c0-f286eec95c63" /> </td> </tr> </table> --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#274811 Forward-Port-Of: odoo/odoo#274050
backport of 5324615 As in Finland the tax rate will change from 14 to 13.5 on certain goods and services those taxes and the corresponding tax group were added to the l10n task-6332565 Forward-Port-Of: odoo/odoo#274460
Original PR description
backport of 5324615 As in Finland the tax rate will change from 14 to 13.5 on certain goods and services those taxes and the corresponding tax group were added to the l10n task-6332565 Forward-Port-Of: odoo/odoo#274460
Resolved issues and error corrections
Bank reconciliation entries that include taxes now show the correct tax base amount immediately. This prevents tax-related journal lines from displaying zero values and gives accounting users accurate figures without needing to reset entries to draft.
Original PR description
### Steps to reproduce the issue: 1. Download Accounting 2. Go to Journal entries and create a new one 3. Click Toggle Studio button and go to the view to add the tax_base_amount field to the list of…
### Steps to reproduce the issue: 1. Download Accounting 2. Go to Journal entries and create a new one 3. Click Toggle Studio button and go to the view to add the tax_base_amount field to the list of existing fields 4. Add 2 lines (example): 1. account Product Sales with 1000 dollars credit and 15% tax under Tax column 2. account Bank with 1000 dollars debit 5. Go to Dashboard > Bank > Click the 3 dots of one random bank matching line and click on Manage Models 6. Go to bank fees and add the 15% tax 7. Go back to bank reconciliation and create a new one of 2000 dollars with label bank fees (it will associate the tax automatically) 8. Go back to Journal Entries, group by Journal and search for the transaction of 2000 sollars for account Bank 9. Problem: see that the Base Amount for the 15% bank fees lines (251000 Tax Received account) is 0. Clicking on Reset to draft button the base amount column is automatically updated but this should happen automatically ### Cause of the issue: This occurs because the _lines_prepare_tax_line method in account.bank.statement.line fails to map this field in its return dictionary. ### Reason to introduce the fix: Currently, when applying a reconciliation model with taxes the generated tax lines incorrectly record a tax_base_amount of $0.00. Instead, it should be displayed and calculated. opw-6220948 Forward-Port-Of: odoo/enterprise#122139
This fix prevents Australian payroll users from seeing an error when recalculating a payslip after an employee's Income Stream Type has been updated. Payslips now refresh the relevant income stream information before calculation, helping payroll teams complete processing reliably.
Original PR description
When an employee's Income Stream Type is changed after a payslip has been created, computing the sheet for payslip will raise a traceback. Steps to reproduce the error: - Install…
When an employee's Income Stream Type is changed after a payslip has been created, computing the sheet for payslip will raise a traceback. Steps to reproduce the error: - Install ``l10n_au_hr_payroll_account`` module with demo data - Switch to ``My Australian Company`` company - Create a new payslip for ``Dennis Cactus`` Employee > Save - Go to Employees > Open the ``Dennis Cactus`` employee > In Payroll tab, Income Stream Type: Other specified payments > Save - Go back to payslip > click the compute sheet button Traceback: ```py KeyError: 'OSP' ``` https://github.com/odoo/enterprise/blob/13e64e0cb7f566cbbb46109fe1c218eb7a14eead/l10n_au_hr_payroll/models/hr_payslip.py#L175-L178 The ``l10n_au_income_stream_type`` field on the payslip is a computed field that only depends on ``employee_id``. As a result, changing the employee's Income Stream Type does not trigger a recomputation of the corresponding field on existing payslip. So, when the ``payslip_ytd_totals`` field is computed, it uses the old value of ``l10n_au_income_stream_type`` field at [1], The resulting ``payslip_ytd_totals`` is then used to build the ``totals`` dictionary, and eventually, when the employee's current ``income_stream_type`` is used to access ``totals``, the mismatch key leads to the above traceback. https://github.com/odoo/enterprise/blob/13e64e0cb7f566cbbb46109fe1c218eb7a14eead/l10n_au_hr_payroll_account/models/hr_payslip.py#L75-L88 [1]: https://github.com/odoo/enterprise/blob/13e64e0cb7f566cbbb46109fe1c218eb7a14eead/l10n_au_hr_payroll/models/hr_payslip.py#L269-L272 solution: I added ``l10n_au_income_stream_type`` to ``add_to_compute()`` in ``compute_sheet()``. This ensures that stale values of ``l10n_au_income_stream_type`` on existing payslips are recomputed when the payslip sheet is computed. sentry-7536819310 Forward-Port-Of: odoo/enterprise#123288 Forward-Port-Of: odoo/enterprise#120143
This fix prevents a crash when updating quantities on Field Service sale orders while automation rules listen for incoming messages. It keeps chatter noise suppressed as intended, but returns a safe internal response so automated workflows can continue without interruption.
Original PR description
Steps to reproduce: ---------------------------------------- 1. Install `industry_fsm_sale` and `base_automation` modules 2. Create a product with: * Type: Service * Create on order: Task * Project:…
Steps to reproduce:
----------------------------------------
1. Install `industry_fsm_sale` and `base_automation` modules
2. Create a product with:
* Type: Service
* Create on order: Task
* Project: Field Service
3. Create an automation rule with:
* Model: Sales order
* Trigger: Incoming message
4. Create and confirm a sale order with this product
5. Add another product to the SO via the catalog view:
* Change the quantity to 2 or more
Observation:
----------------------------------------
Traceback occurs:
```
File '/home/odoo/src/odoo/addons/base_automation/models/base_automation.py', line 871, in _message_post
message_sudo = message.sudo().with_context(active_test=False)
AttributeError: 'bool' object has no attribute 'sudo'
```
Root Cause:
----------------------------------------
* Catalog qty change calls `set_fsm_quantity()` method
* Setting `fsm_quantity` triggers its inverse `_inverse_fsm_quantity()`, which writes the new qty to the SOL, but passes `fsm_no_message_post=True` in context to suppress chatter noise
https://github.com/odoo/enterprise/blob/ac5d670832a5e0db714c0bd056e1406b50bb4c17/industry_fsm_sale/models/product_product.py#L72-L83
* `sale.order.line.write()` detects a qty change on a confirmed order and calls `_update_line_quantity()`, which posts a message on the parent sale order
* FSM's `message_post` override sees the context flag and returns `False`
* When `base_automation` has an `on_message_received` rule on `sale.order`, it wraps `message_post` at registry load time. That wrapper calls `sudo()` on whatever `message_post` returns, Which was `False`
Solution:
----------------------------------------
Return `self.env['mail.message']` (empty recordset) instead of False, it's still falsy, but it's a proper ORM object that `sudo()` can be called on
opw-6276916
Forward-Port-Of: odoo/enterprise#123010
Forward-Port-Of: odoo/enterprise#119542Facebook GIFs attached to feed comments now appear as a still preview instead of being hidden. Users can click the preview to open the animated version on Facebook, making comment content clearer and easier to review.
Original PR description
Bug === When opening the comments modal of the feed view, the GIF images are not visible. Technical ========= The API does not return the GIF, it only returns the MP4 and the JPG. So we show the fixed image, and when clicking on it, it opens the video on Facebook. Task-6241607 Forward-Port-Of: odoo/enterprise#123181 Forward-Port-Of: odoo/enterprise#118619
Sales commission achievement records with very large IDs can now be opened correctly. This prevents users from seeing an incorrect “record does not exist” message when accessing affected achievement reports.
Original PR description
Steps to reproduce: - Open an achievement with id > JS limit Issues: - We get a pop-up saying the record does not exists The reason we get this error is because since we are browsing a record with an id greater than JS limit the browser truncate it. In order to solve this issue the following PR was made #108751. A field `id_str` was added but it still wasn't working as we weren't retrieving the `id_str`. We now do this by passing `id_str` in the context and retrieving it on the `web_read`.
A stability issue in the Knowledge app was fixed by preventing an action from running after a popover has already closed. This reduces random failures during guided workflows and helps keep the user experience reliable.
Original PR description
This commit is a followup of [1] which made some tours fail ramdomly because of a crash. The crash occurred when `this.activeEl` was falsy, presumably because the popover was already closed. This commit only prevents the issue by adding a safe guard (which was there before [1] though). This commit is actually a backport of [2], which already fixed the issue as of saas-19.2 [1] https://github.com/odoo/enterprise/pull/114168 [2] https://github.com/odoo/enterprise/pull/115483 Runbot error~242907 Forward-Port-Of: odoo/enterprise#123309
Imported Chilean electronic invoices now use the amount in the invoice currency instead of incorrectly using Chilean peso amounts. This prevents vendor bills in foreign or indexed currencies such as UF from showing wrong totals, improving accounting accuracy.
Original PR description
**STEP TO REPRODUCE** 1. Create a invoice to a chilian company, using another currency (for example UF, don't forget setup up a currency rate). 2. Confirm. 3. Download the xml in the chatter, and import it as a vendor bill. 4. Notice the imported bill amount are wrong (Pesos amount are used, with the currency being UF). opw-6269662 Forward-Port-Of: odoo/enterprise#123179 Forward-Port-Of: odoo/enterprise#119664
The AI option that fills in SEO metadata now uses the website page's language instead of the user's personal language setting. This prevents titles and descriptions from being generated in the wrong language on multilingual websites, improving consistency for visitors and search engines.
Original PR description
The SEO "Fill with AI" autofill used the user's language for generation. On a website whose language differs from the user's, the generated seo metadata was therefore in the wrong language. This commit fixes this by using the page language instead.
A missing user-facing text string was added in the Stripe expense card setup. This prevents an outdated or incomplete message from appearing, improving clarity for users managing expense card merchant category settings.
Original PR description
Add missing string runbot-941402 Forward-Port-Of: odoo/enterprise#123495
DHL deliveries made from a company other than the main company now send a valid commercial invoice number. This prevents DHL validation errors and allows international shipments with dutiable goods to be processed correctly.
Original PR description
Issue ----- When validating a delivery in a different company from the main one, the commercial invoice's number field is incorrect, which raises an error on DHL end.…
Issue ----- When validating a delivery in a different company from the main one, the commercial invoice's number field is incorrect, which raises an error on DHL end. `content/exportDeclaration/invoice/number: expected type: String, found: Boolean` Steps to reproduce ----- - Create a Belgian company - Setup DHL - DHL Product D - Express Worldwide - Dutiable Material enabled - Create an amrican customer - Deliver a product to the american customer > Validation Error Cause ----- The field is populated in https://github.com/odoo/enterprise/blob/cf3c2fce8a6b7b2d7547d44a0e4423f887986d52/delivery_dhl_rest/models/dhl_request.py#L204 The problem is that `next_by_code` uses the company found in the env, whereas the sequence's company is the main one, so it is not found when doing https://github.com/odoo/odoo/blob/e3b0ca11d99b2ef819cdad68b169112cd73668b6/odoo/addons/base/models/ir_sequence.py#L287 ----- Ticket: opw-6171886 Forward-Port-Of: odoo/enterprise#123170 Forward-Port-Of: odoo/enterprise#118379
Restaurant delivery orders are now retrieved in one combined request instead of multiple back-to-back requests. This reduces waiting time and server load when point of sale orders are refreshed, improving day-to-day reliability for restaurants using UrbanPiper integrations.
Original PR description
Issue: pos_urban_piper overrode getServerOrders() to add a separate loadServerOrders() call for it's own orders before delegating to super, resulting in up to an additional sequential RPCs on every order fetch. Fix: Extract the base query domain into a new overridable getServerOrdersDomain() method. Each module overrides it to OR in its own domain via Domain.or([super.getServerOrdersDomain(), extraDomain]), so all orders are fetched in a single RPC call instead of three. Task-6284860 Forward-Port-Of: odoo/enterprise#123160 Forward-Port-Of: odoo/enterprise#120001
Knowledge article PDF downloads now exclude on-screen elements such as scrollbars and open menus. This makes exported articles look cleaner and more professional, especially for longer documents or when the browser is zoomed in.
Original PR description
The Download PDF option of an article prints the page with the browser. On screen, the article body is inside .o_scroll_view_lg, which scrolls when the content is longer than the screen:…
The Download PDF option of an article prints the page with the browser. On screen, the article body is inside .o_scroll_view_lg, which scrolls when the content is longer than the screen: https://github.com/odoo/enterprise/blob/79f8defa04476e1b939dc8bb5449a775137aed62/knowledge/static/src/scss/knowledge_views.scss#L170-L177 The print stylesheet used to force overflow: visible on every div, so this container did not scroll when printing. It also hid every child of the body except the action manager, so the navbar and open dropdowns were left out of the print. Commit https://github.com/odoo/enterprise/commit/69612c80ea0aec5ccf2c2857449da03e61273457 rewrote knowledge_print.scss to scope its rules to the Knowledge view and removed both rules. The scroll container now keeps its fixed height and its scrollbar when printing, so the scrollbar is drawn in the print preview and on every page of the PDF. The dropdown opened to reach Download PDF is printed on top of the article when it overlaps the page area, which happens when the browser is zoomed in. Add overflow: visible to the print rule of knowledge_print.scss that already targets .o_scroll_view and .o_scroll_view_lg with position: static. That rule exists to undo the screen positioning of the scroll containers when printing, so the overflow reset belongs there. Its selector is also more specific than the screen one, so the value applies without !important, like position: static already does. Restore the rule that hides the body children other than the action manager, scoped to the Knowledge view like the rest of the file since the print stylesheet is now loaded on every page. Before: <img width="497" height="703" alt="image" src="https://github.com/user-attachments/assets/44aa3366-3fc8-4382-8aa2-84625fa4b6d8" /> After: <img width="497" height="703" alt="image" src="https://github.com/user-attachments/assets/8b6eb2bc-37a3-4666-b871-0e6149c41fea" /> Steps to reproduce: 1. Open the Knowledge app and create an article 2. Paste enough text in the article to fill more than one PDF page 3. Zoom the browser to 200% 4. Click the three dots in the top right corner, then Download PDF 5. Check the print preview or the saved PDF => A scrollbar is drawn on the right edge of every page and the dropdown menu is printed on top of the article Ticket [link](https://www.odoo.com/odoo/project.task/6279174) opw-6279174 Forward-Port-Of: odoo/enterprise#120249
The POS settlement payment screen now only runs India-specific invoice logic when the company is actually in India. This prevents unnecessary errors in other countries and keeps the checkout flow stable.
Original PR description
Toggle invoice button was making a call in IN localization even when not in a IN country. This was causing an error in runbot 940146. This commit fixes the issue by checking if the country is IN before making the call. In `pos_settle_due` the method signature was not correct. Forward-Port-Of: odoo/enterprise#123539
In multi-company setups, project Gantt charts now correctly shade unavailable time slots for users linked to employees in different companies. This keeps the visual schedule aligned with time-off warnings, helping planners avoid assigning work during approved absences.
Original PR description
Steps to reproduce: - Create one user linked to two companies. - Create one employee per company for that user. - Select one company and approve a time off for the employee. - Open Tasks > Gantt and…
Steps to reproduce: - Create one user linked to two companies. - Create one employee per company for that user. - Select one company and approve a time off for the employee. - Open Tasks > Gantt and create a task during the approved time-off period the warning is shown and the Gantt cell is grayed out. - Select both companies and create a task during the same time-off period in Tasks > Gantt. Issue: When multiple companies are selected, the time-off warning is still displayed but the corresponding Gantt cells are no longer grayed out, leading to an inconsistency between the warning logic and the Gantt rendering. Cause: In multi-company setups, a user can be linked to multiple resources. The Gantt unavailability logic assumed a one-to-one relationship between user and resource causing unavailability intervals from some resources to be overwritten. Solution: Aggregate unavailability intervals from all resources linked to the same user, limited to the selected companies, and merge them with the company calendar unavailability to ensure consistent Gantt gray rendering. Related PR: https://github.com/odoo/enterprise/pull/57028 task-5089385 Forward-Port-Of: odoo/enterprise#105288
Users who are not linked to an employee can now create an expense from a document, provided they have permission to create expenses for another employee. This removes an unnecessary blocker while preserving the existing access controls for expense creation.
Original PR description
Removes the constraint saying a user has to be linked to an employee to create an expense from a document. In this case, the user still needs the rights to create an expense for another employee. task-6237021 Forward-Port-Of: odoo/enterprise#123523 Forward-Port-Of: odoo/enterprise#118955
Canadian check printing now consistently hides check numbers on both the check and its stubs when using pre-numbered checks. This prevents duplicate or confusing numbering on printed payment documents.
Original PR description
The check itself respected the check_manual_sequencing field, but the stubs did not. Hide the numbers on stubs as well, exactly like on US checks. task-6343701 Forward-Port-Of: odoo/enterprise#122565
This change fixes a flaky automated test in the email/content editor toolbar. It makes the test reliable by checking the order of internal updates instead of depending on browser timing, which helps prevent random failures in development and continuous integration.
Original PR description
### Description of the issue/feature this PR addresses: - Resolve non-deterministic failures in the 'toolbar should not open between double and triple click' Hoot test. - Because browser-level selectionchange events are dispatched asynchronously in the event loop, asserting on the presence of `.o-we-toolbar` in the DOM leads to timing race conditions. ### Solution: - Resolves the flakiness by introducing a wrapper method `triggerDebouncedUpdateToolbar` in `ToolbarPlugin` and refactoring the test to track method call sequences instead of asserting on DOM elements. This verifies the scheduled debounced updates in a deterministic sequence. task: https://runbot.odoo.com/odoo/error/243145 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#274498 Forward-Port-Of: odoo/odoo#273303
This change stops Odoo from reprocessing work entries when no eligible employees remain after exclusions. It prevents a fallback that could unexpectedly scan many employees and slow down large databases, while keeping existing validated work entries intact.
Original PR description
**Steps to Reproduce:** - Create an employee with at least one validated work entry. - Modify the employee's resource_calendar_id. - The schedule change triggers work entry recomputation. - During…
**Steps to Reproduce:** - Create an employee with at least one validated work entry. - Modify the employee's resource_calendar_id. - The schedule change triggers work entry recomputation. - During regeneration, the employee is excluded because of the validated work entry. - No valid employees remain for regeneration. **Issue:** - Employees with validated work entries are excluded from: `valid_employees = self.employee_ids - self.validated_work_entry_employee_ids` - When all employees are excluded, `valid_employees` becomes empty. - The flow still calls: `valid_employees.generate_work_entries(date_from, date_to, True)` - An empty employee recordset causes `generate_work_entries()` to follow the global generation path and fetch all employee versions in the requested period. `_get_all_versions_with_contract_overlap_with_period(date_start, date_stop)` - This can trigger unintended global work entry regeneration and cause severe performance issues on large databases. **Root Cause:** - The regeneration flow does not stop when no valid employees remain after excluding employees having validated work entries. - As a result, generate_work_entries() is called with an empty employee recordset, which falls back to the global generation path. **Solution:** - Stop the regeneration flow when the valid employee recordset is empty Because the employee already has a valid work entry, there is no need to regenerate. **Result:** - Prevents unintended generation on all employee versions. - Avoids unnecessary performance degradation on large databases. **OPW-6290122** 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#270728
This fix prevents an existing message from being reclassified when its content is updated. It helps keep message records consistent and avoids unexpected changes in how messages are handled or displayed.
Original PR description
Set message_type as 'comment' only when creating a new message. Updating content should not change it. Task-6368820 Part of Task-3704380 Forward-Port-Of: odoo/odoo#274988
This update fixes a crash that could happen in the website editor when a user selected an icon and then opened the Documents tab while replacing media. It improves the editor’s stability so users can switch between media options without losing work or hitting an error.
Original PR description
### Steps to reproduce: - Open the website editor and insert a snippet. - Inside the snippet, add an image and a document via /media. - Select the image, click Replace, pick an icon. - Click the icon, then click Replace from the sidebar. - In the dialog, click the Documents tab. - Traceback occurs. ### Root cause: - Both icon and document box elements are `<span>` tags. `DocumentSelector` inherits `selectInitialMedia()` from `FileSelector` which only checks the tag name, so it incorrectly returns true for icons. This causes `fetchAttachments` to call `querySelector(a)` on the icon span, which returns null and crashes. ### Solution: - Override `selectInitialMedia()` in `DocumentSelector` to also check for the `o_file_box` class. Add optional chaining on `querySelector(a)` as a safety net. task-6310147 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270548
When a task is moved from one project to another, its followers now inherit the notification preferences of the destination project. This prevents people from missing important updates, such as stage changes, after a task is reassigned.
Original PR description
Steps to reproduce: - 1. Create projects A and B. 2. Add a user as a follower of project B and select specific notification subtypes (e.g., 'Stage Changed'). 3. Create a task in project A and add the same user as a follower(defaulting to 'Discussions'). 4. Move the task from project A to project B. Issue: - The follower's subscription preferences on the task do not reflect their project-level settings after the move. In the example above, the user remains subscribed only to 'Discussions' and misses 'Stage Changed' updates. Cause: - The default auto-subscription logic skips existing followers. When moving a task, this prevents the system from adding the new project's notification preferences to users who were already following the task. Fix: - Override `_message_auto_subscribe` in project.task to the `update` policy when the `project_id` is changed. task-5877507 Forward-Port-Of: odoo/odoo#248224
This change fixes an issue in list views where selecting rows also blocked copying or selecting the totals shown in the footer. Users can now highlight footer totals even when one or more rows are selected, making it easier to reuse or compare values.
Original PR description
Before this commit, selecting one or more rows in a list view disabled text selection on the whole list, which also prevented users from selecting the totals displayed in the footer. This commit fixes the issue on the list footer, so totals remain selectable even when rows are selected. task:6240238 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#272126
This change protects paid point-of-sale orders from being lost if the network drops while the order is being synchronized. It also prevents accidental tab closing or refreshing in that brief window, reducing the risk of losing completed sales.
Original PR description
When an order is validated, the state is set to "paid" and a sync to the server is attempted. If the network dropped during that sync, the order could be permanently lost: the 300ms IndexedDB debounce had not yet fired, so the paid order lived only in memory, and no guard prevented the cashier from accidentally closing or refreshing the tab in that window. opw-6237823 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#267799
This change reduces the number of server calls needed to fetch orders in Point of Sale, especially when self-order features are used. As a result, order loading should feel faster and put less load on the system during busy periods.
Original PR description
Issue: pos_self_order overrode getServerOrders() to add a separate loadServerOrders() call for it's own orders before delegating to super, resulting in up to an additional sequential RPCs on every order fetch. Fix: Extract the base query domain into a new overridable getServerOrdersDomain() method. Each module overrides it to OR in its own domain via Domain.or([super.getServerOrdersDomain(), extraDomain]), so all orders are fetched in a single RPC call instead of three. Task-6284860 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#274548 Forward-Port-Of: odoo/odoo#269260
This fix prevents Point of Sale from crashing when a sale order contains an archived product attribute. It ensures settled sales continue to work smoothly even if the related attribute was removed from a product template and archived.
Original PR description
When a product attribute line is used in a confirmed sale order, Odoo archives it (active=False) instead of deleting it when removed from the product template. If the corresponding product.attribute record is also archived, settling that sale order in PoS crashes with: TypeError: Cannot read properties of undefined (reading 'create_variant') opw-6315766 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#271778
This fix prevents manufacturing byproducts from being reset to zero when a production order is unreserved. As a result, users can unreserve and recheck availability without losing the byproduct quantities that should be produced.
Original PR description
On a mo that has byproducts, if you unreserve, it will also set the quantity of byproducts to 0 Steps to reproduce: ------------------- * Create a Products main, component and byproduct * Create a…
On a mo that has byproducts, if you unreserve, it will also set the quantity of byproducts to 0 Steps to reproduce: ------------------- * Create a Products main, component and byproduct * Create a bom for main with component as component and byproduct as byproduct * Create and confirm a mo for main * Set qty_producing to quantity ot produce * click on "Unreserve" (do_unreserve) * click on "Check availability" (action_assign) * Produce All -> the byproducts will not be produced. Observation: ------------- When updating the qty_producing value it will also update the quantity of the byproducts moves: https://github.com/odoo/odoo/blob/77b180e8251fb8019e0034e1c2f485fd2c34ea4e/addons/mrp/models/mrp_production.py#L892-L893 https://github.com/odoo/odoo/blob/77b180e8251fb8019e0034e1c2f485fd2c34ea4e/addons/mrp/models/mrp_production.py#L1350 https://github.com/odoo/odoo/blob/77b180e8251fb8019e0034e1c2f485fd2c34ea4e/addons/stock/models/stock_move.py#L2382 The quantity on the byproducts move has been updated. When clicking on Unreserve it will call do_unreserve, https://github.com/odoo/odoo/blob/aca2e226143487b421f79194d677bb48d5da1358/addons/mrp/models/mrp_production.py#L2297-L2298 It will filters the moves that do not need to be unreserved and select the others: https://github.com/odoo/odoo/blob/aca2e226143487b421f79194d677bb48d5da1358/addons/stock/models/stock_move.py#L900 and it will unlink all the sml from the moves: https://github.com/odoo/odoo/blob/aca2e226143487b421f79194d677bb48d5da1358/addons/stock/models/stock_move.py#L919 Which will set the quantity on the byproduct moves to 0. When Producing all (button_mark_done) since the qty_producing has already been set, it will simply mark the byproduct move has picked. https://github.com/odoo/odoo/blob/aca2e226143487b421f79194d677bb48d5da1358/addons/mrp/models/mrp_production.py#L1323-L1324 In our case, this means that the no byproducts will be created since, the quantity was previously set to 0 opw-6296562 Forward-Port-Of: odoo/odoo#273739 Forward-Port-Of: odoo/odoo#272216
This change makes error messages shown to the client more informative when a proxy response fails. Instead of only displaying the error type, the system now tries to include the actual message, which helps users understand and resolve issues faster.
Original PR description
Currently, if you have an error in the response, we don't try to get the error message, we just give the type of error. Let us do that. Partial fw-port of https://github.com/odoo/odoo/commit/4bfe16cd45828a864a159b566d7983246e7e03a5 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#273754
The translation button is now neutralized for newly added sub-records that have not been saved yet, such as answer lines inside a survey. This prevents an error when users try to translate content before the record has a database ID, while still showing that translation will be available after saving.
Original PR description
The translate button next to a translatable field saves the record before opening the translation dialog for its id. Since https://github.com/odoo/odoo/commit/a85ca9679e3855936afc66b034d05d75f672dd26…
The translate button next to a translatable field saves the record before opening the translation dialog for its id. Since https://github.com/odoo/odoo/commit/a85ca9679e3855936afc66b034d05d75f672dd26 it saves record.model.root rather than the record itself. When the field belongs to a new record still edited inside an x2many, for example an answer added in the survey question popup, saving the root only saves the parent and the new line keeps no database id. The dialog then opens with the id set to false and calls update_field_translations on it, which builds WHERE id = false and the database rejects it with operator does not exist: integer = boolean. Such a record gets no id of its own, and after a save and reload there is no reliable way to match the saved line back to the one that was clicked, so the dialog can never open for it. A canTranslate getter in TranslationButton returns false for a new record whose model root is another record, which is exactly a line still edited inside an x2many, and the template only renders the button when it is true. The variant in editable lists, where model.root is a list rather than a record, was handled in https://github.com/odoo/odoo/commit/cb34b318004c3ca9db755d8dbbad429609220df3. Steps to reproduce: 1. Activate a second language in Settings > Translations > Languages 2. Open the Surveys app and create a survey 3. Add a question, then in the Answers tab add a line and type a value 4. Click the EN button next to the answer, fill the second language, and Save => RPC error operator does not exist: integer = boolean from WHERE id = false Ticket [link](https://www.odoo.com/odoo/project.task/6260427) opw-6260427 Forward-Port-Of: odoo/odoo#270410 Forward-Port-Of: odoo/odoo#267781
The following use case has been observed: 0. Customer start a payment from /shop/payment. 1. We received the webhook that notifies that the payment succeeded. 2. The payment post-processing cron start (it gather all the transactions that need to be processed, including the customer new transaction) 3. Meanwhile, the customer is redirected back by the payment provider to Odoo, which then redirect to /payment/status and start the payment post-processing for that specific transaction 4. The
Original PR description
The following use case has been observed: 0. Customer start a payment from /shop/payment. 1. We received the webhook that notifies that the payment succeeded. 2. The payment post-processing cron…
The following use case has been observed: 0. Customer start a payment from /shop/payment. 1. We received the webhook that notifies that the payment succeeded. 2. The payment post-processing cron start (it gather all the transactions that need to be processed, including the customer new transaction) 3. Meanwhile, the customer is redirected back by the payment provider to Odoo, which then redirect to /payment/status and start the payment post-processing for that specific transaction 4. The customer initiated payment processing finishes, he is redirected back to /my/orders/... page. 5. The payment post-processing cron finally start processing the same customer transaction and process it (a second time). In that case, as the transactions to be post-processed backlog was quite high, there is consequent time between the time we gather all the TXs to post-process and actually process the customer transaction. Also we don't end up with a `SerializationError` as the cron do commit after each transaction post-processing. This commit force invalidate individual transaction cache values and recheck if it effectively still need to be post-processed before doing it. opw-6332192 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#274725 Forward-Port-Of: odoo/odoo#274010
Toggle invoice button was making a call in IN localization even when not in a IN country. This was causing an error in runbot 940146. This commit fixes the issue by checking if the country is IN before making the call. In `pos_settle_due` the method signature was not correct. The linked enterprise commit also fix it. Forward-Port-Of: odoo/odoo#275055
Original PR description
Toggle invoice button was making a call in IN localization even when not in a IN country. This was causing an error in runbot 940146. This commit fixes the issue by checking if the country is IN before making the call. In `pos_settle_due` the method signature was not correct. The linked enterprise commit also fix it. Forward-Port-Of: odoo/odoo#275055
Issue: --- PDF quotes with multiple pages might have display issue on total section, cutting it to halves in two pages. Steps: 1- Set `Boxed` layout in document layout. 2- Create a SO with multiple lines and large descriptions and print it. This issue was previously fixed by 344007299c91d990c851ad9ed6f7fb5f8aa7a273 but the fix was reverted because of its effect on purchase document layout: f2dc10adc5576fc85a8c5100362f1a41c1d13054 Here the proposition is to apply the same fix but thi
Original PR description
Issue: --- PDF quotes with multiple pages might have display issue on total section, cutting it to halves in two pages. Steps: 1- Set `Boxed` layout in document layout. 2- Create a SO with multiple lines and large descriptions and print it. This issue was previously fixed by 344007299c91d990c851ad9ed6f7fb5f8aa7a273 but the fix was reverted because of its effect on purchase document layout: f2dc10adc5576fc85a8c5100362f1a41c1d13054 Here the proposition is to apply the same fix but this time precisely target `#total` from sale order document. opw-5934240 Forward-Port-Of: odoo/odoo#274673
Version: --------- - saas-18.4+ Steps to reproduce: ---------------------- 1. Install `stock` and `purchase` module 2. Create a storable and tracking product with a vendor. 3. Create a reordering rule: Route = Buy, Min Qty = 10, Max Qty = 20 and Trigger `manual` 4. Create a receipt for 10 units from the vendor and confirm it.(Mark as todo). 5. Observe the Order button is hidden in reorder rule(expected—incoming qty covers the minimum) 6. Delete the receipt move line. 7. Open reord
Original PR description
Version: --------- - saas-18.4+ Steps to reproduce: ---------------------- 1. Install `stock` and `purchase` module 2. Create a storable and tracking product with a vendor. 3. Create a reordering…
Version:
---------
- saas-18.4+
Steps to reproduce:
----------------------
1. Install `stock` and `purchase` module
2. Create a storable and tracking product with a vendor.
3. Create a reordering rule: Route = Buy, Min Qty = 10, Max Qty = 20
and Trigger `manual`
4. Create a receipt for 10 units from the vendor and confirm it.(Mark as todo).
5. Observe the Order button is hidden in reorder rule(expected—incoming qty covers the minimum)
6. Delete the receipt move line.
7. Open reordering rule and check order button
Issue:
------
After deleting a confirmed incoming move, the Order button on the reordering rule
does not reappear, even though there is no longer any incoming quantity to cover
the minimum threshold.
Cause:
-------
https://github.com/odoo/odoo/blob/adabd8cc47b1c5992f0f92631da73977be8beaca/addons/stock/views/stock_orderpoint_views.xml#L55-L56
The Order button visibility is driven by `qty_to_order > 0` on the orderpoint.
`qty_to_order` itself resolves to `qty_to_order_computed` (a stored field) when no
manual override is set:
https://github.com/odoo/odoo/blob/adabd8cc47b1c5992f0f92631da73977be8beaca/addons/stock/models/stock_orderpoint.py#L317-L319
```
qty_to_order─depends─>qty_to_order_computed (stored)
│
└─ reads qty_forecast (non-stored, computed)
│
└─ depends on product_id.stock_move_ids.
```
`_compute_qty_to_order_computed` checks whether `qty_forecast < product_min_qty`.
If so, it calculates how much to order to reach `product_max_qty`. When a confirmed
incoming move exists, its quantity is included in `qty_forecast`,
https://github.com/odoo/odoo/blob/adabd8cc47b1c5992f0f92631da73977be8beaca/addons/stock/models/stock_orderpoint.py#L347
This return False.
So, `qty_to_order_computed = 0` → button hidden.
To avoid recomputing `qty_to_order_computed` for all orderpoints on every stock
move change severe performance issue — see [Commit](https://github.com/odoo/odoo/commit/6f2d1b26b34129cfbd62d2fe44f306bfe89794b5
),
`qty_forecast` is intentionally excluded from
`@api.depends` on `qty_to_order_computed`. Instead,
`_update_orderpoints()` is called selectively from `create()` and `write()` to
schedule a targeted recompute via `env.add_to_compute()`.
However, `unlink()` never called `_update_orderpoints()`, so deleting a move left
`qty_to_order_computed` stale at 0 — the Order button stayed hidden permanently.
Solution:
---------
- `_update_orderpoints()` now returns the found orderpoints recordset.
- `unlink()` calls `_update_orderpoints()` before deletion to capture the affected
orderpoints (product_id / location data still accessible), then calls
`_compute_qty_to_order_computed()` immediately after `super().unlink()`.
opw-6174454
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#274832
Forward-Port-Of: odoo/odoo#264039There is a problem with version dates overlapping in the test tour. Delay was added to wait for the next month to load. task-6365181 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#274391
Original PR description
There is a problem with version dates overlapping in the test tour. Delay was added to wait for the next month to load. task-6365181 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#274391
There is a warning saying that there is no "true" field when editing the view. But in reality this is currently working as expected and the field is hidden. related to opw-5947987 Forward-Port-Of: odoo/odoo#274719 Forward-Port-Of: odoo/odoo#273041
Original PR description
There is a warning saying that there is no "true" field when editing the view. But in reality this is currently working as expected and the field is hidden. related to opw-5947987 Forward-Port-Of: odoo/odoo#274719 Forward-Port-Of: odoo/odoo#273041
Description of the issue/feature this PR addresses: Currently, in the website_sale_stock module, there is no backend validation when subscribing to notifications for products without stock. This allows public users to potentially use emails that belong to registered accounts. Current behavior before PR: Users could subscribe to stock notifications for products that don’t exist or cannot be added (no stock). Public users could use emails already associated with registered accounts, allowi
Original PR description
Description of the issue/feature this PR addresses: Currently, in the website_sale_stock module, there is no backend validation when subscribing to notifications for products without stock. This…
Description of the issue/feature this PR addresses: Currently, in the website_sale_stock module, there is no backend validation when subscribing to notifications for products without stock. This allows public users to potentially use emails that belong to registered accounts. Current behavior before PR: Users could subscribe to stock notifications for products that don’t exist or cannot be added (no stock). Public users could use emails already associated with registered accounts, allowing them to subscribe on behalf of another user. No validation is enforced, leading to potential security issues. Desired behavior after PR is merged: Adding a subscription for a non-existent or unavailable product raises a ValidationError. Public users trying to subscribe with an email that belongs to a registered user receive an AccessError prompting them to sign in first. Backend validation prevents misuse of registered user emails and improves security. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#274555 Forward-Port-Of: odoo/odoo#271880
When computing taxes for the order, we group the base lines based on a grouping key. One of the components of this key is `is_refund`, and reward lines where considered as refund lines, which caused them to be grouped separately. This caused a discrepency between the tax amount computed in the frontend and the one computed in the backend. Steps to reproduce: ------------------- * Change the rounding method to globally * Create a 21% tax not included in price * Create a product with a pric
Original PR description
When computing taxes for the order, we group the base lines based on a grouping key. One of the components of this key is `is_refund`, and reward lines where considered as refund lines, which caused…
When computing taxes for the order, we group the base lines based on a grouping key. One of the components of this key is `is_refund`, and reward lines where considered as refund lines, which caused them to be grouped separately. This caused a discrepency between the tax amount computed in the frontend and the one computed in the backend. Steps to reproduce: ------------------- * Change the rounding method to globally * Create a 21% tax not included in price * Create a product with a price of 76.01 and the tax created above * Create a loyalty program with a 10% discount * Create a POS order with the product above and apply the loyalty program * Validate the order and generate the invoice > Observation: The invoice states that 0.01$ is still due Why the fix: ------------ In `round_tax_details_tax_amounts`, we try to detect any delta with the expeced tax amount. To do that we group the base lines based on a grouping key. The grouping key `is_refund` was incorrectly set to `true` for reward lines, which caused them to not be grouped. The delta was then not detected and tax amounts were not adjusted. We now make sure not to consider reward lines as refund. opw-6052112 Forward-Port-Of: odoo/odoo#274175 Forward-Port-Of: odoo/odoo#271577
In the bubble layout, the `name="company_address"` div was inside an unconstrained `<td>`, causing percentage-based widths set in `company_details` (e.g. `width: 25%` on a image) to resolve against the full table cell width, resulting in oversized content compared to other layouts. **Without fix:** <img width="1127" height="411" alt="withoutfix" src="https://github.com/user-attachments/assets/244c3407-1dbb-4b83-95d9-ebaba2b2b13c" /> For e.g, The folder layout wraps the same block in a `w-
Original PR description
In the bubble layout, the `name="company_address"` div was inside an unconstrained `<td>`, causing percentage-based widths set in `company_details` (e.g. `width: 25%` on a image) to resolve against…
In the bubble layout, the `name="company_address"` div was inside an unconstrained `<td>`, causing percentage-based widths set in `company_details` (e.g. `width: 25%` on a image) to resolve against the full table cell width, resulting in oversized content compared to other layouts. **Without fix:** <img width="1127" height="411" alt="withoutfix" src="https://github.com/user-attachments/assets/244c3407-1dbb-4b83-95d9-ebaba2b2b13c" /> For e.g, The folder layout wraps the same block in a `w-50` div, so percentage widths resolve against ~half the page width. <img width="1111" height="410" alt="folderexp" src="https://github.com/user-attachments/assets/ee72712a-94da-483c-9a8e-cb3a850dac8e" /> Added `w-50 ms-auto` to the `name="company_address"` div in the bubble layout to align its sizing context with the other layouts, ensuring consistent rendering of user-defined styles in `company_details` across both layouts. **With fix:** <img width="1119" height="413" alt="wfix" src="https://github.com/user-attachments/assets/6b1caf24-3a8e-450f-865e-cce2b9788fe9" /> Commit which made the bubble layout in table: https://github.com/odoo/odoo/commit/8eb61a245cd3b650e309fdb55d24654c34d785cd opw-6287230 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#270725
Guests never receive any bus notification when the websocket is not handled by Odoo itself but by a proxy that forwards subscriptions through `/websocket/peek_notifications` and dispatches the notifications on its own (which is how Odoo.sh serves databases). The peek controller calls `ir.websocket._prepare_subscribe_data()` directly, bypassing `_subscribe()`, the only method decorated with `@add_guest_to_context`. The guest cookie is present on the request but never read, so the returned chan
Original PR description
Guests never receive any bus notification when the websocket is not handled by Odoo itself but by a proxy that forwards subscriptions through `/websocket/peek_notifications` and dispatches the…
Guests never receive any bus notification when the websocket is not handled by Odoo itself but by a proxy that forwards subscriptions through `/websocket/peek_notifications` and dispatches the notifications on its own (which is how Odoo.sh serves databases).
The peek controller calls `ir.websocket._prepare_subscribe_data()` directly, bypassing `_subscribe()`, the only method decorated with `@add_guest_to_context`. The guest cookie is present on the request but never read, so the returned channel list only contains the anonymous channels (broadcast, res.groups) and lacks the guest's personal ones: ("mail.guest", id), ("discuss.channel", id) and presence. The proxy therefore never registers those channels, and every notification targeted at the guest is silently dropped.
Before this commit, discuss calls with a guest are broken: a 2-person call never connects (p2p offers/answers/ICE are delivered over the bus); in a 3+ person call, any guest who joined before the SFU threshold stays stuck in dead p2p (they never receive the `sfu_hot_swap` notification) while a guest joining as 3rd+ participant works, since the join RPC returns the SFU credentials synchronously.
We override `peek_notifications` with `add_guest_to_context` so the route is aware of guests.
opw-5490739
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#274743SurveyResult binds a click listener on each .filter-add-answer icon when it starts. The response tables are rendered by a separate interaction, SurveyResultPagination, which swaps the tbody through a t-out directive on every page change and on Show All. Those new rows are nodes SurveyResult never bound, so their filter icon does nothing and the page reloads on the unfiltered view. The direct binding comes from https://github.com/odoo/odoo/commit/dfc1c742e35f75f2c386c4ef50d5584537ac1ed4, which r
Original PR description
SurveyResult binds a click listener on each .filter-add-answer icon when it starts. The response tables are rendered by a separate interaction, SurveyResultPagination, which swaps the tbody through a…
SurveyResult binds a click listener on each .filter-add-answer icon when it starts. The response tables are rendered by a separate interaction, SurveyResultPagination, which swaps the tbody through a t-out directive on every page change and on Show All. Those new rows are nodes SurveyResult never bound, so their filter icon does nothing and the page reloads on the unfiltered view. The direct binding comes from https://github.com/odoo/odoo/commit/dfc1c742e35f75f2c386c4ef50d5584537ac1ed4, which replaced the jQuery delegated handlers that used to survive re-renders. https://github.com/odoo/odoo/commit/c2f0f681714fcb936ce058e8dd4f1b1e9fa7448c reattaches them on tab change but not on pagination or Show All, so only the first page works. Bind updateContent on .pagination_wrapper, which holds the page links and the Show All button and stays outside the re-rendered tbody. A click on either bubbles up and rebinds .filter-add-answer on the rows that were just rendered. Steps to reproduce: 1. Install survey 2. Create a survey with a Date question 3. Share it and record more than ten responses so the responses table spans several pages 4. Open the survey results page and click the list icon on the date question to show the User Responses table 5. Move to page 2 and click the filter icon on any row => The page reloads on the unfiltered view and the selected date is not applied Ticket [link](https://www.odoo.com/odoo/project.task/6238514) opw-6238514 Forward-Port-Of: odoo/odoo#268569
In this PR https://github.com/odoo/odoo/pull/237069 we used t-att-class for applying the style and because of that style was not properly applying and ICE was visible on address page on initial rendering for a moment even it does not have anything to do with MA. Used t-att-style instead t-att-class. task-5208254
Original PR description
In this PR https://github.com/odoo/odoo/pull/237069 we used t-att-class for applying the style and because of that style was not properly applying and ICE was visible on address page on initial rendering for a moment even it does not have anything to do with MA. Used t-att-style instead t-att-class. task-5208254
Problem: When creating a product from an invoice form view, the list price set on the product is not saved on the product template. After creating the product, when checking the product from the products list, the set list price is not shown. Steps to reproduce: 1. Go to Accounting > Customers > Invoices 2. Create a new invoice 3. Create and edit a new product from the invoice line and set a new list price for the new product 4. Go to Sales > Products and open the created product 5. Che
Original PR description
Problem: When creating a product from an invoice form view, the list price set on the product is not saved on the product template. After creating the product, when checking the product from the…
Problem: When creating a product from an invoice form view, the list price set on the product is not saved on the product template. After creating the product, when checking the product from the products list, the set list price is not shown. Steps to reproduce: 1. Go to Accounting > Customers > Invoices 2. Create a new invoice 3. Create and edit a new product from the invoice line and set a new list price for the new product 4. Go to Sales > Products and open the created product 5. Check the list price of the product 6. Notice how the set list price is not shown on the product form view Cause: Product variants show lst_price while product templates show list_price on their form views. When creating a product from the invoice line, the shown form is for the product variant. When setting the list price on the product variant form (lst_price in that case), the inverse method of llst_price, which sets the list_price in return, is called during the creation of the variant, after the template has been created and saved. As a result, editing the list price does not trigger a write on the already saved product template, causing the list_price of the variant to be different than the list_price of the template. opw-6272825
Stacktrace during Peppol file generation when creating downpayment from POS - activate peppol (and set system parameter to peppol demo) - create a sale order - on the pos, select order, choose downpayment - pay (for example by card) -> error: <img width="461" height="276" alt="image" src="https://github.com/user-attachments/assets/aadc9467-04e5-413a-97ea-8378252a56a0" /> When an invoice line has no description (name=False in Odoo ORM), base_line.get('name', '') returns False because
Original PR description
Stacktrace during Peppol file generation when creating downpayment from POS - activate peppol (and set system parameter to peppol demo) - create a sale order - on the pos, select order, choose…
Stacktrace during Peppol file generation when creating downpayment from POS
- activate peppol (and set system parameter to peppol demo)
- create a sale order
- on the pos, select order, choose downpayment
- pay (for example by card)
-> error:
<img width="461" height="276" alt="image" src="https://github.com/user-attachments/assets/aadc9467-04e5-413a-97ea-8378252a56a0" />
When an invoice line has no description (name=False in Odoo ORM), base_line.get('name', '') returns False because the key exists. Using `or ''` ensures we always get a string before calling .replace().
Reproduces when generating Peppol XML for a POS down-payment invoice with no line description.
Description of the issue/feature this PR addresses:
Current behavior before PR:
stacktrace:
```
2026-07-03 12:19:39,347 46424 ERROR bsr odoo.http: Exception during request handling.
Traceback (most recent call last):
File ".../lib/python3.13/site-packages/odoo/http.py", line 2856, in __call__
response = request._serve_db()
File ".../lib/python3.13/site-packages/odoo/http.py", line 2331, in _serve_db
raise self._update_served_exception(exc)
File ".../lib/python3.13/site-packages/odoo/http.py", line 2329, in _serve_db
return service_model.retrying(serve_func, env=self.env)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/service/model.py", line 188, in retrying
result = func()
File ".../lib/python3.13/site-packages/odoo/http.py", line 2384, in _serve_ir_http
response = self.dispatcher.dispatch(rule.endpoint, args)
File ".../lib/python3.13/site-packages/odoo/http.py", line 2599, in dispatch
result = self.request.registry['ir.http']._dispatch(endpoint)
File ".../lib/python3.13/site-packages/odoo/addons/base/models/ir_http.py", line 353, in _dispatch
result = endpoint(**request.params)
File ".../lib/python3.13/site-packages/odoo/http.py", line 838, in route_wrapper
result = endpoint(self, *args, **params_ok)
File ".../lib/python3.13/site-packages/odoo/addons/web/controllers/dataset.py", line 32, in call_kw
return call_kw(request.env[model], method, args, kwargs)
File ".../lib/python3.13/site-packages/odoo/service/model.py", line 97, in call_kw
result = method(recs, *args, **kwargs)
File ".../lib/python3.13/site-packages/odoo/addons/pos_sale/models/pos_order.py", line 59, in sync_from_ui
data = super().sync_from_ui(orders)
File ".../lib/python3.13/site-packages/odoo/addons/pos_enterprise/models/pos_order.py", line 25, in sync_from_ui
data = super().sync_from_ui(orders)
File ".../lib/python3.13/site-packages/odoo/addons/point_of_sale/models/pos_order.py", line 1271, in sync_from_ui
order_ids.append(self._process_order(order, False))
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/pos_online_payment/models/pos_order.py", line 78, in _process_order
return super()._process_order(order, existing_order)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/point_of_sale/models/pos_order.py", line 151, in _process_order
return pos_order._process_saved_order(draft)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/point_of_sale/models/pos_order.py", line 161, in _process_saved_order
self._generate_pos_order_invoice()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File ".../lib/python3.13/site-packages/odoo/addons/point_of_sale/models/pos_order.py", line 1196, in _generate_pos_order_invoice
invoice.with_context(skip_invoice_sync=True)._generate_and_send()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File ".../lib/python3.13/site-packages/odoo/addons/account/models/account_move.py", line 6770, in _generate_and_send
wizard.action_send_and_print(allow_fallback_pdf=allow_fallback_pdf)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/account_peppol/wizard/account_move_send_wizard.py", line 78, in action_send_and_print
return super().action_send_and_print(allow_fallback_pdf=allow_fallback_pdf)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/account/wizard/account_move_send_wizard.py", line 397, in action_send_and_print
attachments = self._generate_and_send_invoices(
self.move_id,
**self._get_sending_settings(),
allow_fallback_pdf=allow_fallback_pdf,
)
File ".../lib/python3.13/site-packages/odoo/addons/account/models/account_move_send.py", line 833, in _generate_and_send_invoices
self._generate_invoice_documents(moves_data, allow_fallback_pdf=allow_fallback_pdf)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/account/models/account_move_send.py", line 723, in _generate_invoice_documents
self._hook_invoice_document_before_pdf_report_render(invoice, invoice_data)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/account_edi_ubl_cii/models/account_move_send.py", line 132, in _hook_invoice_document_before_pdf_report_render
._export_invoice(invoice)
~~~~~~~~~~~~~~~^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_20.py", line 46, in _export_invoice
document_node = self._get_invoice_node(vals)
File ".../lib/python3.13/site-packages/odoo/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_21.py", line 21, in _get_invoice_node
document_node = super()._get_invoice_node(vals)
File ".../lib/python3.13/site-packages/odoo/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_20.py", line 142, in _get_invoice_node
self._add_invoice_line_nodes(document_node, vals)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_bis3.py", line 240, in _add_invoice_line_nodes
self._ubl_add_invoice_line_nodes(sub_vals)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/account_edi_ubl_cii/models/account_edi_ubl.py", line 1716, in _ubl_add_invoice_line_nodes
self._ubl_add_invoice_line_node(sub_vals)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_bis3.py", line 222, in _ubl_add_invoice_line_node
vals['line_node'].update(self._get_invoice_line_node(sub_vals))
~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_20.py", line 415, in _get_invoice_line_node
self._add_invoice_line_item_nodes(line_node, vals)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_bis3.py", line 205, in _add_invoice_line_item_nodes
self._ubl_add_line_item_node(sub_vals)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/account_edi_ubl_cii/models/account_edi_ubl.py", line 1092, in _ubl_add_line_item_node
self._ubl_add_line_item_name_description_nodes(sub_vals)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File ".../lib/python3.13/site-packages/odoo/addons/account_edi_ubl_cii/models/account_edi_ubl.py", line 929, in _ubl_add_line_item_name_description_nodes
description = line_name.replace(name, '').strip() # Remove the redundant product's name from the description.
^^^^^^^^^^^^^^^^^
AttributeError: 'bool' object has no attribute 'replace'
```
Desired behavior after PR is merged:
PEPPOL file is generated
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#274034`_add_manual_models` builds Python model classes for custom ("manual") `ir.model` records with a raw `SELECT *` on the `ir_model` table, then hands each row to `_instanciate_attrs`, which reads a fixed list of expected columns (`model`, `name`, `abstract`, `transient`, `order`, `fold_name`, `info`) via dict subscription (`model_data['xxx']`). This assumes every one of these fields is always backed by a real database column. That assumption breaks whenever a field originally declared as stored
Original PR description
`_add_manual_models` builds Python model classes for custom ("manual") `ir.model` records with a raw `SELECT *` on the `ir_model` table, then hands each row to `_instanciate_attrs`, which reads a…
`_add_manual_models` builds Python model classes for custom ("manual") `ir.model` records with a raw `SELECT *` on the `ir_model` table, then hands each row to `_instanciate_attrs`, which reads a fixed list of expected columns (`model`, `name`, `abstract`, `transient`, `order`, `fold_name`, `info`) via dict subscription (`model_data['xxx']`).
This assumes every one of these fields is always backed by a real database column. That assumption breaks whenever a field originally declared as stored on `ir.model` gets redeclared as non-stored (`store=False`) by another module inheriting the same model: Odoo correctly drops the now-unneeded column during that module's `_auto_init()`, but any code still doing `SELECT *` on the table no longer gets that key in the result row, and the subscript access raises a bare `KeyError`. This aborts the *entire* registry load, for every model in the database, not just the one whose field changed -- because `_add_manual_models` runs once per registry load and any database with at least one manual/custom model exercises it.
This is exactly the scenario introduced by 267bec440d56 ("[IMP] base: add IrModel.abstract field"): it added a new, plain stored `abstract` field on `ir.model`. `web_studio` has independently defined its own `abstract` field on `ir.model` since 2016 (ac1129f6577), as a computed, `store=False` field. Once both are loaded together, Odoo's field- merging logic settles on the `store=False` definition, so `_auto_init()` drops the `abstract` column -- and any database that also has at least one manual model (built through Studio, or via a module that dynamically generates SQL-view-backed models) then fails to boot its registry at all:
File ".../odoo/orm/model_classes.py", line 554, in _add_manual_models
attrs = env['ir.model']._instanciate_attrs(model_data)
File ".../odoo/addons/base/models/ir_model.py", line 487, in _instanciate_attrs
'_abstract': bool(model_data['abstract']),
KeyError: 'abstract'
Fix `_instanciate_attrs` to fall back to a sane default instead of assuming the column exists. This doesn't just patch the `base`/ `web_studio` collision -- it hardens `_add_manual_models` against any future field made non-stored by an inheriting module, which is otherwise a silent, whole-database-breaking failure with no visible relation to whichever module actually changed the field.
A companion fix removing web_studio's now-redundant override (the actual root cause of the collision) is proposed separately.
Steps to reproduce:
1. Install `base` and `web_studio` together (or in either order) --
Odoo's field-merging settles on web_studio's `store=False`
definition, but leaves the `abstract` column physically in place;
it is not dropped automatically.
2. Have at least one manual model in the database (`state='manual'`
on `ir.model`, e.g. created through Settings > Technical > Models).
3. Actually remove the now-orphaned `abstract` column -- e.g. via the
OCA `database_cleanup` module's column-purge wizard
(`cleanup.purge.wizard.column`), which explicitly targets exactly
this kind of column (computed + non-stored fields are excluded
from its "keep" list, so `abstract` gets flagged and dropped).
4. Restart Odoo / reload the registry: it fails to boot entirely with
a KeyError on that field name, for every model, not just the one
whose owning module changed.
Related: odoo/enterprise#123572
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#275092Miscellaneous changes
Before this commit, calls to the route `/shop?search=<search_term>` would lead the search for `product_public_categories` to use a query in the shape of `product_id in (<list_of_ids>)`. In the case where the cardinality of the product table becomes large (> 10k products), if `search_term` is very broad-spectrum, the length of `list_of_ids` reaches the size of the product table. In that case the query generated to search for categories becomes significantly slow to parse (> 14 seconds in our te
Original PR description
Before this commit, calls to the route `/shop?search=<search_term>` would lead the search for `product_public_categories` to use a query in the shape of `product_id in (<list_of_ids>)`. In the case…
Before this commit, calls to the route `/shop?search=<search_term>` would lead the search for `product_public_categories` to use a query in the shape of `product_id in (<list_of_ids>)`. In the case where the cardinality of the product table becomes large (> 10k products), if `search_term` is very broad-spectrum, the length of `list_of_ids` reaches the size of the product table. In that case the query generated to search for categories becomes significantly slow to parse (> 14 seconds in our tests). This commit proposes as an alternative to resort to a sub-query to fetch the product ids that are relevant in the context of `product_public_categories`. Because of how the list of products is acquired initially, we can't rely on the `_search()` method to generate the sub-query. The following table shows average gains for "increasing search term specificity". Here, "specificity" is determined by the amount of characters in the search term and the related number of hits expected from the search. ### Cardinality ~250k records | Metric | ~228,872 hits | ~2,870 hits | ~377 hits | ~11 hits | | :--- | :--- | :--- | :--- | :--- | | Before Fix | 14.027s | 5.726s | 3.183s | 3.139s | | After Fix | 1.216s | 3.794s* | 1.330s | 1.301s | ### Cardinality 10k records | Metric | 8,897 hits | 1,122 hits | 15 hits | 0 hits | | :--- | :--- | :--- | :--- | :--- | | Before Fix | 1.386s | 3.232s* | 0.580s | 0.395s | | After Fix | 0.756s | 3.410s* | 0.772s | 0.572s | *Note: The increase in response time in the second column is caused by a different execution path taken (fuzzy search) which becomes the most significant path in terms of execution time once the other queries are optimized. opw-6299343 Forward-Port-Of: odoo/odoo#271306
When opening the bank rec widget on huge DB's, it takes a lot of time to load everything. This commit aims to improve the loading performances by removing some JS fields: 1 - reconciled_lines_ids: We only use the first element of this recordset in JS, so we add a new computed field to only send 1 record to the JS 2 - hasAttachment: replace the long JS computation of get hasAttachment with a python computed field. 3 - Replace matched_credit_ids and matched_debit_ids with exc
Original PR description
When opening the bank rec widget on huge DB's, it takes
a lot of time to load everything.
This commit aims to improve the loading performances by
removing some JS fields:
1 - reconciled_lines_ids: We only use the first element of
this recordset in JS, so we add a new computed field
to only send 1 record to the JS
2 - hasAttachment: replace the long JS computation of
get hasAttachment with a python computed field.
3 - Replace matched_credit_ids and matched_debit_ids
with exchange_diff_partial_ids.
Linked:https://github.com/odoo/enterprise/pull/119557
task-6275945
Forward-Port-Of: odoo/odoo#269119