Friday, September 18, 2026
3 changes · 19.0
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
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