Skip to content
Permalink
Browse files

[FIX] (PERF) SO validation is very slow when there is a big amount of…

… lines.

This is due to multiple factors and one of them is the fact that moves are treated one by one.
This commit does not treat totally all moves at once but :
- moves are grouped by rules to prevent to read rule multiple time
- action_confirm is called on all moves at once
- added a method to run multiple moves at a time (even if at the end we call the rule on each move one by one)
- makes the run in a no recompute env to gain time

opw 1965242
  • Loading branch information...
xdu-odoo committed Apr 16, 2019
1 parent e7ce0b4 commit 5d78f3a62dc0319090be81fdae012bcd80a08c1e
@@ -3,17 +3,18 @@

from odoo import api, models


class SaleOrderLine(models.Model):
_inherit = "sale.order.line"

@api.multi
def _action_launch_procurement_rule(self):
res = super(SaleOrderLine, self)._action_launch_procurement_rule()
orders = list(set(x.order_id for x in self))
for order in orders:
reassign = order.picking_ids.filtered(lambda x: x.state=='confirmed' or (x.state in ['waiting', 'assigned'] and not x.printed))
if reassign:
reassign.do_unreserve()
reassign.action_assign()
with self.env.norecompute():
res = super(SaleOrderLine, self)._action_launch_procurement_rule()
orders = list(set(x.order_id for x in self))
for order in orders:
reassign = order.picking_ids.filtered(lambda x: x.state=='confirmed' or (x.state in ['waiting', 'assigned'] and not x.printed))
if reassign:
reassign.do_unreserve()
reassign.action_assign()
self.recompute()
return res
@@ -109,7 +109,7 @@ def write(self, values):
if lines:
lines.with_context(previous_product_uom_qty=previous_product_uom_qty)._action_launch_procurement_rule()
return res


@api.depends('order_id.state')
def _compute_invoice_status(self):
@@ -246,9 +246,9 @@ def _action_launch_procurement_rule(self):
depending on the sale order line product rule.
"""
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
errors = []
line_values = []
for line in self:
if line.state != 'sale' or not line.product_id.type in ('consu','product'):
if line.state != 'sale' or not line.product_id.type in ('consu', 'product'):
continue
qty = line._get_qty_procurement()
if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0:
@@ -283,12 +283,17 @@ def _action_launch_procurement_rule(self):
product_qty = line.product_uom._compute_quantity(product_qty, quant_uom, rounding_method='HALF-UP')
procurement_uom = quant_uom

try:
self.env['procurement.group'].run(line.product_id, product_qty, procurement_uom, line.order_id.partner_shipping_id.property_stock_customer, line.name, line.order_id.name, values)
except UserError as error:
errors.append(error.name)
if errors:
raise UserError('\n'.join(errors))
line_values.append({
'product_id': line.product_id,
'product_qty': product_qty,
'product_uom': procurement_uom,
'location_id': line.order_id.partner_shipping_id.property_stock_customer,
'name': line.name,
'origin': line.order_id.name,
'values': values
})

self.env['procurement.group'].run_multiple(line_values)
return True

@api.multi
@@ -81,6 +81,33 @@ def _run_move(self, product_id, product_qty, product_uom, location_id, name, ori
move._action_confirm()
return True

def _run_move_multiple(self, move_values):
if not self.location_src_id:
msg = _('No source location defined on procurement rule: %s!') % (self.name, )
raise UserError(msg)

move_ids = []
Move = self.env['stock.move'].sudo()
company = False
for move_value in move_values:
group_id = False
if self.group_propagation_option == 'propagate':
group_id = move_value['values'].get('group_id', False) and move_value['values']['group_id'].id
elif self.group_propagation_option == 'fixed':
group_id = self.group_id.id

move_params = self._get_stock_move_values(move_value['product_id'], move_value['product_qty'],
move_value['product_uom'], move_value['location_id'], move_value['name'],
move_value['origin'], move_value['values'], group_id)
if company != move_params.get('company_id', False):
company = move_params.get('company_id', False)
Move = Move.with_context(force_company=company)
move = Move.create(move_params)
move_ids.append(move.id)
# Since action_confirm launch following procurement_group we should activate it.
Move.browse(move_ids)._action_confirm()
return True

def _get_stock_move_values(self, product_id, product_qty, product_uom, location_id, name, origin, values, group_id):
''' Returns a dictionary of values that will be used to create a stock move from a procurement.
This function assumes that the given procurement has a rule (action == 'move') set on it.
@@ -173,6 +200,61 @@ class ProcurementGroup(models.Model):
('one', 'All at once')], string='Delivery Type', default='direct',
required=True)

@api.model
def run_multiple(self, line_values):
rule_cache = {}

def cache_rule_add(line_value, rule):
key = cache_get_rule_key(line_value)
rule_cache[key] = rule

def cache_get_rule_key(line_value):
warehouse = line_value['values'].get('warehouse_id', False)
warehouse_routes = warehouse.route_ids
routes = line_value['values'].get('route_ids', False)
product_routes = line_value['product_id'].route_ids | line_value['product_id'].categ_id.total_route_ids
return (
warehouse.id if warehouse else False,
line_value['location_id'].id,
tuple(routes.ids) if routes else False,
tuple(product_routes.ids) if product_routes else False,
tuple(warehouse_routes.ids) if warehouse_routes else False
)

def cache_get_rule(line_value):
key = cache_get_rule_key(line_value)
return rule_cache.get(key, False)

def get_rule(line_value):
rule = cache_get_rule(line_value)
if not rule:
rule = self._get_rule(line_value['product_id'], line_value['location_id'], line_value['values'])
cache_rule_add(line_value, rule)
if not rule:
raise UserError(_('No procurement rule found. Please verify the configuration of your routes'))
return rule

grouped_values = {}
company = self.env['res.company']._company_default_get('procurement.group')
for line_value in line_values:
line_value['values'].setdefault('company_id', company)
line_value['values'].setdefault('priority', '1')
line_value['values'].setdefault('date_planned', fields.Datetime.now())

rule = get_rule(line_value)
rule_fct = '_run_%s' % rule.action
grouped_values.setdefault(rule_fct, {'rule': rule, 'lines': []})
grouped_values[rule_fct]['lines'].append(line_value)

for fct, grouped_value in grouped_values.items():
multiple_rule_fct = fct + '_multiple'
if hasattr(grouped_value['rule'], multiple_rule_fct):
getattr(grouped_value['rule'], multiple_rule_fct)(grouped_value['lines'])
else:
for line in grouped_value['lines']:
getattr(grouped_value['rule'], fct)(**line)
return True

@api.model
def run(self, product_id, product_qty, product_uom, location_id, name, origin, values):
values.setdefault('company_id', self.env['res.company']._company_default_get('procurement.group'))
@@ -757,11 +757,20 @@ def _action_confirm(self, merge=True, merge_into=False):
to_assign[key] |= move

# create procurements for make to order moves
line_values = []
for move in move_create_proc:
values = move._prepare_procurement_values()
origin = (move.group_id and move.group_id.name or (move.rule_id and move.rule_id.name or move.origin or move.picking_id.name or "/"))
self.env['procurement.group'].run(move.product_id, move.product_uom_qty, move.product_uom, move.location_id, move.rule_id and move.rule_id.name or "/", origin,
values)
line_values.append({
'product_id': move.product_id,
'product_qty': move.product_uom_qty,
'product_uom': move.product_uom,
'location_id': move.location_id,
'name': move.rule_id and move.rule_id.name or "/",
'origin': origin,
'values': values
})
self.env['procurement.group'].run_multiple(line_values)

move_to_confirm.write({'state': 'confirmed'})
(move_waiting | move_create_proc).write({'state': 'waiting'})

0 comments on commit 5d78f3a

Please sign in to comment.
You can’t perform that action at this time.