Friday, September 18, 2026
40 changes · 19.0
Security fixes and vulnerability patches
This update restores user verification for IoT websocket communication, matching the behavior from earlier versions. It helps ensure IoT interactions are tied to a valid user, reducing the risk of unintended access or actions.
Original PR description
Restore to pre 19.0 behaviour by checking user.
Enhancements to existing features
Romanian eTransport declarations can now be generated for supported dropship operations, including domestic and international business-to-consumer flows. This helps companies using dropshipping comply with Romanian transport reporting requirements directly in Odoo, while unsupported business-to-business dropship cases now show clear errors.
Original PR description
Before this commit: Dropship operations were not supported by the Romanian eTransport integration. As a result, eTransport declarations could not be generated for dropshipping flows. After this commit: eTransport declarations can now be generated for dropship operations, allowing the supported dropshipping scenarios to be processed through the Romanian eTransport workflow. task-6391489 Forward-Port-Of: odoo/odoo#280199
Resolved issues and error corrections
Fixed an issue where sending a Peppol invoice with a blank invoice line could fail with a technical error instead of showing the normal validation message. Users now receive a clear warning about missing item information, helping them correct the invoice and continue the sending process.
Original PR description
### Steps to Reproduce: 1. Activate Peppol 2. Create invoice for customer with Peppol 3. Add line without product name or label 4. Send invoice through Peppol and observe the Traceback error ###…
### Steps to Reproduce: 1. Activate Peppol 2. Create invoice for customer with Peppol 3. Add line without product name or label 4. Send invoice through Peppol and observe the Traceback error ### Issue: When generating a Peppol invoice, there will be a Traceback error if an invoice line is missing both a product and a label. The XML builder evaluates the missing `cbc:Name` element as `None` instead of an empty dictionary. Therefore, attempting to directly subscript `['_text']` on it throws a TypeError traceback. This prevents the user from seeing the standard validation warning about the missing item data. ### Solution: We can update the document node constraint check to safely access the dictionary keys using `.get()` with an empty dictionary fallback. This prevents the traceback and ensures that the system will correctly display the intended validation error to the user. opw-6577441 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#288713
Documentation and clarification updates
Joris Debonnet has submitted confirmation that the required contributor license agreement has been signed. This administrative update helps ensure contributions can be reviewed and accepted under Odoo's contribution rules.
Original PR description
I confirm I have just signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Inventory transfers with many stock moves now open much faster by reducing unnecessary repeated calculations. This improves responsiveness for warehouse teams working with large pickings and delivery flows.
Original PR description
Opening a picking with a large amount of moves is very slow due to some compute fields doing this rn to see if tests fail or not **Pre fix** /web/dataset/call_kw/stock.picking/web_read# - 786 11.559…
Opening a picking with a large amount of moves is very slow due to some compute fields
doing this rn to see if tests fail or not
**Pre fix**
/web/dataset/call_kw/stock.picking/web_read# - 786 11.559 51.289: 62.7 Seconds
**Fix 1** - local measurement
/web/dataset/call_kw/stock.picking/web_read - 741 11.252 39.448 - 50.6 Seconds
```
+
+ ins_seed = OrderedSet(ins.ids)
for out in outs:
# stop the rollup at the in moves: what feeds them is still outside the warehouse
- linked_move_ids = out._rollup_move_origs(seen=OrderedSet(ins._ids)) - ins_ids
+ linked_move_ids = out._rollup_move_origs(seen=ins_seed.copy()) - ins_ids
linked_moves_per_out[out] = self.env['stock.move'].browse(linked_move_ids)
```
**Fix 2** (Changes fix 1)
/web/dataset/call_kw/stock.picking/web_read - 784 11.331 27.077 - 33.0 Seconds
```
- def _rollup_move_dests(self, seen=False) -> OrderedSet[int]:
- return self._rollup_moves(origin=False, seen=seen)
+ def _rollup_move_dests(self, seen=False, boundary=frozenset()) -> OrderedSet[int]:
+ return self._rollup_moves(origin=False, seen=seen, boundary=boundary)
- def _rollup_move_origs(self, seen=False) -> OrderedSet[int]:
- return self._rollup_moves(seen=seen)
+ def _rollup_move_origs(self, seen=False, boundary=frozenset()) -> OrderedSet[int]:
+ return self._rollup_moves(seen=seen, boundary=boundary)
- def _rollup_moves(self, origin=True, seen=False) -> OrderedSet[int]:
+ def _rollup_moves(self, origin=True, seen=False, boundary=frozenset()) -> OrderedSet[int]:
"""
Find all moves in chain depending the direction (origin)
origin: if set (default), returns the origin moves, else return the destinations
+ boundary: ids at which to stop the rollup (excluded from the result). Unlike `seen`,
+ this is never copied or mutated, so callers can share one boundary set across many
+ independent calls (e.g. one per record of a big recordset) without paying its size
+ on every call.
"""
target_field = "move_orig_ids" if origin else "move_dest_ids"
if not seen:
seen = OrderedSet()
- unseen = OrderedSet(self.ids) - seen
+ unseen = (OrderedSet(self.ids) - seen) - boundary
if not unseen:
return seen
seen.update(unseen)
- self.filtered(lambda m: m.id in unseen)[target_field]._rollup_moves(origin, seen)
+ self.filtered(lambda m: m.id in unseen)[target_field]._rollup_moves(origin, seen, boundary)
return seen
```
```
for out in outs:
# stop the rollup at the in moves: what feeds them is still outside the warehouse
- linked_move_ids = out._rollup_move_origs(seen=OrderedSet(ins._ids)) - ins_ids
+ linked_move_ids = out._rollup_move_origs(boundary=ins_ids)
linked_moves_per_out[out] = self.env['stock.move'].browse(linked_move_ids)
```
**Fix 3**
/web/dataset/call_kw/stock.picking/web_read - 818 5.308 18.648 - 23.9 Seconds
```
def available_carriers(self, partner, source):
- return self.filtered(lambda c: c._match(partner, source))
+ # _match_must_have_tags/_match_excluded_tags/_match_weight/_match_volume each
+ # independently recompute data from source.move_ids/order_line, and none of it depends
+ # on which carrier is being checked. Precompute it once here instead of once per
+ # candidate carrier inside .filtered() below (source's lines can be numerous).
+ match_data = self._prepare_source_match_data(source)
+ return self.filtered(lambda c: c._match(partner, source, match_data))
+
+ def _prepare_source_match_data(self, source):
+ if source._name == 'sale.order':
+ lines = source.order_line
+ products = lines.product_id
+ elif source._name == 'stock.picking':
+ lines = source.move_ids
+ products = lines.with_prefetch().mapped('product_id')
+ else:
+ raise UserError(_("Invalid source document type"))
+ return {
+ 'product_tag_ids': products.all_product_tag_ids,
+ 'total_weight': sum(line.product_id.weight * line.product_qty for line in lines),
+ 'total_volume': sum(line.product_id.volume * line.product_qty for line in lines),
+ }
- def _match(self, partner, source):
+ def _match(self, partner, source, match_data=None):
self.ensure_one()
+ if match_data is not None:
+ return (
+ self._match_address(partner)
+ and (not self.must_have_tag_ids or any(tag in match_data['product_tag_ids'] for tag in self.must_have_tag_ids))
+ and not any(tag in match_data['product_tag_ids'] for tag in self.excluded_tag_ids)
+ and (not self.max_weight or match_data['total_weight'] <= self.max_weight)
+ and (not self.max_volume or match_data['total_volume'] <= self.max_volume)
+ )
return (
self._match_address(partner)
and self._match_must_have_tags(source)
```The Belgian payroll module now includes the new CP200 homeworking representation fee amount of 164.21, effective September 1, 2026. This helps payroll calculations stay aligned with the latest expected Belgian sector parameter for eligible employees.
Original PR description
Add a value of 164.21 for the rule parameter "CP200: Representation Fee - Homeworking - Amount" with `date_from` set to September 1, 2026. Task:6580623
Fixes an issue where dropship valuation entries could count the same analytic allocation twice when a sale and purchase were linked to the same stock move. This keeps cost reporting accurate for dropshipped sales that use analytic distribution rules.
Original PR description
Steps to reproduce: - Set the product category to Automated (perpetual) Inventory Valuation. - Set up an Analytic Distribution Model for a vendor. - Confirm a sale order linked to a project, with a…
Steps to reproduce: - Set the product category to Automated (perpetual) Inventory Valuation. - Set up an Analytic Distribution Model for a vendor. - Confirm a sale order linked to a project, with a line using the Dropshipping route for that vendor, then validate the dropship delivery. Cause of the issue: A dropship move is weird: the same stock move is linked to both a purchase line and a sale line at once (one move covers both sides). When we build the stock valuation entry for that move, two different modules each add their own analytic distribution on top: - purchase_stock adds the purchase line's distribution (vendor's ADM, already combined with the project account by project_purchase) - sale_stock also adds the sale line's distribution, which already has the project account in it too Both just do a plain dict union, no normalization. On a normal delivery or a normal receipt only one of these ever kicks in, but on a dropship move both fire on the same line, so the project account's share gets counted twice (100% + 100% = 200%). Solution: Don't fall back to the sale line's distribution when the move already has a purchase line attached (that's the dropship case) — the purchase line's distribution is already the full, correct one for that move. The test uses two Analytic Distribution Models (one on the vendor, one on the customer) instead of the project app, since stock_dropshipping doesn't depend on project. opw-6383779 Forward-Port-Of: odoo/odoo#282751
This update refreshes Odoo's spreadsheet component and fixes an issue where pivot table running totals could be incorrect when some dimensions were collapsed or hidden. It also improves spreadsheet demo behavior, helping ensure more reliable spreadsheet reporting and examples for users.
Original PR description
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/493acaf76d [REL] 19.0.51 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0)…
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/493acaf76d [REL] 19.0.51 [Task: 0](https://www.odoo.com/odoo/2328/tasks/0) https://github.com/odoo/o-spreadsheet/commit/dbe0b34980 [IMP] demo: debounce xml template build [Task: 6574005](https://www.odoo.com/odoo/2328/tasks/6574005) https://github.com/odoo/o-spreadsheet/commit/54b5a6f9a5 [FIX] demo: fix scorecard demo definition [Task: 6573109](https://www.odoo.com/odoo/2328/tasks/6573109) https://github.com/odoo/o-spreadsheet/commit/fbfc45aaaa [FIX] pivot: wrong running total with collapsed/hidden dimensions [Task: 6569607](https://www.odoo.com/odoo/2328/tasks/6569607) 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>
Invoice imports with fixed taxes now better handle small rounding differences when quantities are greater than one. This prevents valid fixed taxes from being missed during import, reducing incorrect tax matching issues and manual corrections.
Original PR description
When importing an invoice with fixed taxes, if the line has a quantity of more than 1, sometimes, due to rounding errors, searching for the fixed tax fails to find it, as the values needed to exactly match, a new search method was added to give a margin of +/- 0.01 when searching for valid taxes. task-id-6307992 Forward-Port-Of: odoo/odoo#271842
Creating an onsite event from the Onsite action now uses the current user's employee record when no employee is provided. This prevents an error and ensures the new event is visible in the expected onsite event view.
Original PR description
The Onsite action only sets the hr_skills_event_add_employee key in its context, without any employee. Reading default_employee_id directly then raised a KeyError. Even before that, no attendee was added, so the new event did not match the domain of the action and stayed hidden in the kanban view. We now fall back on the employee of the current user. taskid-6361432
This fixes an internal manufacturing test that could fail depending on the timezone where it was run. The change improves confidence in automated checks without changing day-to-day user functionality.
Original PR description
**PROBLEM**
In `test_generate_serial_button_sequence()` we generate a serial number based on the day of the year. In ir_sequence, we use the time based on the environment timezone, but in the test, we don't use any timezone. This can lead the assertion to fail, since the day of the year can differ with the timezone used.
**REPRO STEPS**
1. edit the freeze_time in the test to `freeze_time('2024-01-15T23:00:00')`
If your timezone is UTC+2, then the time according to your timezone will be `2024-01-16T01:00:00`
So, without in UTC+0, it's the 15th day of the year, but in UTC+2 it's already the 16th.
Remove the timezone in the last assertIn() (ie, remove the fix).
(if your timezone is different, adjust the freeze_time accordingly)
2. run the test and see it fails.
runbot-237780This fixes an issue where migrated databases could show an error when a confirmed purchase order line had only its unit price changed. Updating the purchase email template data ensures purchase order changes can be processed normally without template rendering failures.
Original PR description
In this commit: eadf127 the `track_po_line_template` template was added in a `noupdate` file. The commit specifies: “put their declaration in no update when not done if template has no technical code or complex dependency on underlying code;” However, this is not actually the case for the two templates in this file. This did not cause any error in v17, but errors started appearing later because of #254602, which modified this code and the related Python code, leading to an error when no product quantity is changed. Steps to reproduce in a 19.3 database migrated from v19: - Create a purchase order with one product line and confirm it. - Only change the unit price of that line. - Error: "Error rendering template: ..." While this can also be considered a migration issue, I think the simpler and more logical solution is to remove the noupdate from this file. opw-6518729
Italian Split Payment taxes are now configured so VAT report amounts are calculated on the correct base values and shown with the correct sign. Invoice tax labels now clearly identify Split Payment rates, reducing reporting mistakes and customer confusion.
Original PR description
The`SP` (Split Payment) taxes have incorrect configurations: - **Tax Grid Tags on `SP` Taxes**: The ve38 grid tag was incorrectly assigned to the tax line instead of the base line. - **`VE38` VAT…
The`SP` (Split Payment) taxes have incorrect configurations: - **Tax Grid Tags on `SP` Taxes**: The ve38 grid tag was incorrectly assigned to the tax line instead of the base line. - **`VE38` VAT Report Formula**: In Tax Report > VAT Report, the line `VE38 - Transactions with parties referred to in Article 17-ter` was using a negative formula `-ve38` and should be `ve38` - **Tax Group Invoice Label**: Updated the default label on invoices for the SP tax group to display 22% SP instead of 22%. Steps to reproduce: - Install `account` and `l10n_it` - Switch to IT company - Go to taxes and filter for `SP` taxes - The tax grid is wrong for the negative taxes since `ve38` should be in the base line instead of in the tax line - Go in the Tax report > VE VAT Report - The line `VE38 - Transactions with parties referred to in Article 17-ter` has a negative formula `-ve38`, should be positive `ve38` - The label on invoices of the tax group should be `4% SP`, `5% SP`, `10% SP` not `4%` etc. References: https://www.informazionefiscale.it/IMG/pdf/dichiarazione_modello_iva_2026_agenzia_delle_entrate.pdf <img width="1413" height="141" alt="immagine" src="https://github.com/user-attachments/assets/7fa15859-beaa-4345-bf81-fedc3f0af2fa" /> https://fiscomania.com/quadro-ve-della-dichiarazione-iva/ <img width="705" height="154" alt="immagine" src="https://github.com/user-attachments/assets/4f23b407-0e4d-4104-9695-f9891ccfd2f2" /> Ticket [link](https://www.odoo.com/odoo/project.task/6543520) opw-6543520
This fix prevents manufacturing costs in one company from accidentally using purchase cost history from another company. It improves the accuracy of FIFO product costing in multi-company setups and helps keep each company's inventory valuation separate.
Original PR description
**Problem**: In a multi-company environment, while manufacturing a product, if the component of the product is visible for both company and there is no last_in stock move for the component in the current company, then the last_in stock move of the other company is used to compute the cost of the component. This only happens when the costing method of the product is FIFO **Steps to reproduce:** 1. Create company A and company B. 1. Create a product A and set FIFO costing method to it. 2. Make a purchase order of product A in company A and receive it. 3. Create a product B with product A as its BOM material. 4. Create a MO of product B in company B and produce it. 5. The unit cost of product A in company B is using the unit cost from the purchase order of product A in company A. **Fix**: Add a company domain to the last_in stock move search to prevent cross-company last_in stock move search. opw-6411216
Product option pills in the sales configurator now keep consistent spacing when they wrap onto multiple lines. This improves readability and avoids a cramped layout for products with many attribute choices.
Original PR description
Pill-style attribute values used Bootstrap's list-inline/list-inline-item, which only sets margin-right between items. When pills wrapped onto a new line, the rows touched with no vertical gap. Fix: Switch the pill list to a flex container with gap-2, matching the spacing website_sale already uses for its own attribute-value lists, so wrapping rows get the same gap as pills on the same row. opw-6584507 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This fix prevents an error that could occur when creating an event linked to employee skills if employee information is missing from the setup context. It helps keep the event creation process reliable and avoids unnecessary interruptions for users.
Original PR description
The create method searches for an employee by matching the ID with the `default_employee_id` in the context. This is not guaranteed to exist; thus, a KeyError is possible. Instead, we can use a get. opw-6581421
Fixes an issue in the HTML editor where the font size shown in the toolbar could remain outdated after removing formatting. This makes editing clearer and prevents unnecessary formatting markup when users change font sizes on headings or default text styles.
Original PR description
### Steps To Reproduce: - Add this content in the HTML editor: `<h2 class=display-3-fs>heading 2</h2>` - Select the heading text. - Click Remove Format. - Check the font-size input in the toolbar.…
### Steps To Reproduce: - Add this content in the HTML editor: `<h2 class=display-3-fs>heading 2</h2>` - Select the heading text. - Click Remove Format. - Check the font-size input in the toolbar. ### Description of the issue/feature this PR addresses: - The font-size class was removed from the block element, but the font-size input still displayed the value associated with the removed class. - The toolbar did not show the correct font size for default block classes (like `o_default_font_size`). - Applying a new font size inside default block classes created nested spans instead of splitting them. - The "Remove format" button remained enabled even when selection had no custom formatting (only default block classes). ### Desired behavior after PR is merged: - The font-size input is updated after removing the font-size class and correctly displays the font size of the resulting block element. - Update the `getFontSizeDisplayValue` utility to find correct CSS variable dynamically to also find the font size of default block classes. - Add default font size classes (like `o_default_font_size` and headings) to `format_class_predicates` resource in `font_plugin.js`. This allows them to be split and replaced instead of creating nested spans. task-6321166 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Invoices that have already been sent through PEPPOL can no longer have the PEPPOL sending option selected again. This prevents users from accidentally attempting to resend an invoice through the same electronic invoicing channel and makes the form behavior clearer.
Original PR description
If the invoice is already sent through PEPPOL, the checkbox for sending the invoice should then be readonly and unchecked. Currently is correctly unchecked but not readonly. We fix it by copying what French localization (`l10n_fr_pdp`) does already, giving a reason for disabling. <img width="1359" height="948" alt="immagine" src="https://github.com/user-attachments/assets/6f91ab34-8989-487e-8cd9-96dafee7bcb5" /> . Pad: https://pad.odoo.com/p/accountingv20 pad-accountingv20 Forward-Port-Of: odoo/odoo#288898
This fixes a case where linked child contacts could show an incorrect EU VAT validation result when processed before their parent company. Parent companies are now validated first, so child contacts that share the same VAT number inherit the correct result and business records remain accurate.
Original PR description
**Description of the issue/feature this PR addresses:** When a batch recompute of `vies_valid` contains both a parent (commercial) partner and a child sharing the same VAT, and the child happens to…
**Description of the issue/feature this PR addresses:**
When a batch recompute of `vies_valid` contains both a parent (commercial) partner and a child sharing the same VAT, and the child happens to be iterated before its parent, the child ends up with `vies_valid=False` instead of the parent's real, freshly-checked value.
**Current behavior before PR:**
`_compute_vies_valid` loops over `self` in whatever order the batch happens to have:
```python
for partner in self:
...
if partner.parent_id and partner.parent_id.vies_vat_to_check == partner.vies_vat_to_check:
partner.vies_valid = partner.parent_id.vies_valid
continue
status = partner._check_vies_iap()
partner._update_vies_status(status)
```
`Field.compute_value()` removes the whole batch from the "to compute" queue *before* running this loop (it does so upfront, in case the method does not assign every record). So when the loop reaches a child and reads `partner.parent_id.vies_valid` to reuse it, that field is no longer marked "to compute" for the parent, and reading it just returns whatever is currently cached/stored — which, if the parent has not been processed yet in this same loop, is still the old/default value. The child copies that stale value and, being a stored field, keeps it forever: nothing re-triggers its computation afterwards.
Example:
```python
parent = env['res.partner'].create({'name': 'Parent Co'})
child = env['res.partner'].create({'name': 'Child Address', 'parent_id': parent.id})
child.vat = 'BE0477472701' # queues the child's compute first
parent.vat = 'BE0477472701' # queues the parent's compute second
child.vies_valid # False, even though the VAT is valid
parent.vies_valid # True
```
**Desired behavior after PR is merged:**
Every partner without a parent is always computed before any child that may reuse its value, regardless of the batch's original order — so the child correctly ends up with the parent's real `vies_valid`.
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Forward-Port-Of: odoo/odoo#287251
Forward-Port-Of: odoo/odoo#279927Leave for flexible employees now correctly marks every full day as unavailable, including the first and last days. This prevents planning and attendance views from showing partial or missing leave days, giving managers a more accurate view of employee availability.
Original PR description
### Steps to reproduce: - Create a flexible employee - Create a leave from a Monday to a Friday for this employee - Navigate to Planning gantt view and check the week where the created leave exists - Notice only 3 days are greyed-out ### Cause: This is happening because in planning we normally exclude days that are not fully unavailable and since flexible doesn't have a specific start and end hours for the working days the start and end days for the flexible leaves are excluded from the unavailabiliy. This is also happening in `hr_attendance` for example but in a different way where the start and end days of the leave will be shown half greyed-out ### Fix: For _unavailable_intervals_batch we make flexible leaves cover the whole days of the leave opw-6142094 Forward-Port-Of: odoo/odoo#261597
On mobile checkout, the order summary will no longer stay fixed over address fields when shoppers open the on-screen keyboard. This makes it easier for Android users to complete checkout without fields being hidden.
Original PR description
The mobile summary element hides the address form on Android Steps to reproduce: 1. Install eCommerce 2. On android (or using browserstack) and without being logged in, go to the eCommerce 3. Add any…
The mobile summary element hides the address form on Android Steps to reproduce: 1. Install eCommerce 2. On android (or using browserstack) and without being logged in, go to the eCommerce 3. Add any product to the cart and go to checkout 4. Click on "Checkout" to enter the address details 5. Open the keyboard by clicking inside the first input 6. The virtual keyboard opens, enter a name and click on "Next" in the virtual keyboard 7. Do this a couple of times: the cart summary element doesn't scroll and the input field is hidden behind it Similar problem happens when you scroll a bit after clicking in the first input: the cart summary element displayed at the bottom is still visible (on top of the virtual keyboard) which hides part of the page and makes it difficult to navigate the page and fill in the details Issue: `sticky-bottom` keeps the element at the bottom of the screen Solution: Remove class `sticky-bottom` from `o_mobile_summary` when we click in one of the input opw-6500503
Customers' credit usage now includes confirmed sales orders even when products have not yet been delivered. This prevents additional orders from being accepted without a warning when the customer has already committed beyond their credit limit.
Original PR description
### Steps to reproduce Set a credit limit of 1.000 on a customer, then: 1. Create a Sale Order for a product with Invoice Policy on **Delivered Quantities**, over that amount → the warning shows.…
### Steps to reproduce
Set a credit limit of 1.000 on a customer, then:
1. Create a Sale Order for a product with Invoice Policy on **Delivered Quantities**, over that amount → the warning shows. Good.
2. Confirm it, and deliver nothing.
3. Create a second order for the same customer → the warning never shows, even though the customer is already over the limit.
### Solution
The ordered quantity is what the customer committed to, delivered or not. Using `qty_to_invoice` instead of `uom_qty_to_consider` or `qty_delivered`
Introducing invoice_status in sales domain for `_compute_credit_to_invoice`: `untaxed_amount_to_invoice` still follows the invoicing policy, while `amount_to_invoice` no longer does. On a confirmed order for a delivery-based product with nothing delivered:
```
line.untaxed_amount_to_invoice = 0 # still gated by qty_delivered
line.invoice_status = 'no'
order.amount_to_invoice = 2000 # fixed by this PR
```
The domain filters on the first one, so the order is dropped from the search before its amount is ever read and `credit_to_invoice` stays at 0.`'no'` is the stored marker for a confirmed line that is not invoiceable yet, which is exactly what the first clause misses.
ticket: [6480211](https://www.odoo.com/odoo/project/967/tasks/6480211)
Forward-Port-Of: odoo/odoo#285578Bank synchronization no longer removes payment options that users already configured on a bank journal. This prevents custom incoming and outgoing payment setup from being lost when connecting a bank feed, while still adding any missing default options.
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 a customization issue where websites could not reliably override which flag is shown for a language. Businesses can now tailor language flag choices to better match their region, audience, or branding without duplicating the underlying field setup.
Original PR description
Which flag stands for a language is a presentation choice, not a property of its code. `_compute_field_flag_image_url` derives the URL from the country part of the locale, and a site that wants a…
Which flag stands for a language is a presentation choice, not a property of its code. `_compute_field_flag_image_url` derives the URL from the country part of the locale, and a site that wants a different flag for a language it offers, a regional one or the flag of the country it actually serves rather than the one the code names, has to compute that URL its own way.
That is not possible today. The field passes the compute as the function object:
flag_image_url = fields.Char(compute=_compute_field_flag_image_url)
`determine()` takes the `callable` branch and calls that object with the recordset. A plain function is not bound, so the implementation of `base` runs whatever class the record has: a module that inherits res.lang and defines `_compute_field_flag_image_url` changes nothing, and nothing says so. No error, no warning, and the field keeps answering the same URL. Getting around it means redeclaring the field only to replace its compute, which is more than the situation calls for.
Passing the method name puts the computation back on the MRO and costs nothing else: `Field.get_depends` resolves a string with `resolve_mro` and collects `_depends` from every implementation it finds, so the `@api.depends('code', 'flag_image')` declared here keeps applying, to this implementation and to an override alike.
This is the same kind of fix as #185419, which made the `domain` of a field reachable by an override for the same reason. Two field declarations in the codebase still pass a function object this way; the other is `website.menu.is_mega_menu`, which passes `inverse` the same way and is left alone here.Users adding reactions in Discuss can now hold Shift when selecting an emoji from the Quick Reaction Menu to keep the emoji picker open. This makes it easier to add multiple reactions in a row without repeatedly reopening the picker.
Original PR description
When adding a reaction to a message in Discuss, selecting an emoji adds the reaction and closes the emoji picker. This can be inconvenient when adding multiple reactions in a row, as the picker has to be reopened every time. To address this problem, the emoji picker remains open when the user holds Shift while selecting an emoji. However, this behavior was overlooked when implementing the Quick Reaction Menu, which therefore closes the emoji picker upon emoji selection, regardless of whether Shift is pressed. This commit fixes the Quick Reaction Menu so that holding Shift while selecting an emoji once again prevents the emoji picker from closing. [Task-6575382](https://www.odoo.com/odoo/project/1519/tasks/6575382)
This fix ensures that grid views recalculate their visible content when the page element behind them is recreated. Users should no longer see an empty grid after certain refreshes or interface updates until they scroll or resize the window.
Original PR description
See commit messages for details. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#281721
Fixed an issue where opening customer orders in Point of Sale could show a blank screen when an order belonged to a delivery address without its own name. The system now uses the related company name as a fallback, so staff can still find and view those orders normally.
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
When helpdesk tickets are merged, related timesheets now use the sales order item from the destination ticket. This prevents mismatched billing or reporting details after tickets with different sales items are combined.
Original PR description
Currently when you merge helpdesk tickets with differing sale order items, the sales order item listed on the timesheets of the source ticket is not updated to match the destination ticket's sales order item. This PR ensures uniformity bugfix-6473396 Forward-Port-Of: odoo/enterprise#131155 Forward-Port-Of: odoo/enterprise#129060
This change removes duplicated logic from the US ISO 20022 payment file process after the shared payment logic was updated. It keeps the US-specific requirement to include the bank country, reducing maintenance risk without changing the expected business workflow.
Original PR description
Since this previous [commit](https://github.com/odoo/enterprise/commit/05f175c8c9a2562beaefe331af0573f807a4a679) `MmbId` is now the location that the bank clearing code is instead of directly within `ClrSysMmbId`. This means the extra code in the US specific ISO generation system is unecessary. As such this PR removes the duplicated code and instead just focuses on adding the Country of the bank to the node which currently no other implementation requires.
This fix places company car-related payroll fields in the correct fleet payroll module instead of the base Belgian payroll module. It prevents payroll setup tests from failing when Belgian payroll is installed without fleet features, improving installation reliability.
Original PR description
On https://github.com/odoo/enterprise/pull/94557, fleet-related fields where added to the whitelist of l10n_be_hr_payroll. However those fields are added in l10n_be_hr_payroll_fleet, which causes the TestWhitelistFromTemplate.test_be_contract_template_loading test to failwhen payroll is installed but not fleet. This commit adds those fields to the whitelist on the correct module.
This fix prevents appointment resources from being treated as having negative available capacity when bookings are made from the Gantt view. It helps avoid failed website bookings and keeps capacity handling consistent for shareable appointment resources.
Original PR description
**Steps to reproduce:** - Install Appointment app - Create an appointment type based on resources, auto-assigned, with two shareable resources linked together - Set the first resource's capacity to 3…
**Steps to reproduce:**
- Install Appointment app
- Create an appointment type based on resources, auto-assigned, with two shareable resources linked together
- Set the first resource's capacity to 3 and the second resource's capacity to 4
- From the backend Gantt view, create a booking for 2 people on the first resource
- Create a second booking for 2 people on the same resource, at the same date and time
- First resource is now overbooked with reserved capacity of 4 out of 3
- Try to create an appointment from the website
- Error: "The capacity reserved should be positive."
**Issue:**
When bookings are created from the backend gantt view, the selected resource can be overbooked even if another linked resource still has available capacity.
Then when trying to book an appointment the new booking lines will trigger this constraint:
```py
_check_capacity_reserved = models.Constraint(
'CHECK(capacity_reserved >= 0)',
"The capacity reserved should be positive.",
)
```
This is caused by the negative values in:
```py
booking_line_values = []
if appointment_type.schedule_based_on == 'resources':
capacity_to_assign = asked_capacity
for resource in resources:
resource_remaining_capacity = resources_remaining_capacity.get(resource)
new_capacity_reserved = min(resource_remaining_capacity, capacity_to_assign, resource.capacity)
capacity_to_assign -= new_capacity_reserved
booking_line_values.append({
'appointment_resource_id': resource.id,
'capacity_reserved': new_capacity_reserved,
'capacity_used': new_capacity_reserved if resource.shareable and appointment_type.resource_manage_capacity else resource.capacity,
})
```
**Fix:**
Avoid negative remaining value in resource booking when computing available slots.
Note: Tried to take the capacity already used by overlapping bookings into account when assigning resource booking lines from the backend gantt view. And also force linked_resources booking when trying to book more
than the total capacity to properly dispatch as many slots as possible. But it was breaking `appointment_google_reserve` tests.
opw-6503147
Forward-Port-Of: odoo/enterprise#130520AI-generated views that group data by columns are now accepted when the setup is valid. This prevents users from being blocked when applying legitimate AI-created views, while still rejecting invalid configurations.
Original PR description
When opening a view with a column groupby, the AI view validators incorrectly rejected the generated configuration, even though it was valid. As a result, valid AI-generated views could not be applied. This commit updates the validation logic to correctly accept valid column groupbys while preserving the existing validation behavior for invalid configurations. task-6377810
This fixes the background styling for constant signature fields so they remain slightly transparent instead of appearing as a lighter solid grey. It also prevents a recurring stylesheet warning during PDF signing asset generation, reducing noise and future compatibility risk.
Original PR description
### Problem `sign/static/src/scss/iframe.scss` sets the background of `.o_sign_sign_item.o_color_constant` with ```scss background-color: hsl(0, 0%, 80%) / 0.9; ``` That slash is not the modern CSS…
### Problem `sign/static/src/scss/iframe.scss` sets the background of `.o_sign_sign_item.o_color_constant` with ```scss background-color: hsl(0, 0%, 80%) / 0.9; ``` That slash is not the modern CSS alpha syntax. Sass parses it as a division of a color by a number: `hsl(0, 0%, 80%)` is `#cccccc`, each channel is divided by 0.9 (204 / 0.9 = 226.67 -> 227) and the rule compiles to an opaque `#e3e3e3`. Constant sign items lose the transparency they were meant to have and render a shade lighter than the `o_color_responsible_*` items right above, which do use 90% alpha. Generating the `sign.assets_pdf_iframe` bundle also logs, every time: ``` DEPRECATION WARNING: The operation `#cccccc div 0.9` is deprecated and will be an error in future versions. Consider using Sass's color functions instead. ``` ### Fix Use `rgba()`, the way the two `o_color_responsible_*` blocks just above already do. Compiled with libsass 3.6.5 (the compiler `ScssStylesheetAsset` uses): | | compiled output | | --- | --- | | before | `background-color: #e3e3e3;` plus the deprecation warning | | after | `background-color: rgba(204, 204, 204, 0.9);`, no warning | 18.0 is not affected: there the rule was written as a plain `opacity: 0.9`. In saas-19.4 the same declaration already reads `rgba(mix(...), .9)`, so this only brings 19.0 in line with it.
Mexican electronic payment records now use the payment method configured on the selected bank journal instead of always defaulting to bank transfer. This helps ensure payment complements sent to the tax authority reflect the actual payment method, reducing manual corrections and compliance risk.
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
Flexible employee leave now blocks the full duration in Planning, including the first and last day. This prevents managers from seeing employees as partially available when they are actually on leave, improving schedule accuracy.
Original PR description
### Steps to reproduce: - Create a flexible employee - Create a leave from a Monday to a Friday for this employee - Navigate to Planning gantt view and check the week where the created leave exists - Notice only 3 days are greyed-out ### Cause: This is happening because in planning we normally exclude days that are not fully unavailable and since flexible doesn't have a specific start and end hours for the working days the start and end days for the flexible leaves are excluded from the unavailability. This is also happening in `hr_attendance` for example but in a different way where the start and end days of the leave will be shown half greyed-out ### Fix: For _unavailable_intervals_batch we make flexible leaves cover the whole days of the leave opw-6142094 Forward-Port-Of: odoo/enterprise#115485
The Ecuadorian ATS tax export now consolidates transactions from a company and its branches when they share the same RUC. This ensures the exported XML matches the consolidated tax return totals and avoids missing branch sales or tax data.
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
Users with attendance access can now open the Attendances Gantt even when it includes a colleague's approved time off on a flexible schedule. This prevents an access error while still only showing the unavailability information already needed for the schedule view.
Original PR description
### Problem Attendances Gantt raises an AccessError for a user who can see attendances but cannot read a colleague's time off, as soon as the window covers a day off of a flexible-schedule employee. ### Cause `_handle_flexible_leave_interval` reads the linked `hr.leave` (request unit, half-day period, hours) to size the grayed-out interval. It reads it as the current user, so the `hr.leave` record rule blocks any leave the user does not for the same reason; the `resource_calendar` override (commit 8e12ae9486) missed it. ### Fix Read `holiday_id` through sudo. Those values size the unavailability interval the Gantt shows to this user regardless, so sudo exposes no restricted data. `hr_holidays_gantt/models/hr_leave.py` sudoes the same fields for this reason; the `resource_calendar` override (commit 8e12ae9486) missed it.
Accounting predictions now use the most recent prior entries instead of older records. This helps improve the relevance of suggested accounting values and reduces the chance of predictions being based on outdated activity.
Original PR description
This commit: https://github.com/odoo/enterprise/pull/38830/changes#diff-6f6931855e0903ff0d3f2b39bd5703ceaef9bdf9a516862b83f38d2d5b21a232 change the order of the predictive queries, removing the sorting order by date. Based on the current docstring: https://github.com/odoo/enterprise/blob/66682012145e5116ebec0183102bc5e930c2c343/account_accountant/models/account_move.py#L676 this is not correct, as we expect to retrieve the previous 100 entries, rather than the oldest ones. Correcting the query order ensures that the most recent entries are considered for predictive purposes. opw-6558929 Forward-Port-Of: odoo/enterprise#131776 Forward-Port-Of: odoo/enterprise#131731
Fixed an issue where planning shifts created from overnight templates could incorrectly span one additional day. This helps schedules reflect the intended shift duration, avoiding confusion and incorrect staffing plans.
Original PR description
This is a backport of this [commit](https://github.com/odoo/enterprise/commit/c02b8eccd72758127cc7cdc51ab8ec461a2ec45e) Issue: ---------------------------------------- Creating a night shift from a…
This is a backport of this [commit](https://github.com/odoo/enterprise/commit/c02b8eccd72758127cc7cdc51ab8ec461a2ec45e) Issue: ---------------------------------------- Creating a night shift from a template produces a shift spanning over one additional day. Steps to reproduce: ---------------------------------------- - Create a planning shift template form 23h to 1h the next day (2h) - It must have a span over 2 working days - Create a shift and use this template - The shift spans over one more day Cause: ---------------------------------------- In `_calculate_start_end_dates()`, we call `plan_days()` with `start` having the hours specified. So in `plan_days()` when retrieving the worked days, the first day is ignored because the resource is not supposed to be working from 23h to 1h. Then we count two days and so the end date is offset by one day. Solution: ---------------------------------------- We should call `plan_days()` without the hour specified so we make sure the first day is included in the count. opw-6535758 Forward-Port-Of: odoo/enterprise#131140
AI live chat now only shows the option to ask for a human when a human operator is actually available. This prevents visitors from clicking a support option that can only lead to an unavailable-agent message, creating a clearer chat experience.
Original PR description
## Issue In `ai_livechat`, the "Ask a Human" button shown below the chat composer when an AI agent is the operator does not check whether a human operator is actually available on the livechat…
## Issue In `ai_livechat`, the "Ask a Human" button shown below the chat composer when an AI agent is the operator does not check whether a human operator is actually available on the livechat channel. With a channel configured for AI-only (no users assigned, rule with `chatbot_enabled_condition = only_if_no_operator` and an `ai_agent_id`), clicking the button always returns the notification *"There is no human agent available at the moment. Please try again later."* — a dead-end UX. ## Steps to reproduce 1. Create an `im_livechat.channel` with no users assigned. 2. Add a rule on that channel with: - `chatbot_enabled_condition = only_if_no_operator` - `ai_agent_id` set (any non-system AI agent). 3. Open the public livechat URL as a visitor. 4. Observe the *Ask a Human* button below the composer; clicking it produces the no-human-available warning. ## Cause `ChatWindow.showForwardOperatorButton` (in `ai_livechat/static/src/discuss/core/common/chat_window_patch.js`) only checks `thread.channel_type === 'livechat' && thread.ai_agent_id`. It does not consider whether the parent livechat channel currently has any available human operator. ## Fix - Expose a new `ai_livechat_has_human_operator` attribute on the `discuss.channel` Store payload via `Store.Attr`. The value is computed on the fly from the already-existing `im_livechat.channel.available_operator_ids` field — **no new stored field is introduced** (relevant for stable). The attribute is only emitted for livechat channels that have an AI agent set, so the extra cost is paid only by AI-backed sessions. - The web client hides the *Ask a Human* button when this flag is `false`. When the attribute is missing (older payloads or non-AI threads), behavior is unchanged. ## After the fix The *Ask a Human* button is hidden when the livechat channel has no available human operator, matching the "pure AI livechat" setup intent. Configurations with operators behind the AI continue to show the fallback button as before. ## Tests `ai_livechat/tests/test_im_livechat_channel.py::test_store_exposes_human_operator_availability_for_ai_livechat` covers the Store payload for an AI-backed livechat session without operators.