Skip to content

Commit

Permalink
[REF] stock, mrp: remove constraint, add onchanges
Browse files Browse the repository at this point in the history
We added a constraint in [1] to improve the usability of introducing
tracked move lines in the system. However, this contraint proved to be
too costly (trigger multiple time during the validation process, never
called in batch as it is set on the model at the end of a o2m).

We replaces the constraint by checks on the validating process or by
(non-blocking) onchanges.

With these changes, validating the receipt of 3000 tracked move lines
goes from 17 minutes to around 4.

[1] 4824074

opw-803887
  • Loading branch information
sle-odoo committed Jan 18, 2018
1 parent 1bb6887 commit 6ef460b
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 54 deletions.
6 changes: 6 additions & 0 deletions addons/mrp/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ def _generate_consumed_move_line(self, qty_to_add, final_lot, lot=False):
move_lines = self.move_line_ids.filtered(lambda ml: ml.lot_id == lot and not ml.lot_produced_id)
else:
move_lines = self.move_line_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_produced_id)

# Sanity check: if the product is a serial number and `lot` is already present in the other
# consumed move lines, raise.
if lot and self.product_id.tracking == 'serial' and lot in self.move_line_ids.filtered(lambda ml: ml.qty_done).mapped('lot_id'):
raise UserError(_('You cannot consume the same serial number twice. Please correct the serial numbers encoded.'))

for ml in move_lines:
rounding = ml.product_uom_id.rounding
if float_compare(qty_to_add, 0, precision_rounding=rounding) <= 0:
Expand Down
32 changes: 16 additions & 16 deletions addons/mrp/wizard/mrp_product_produce.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ def check_finished_move_lots(self):
raise UserError(_('You need to provide a lot for the finished product'))
existing_move_line = produce_move.move_line_ids.filtered(lambda x: x.lot_id == self.lot_id)
if existing_move_line:
if self.product_id.tracking == 'serial':
raise UserError(_('You cannot produce the same serial number twice.'))
existing_move_line.product_uom_qty += self.product_qty
existing_move_line.qty_done += self.product_qty
else:
Expand Down Expand Up @@ -181,27 +183,25 @@ class MrpProductProduceLine(models.TransientModel):

@api.onchange('lot_id')
def _onchange_lot_id(self):
""" When the user is encoding a produce line for a tracked product, we apply some logic to
help him. This onchange will automatically switch `qty_done` to 1.0.
"""
res = {}
if self.product_id.tracking == 'serial':
self.qty_done = 1
return res

@api.constrains('lot_id')
def _check_lot_id(self):
for ml in self:
if ml.product_id.tracking == 'serial':
produce_lines_to_check = ml.product_produce_id.produce_line_ids.filtered(lambda l: l.product_id == ml.product_id and l.lot_id)
message = produce_lines_to_check._check_for_duplicated_serial_numbers()
if message:
raise ValidationError(message)

def _check_for_duplicated_serial_numbers(self):
if self.mapped('lot_id'):
lots_map = [(ml.product_id.id, ml.lot_id.name) for ml in self]
recorded_serials_counter = Counter(lots_map)
for (product_id, lot_id), occurrences in recorded_serials_counter.items():
if occurrences > 1 and lot_id is not False:
return _('You cannot consume the same serial number twice. Please correct the serial numbers encoded.')
@api.onchange('qty_done')
def _onchange_qty_done(self):
""" When the user is encoding a produce line for a tracked product, we apply some logic to
help him. This onchange will warn him if he set `qty_done` to a non-supported value.
"""
res = {}
if self.product_id.tracking == 'serial':
if float_compare(self.qty_done, 1.0, precision_rounding=self.move_id.product_id.uom_id.rounding) != 0:
message = _('You can only process 1.0 %s for products with unique serial number.') % self.product_id.uom_id.name
res['warning'] = {'title': _('Warning'), 'message': message}
return res

@api.onchange('product_id')
def _onchange_product_id(self):
Expand Down
70 changes: 32 additions & 38 deletions addons/stock/models/stock_move_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,36 +109,49 @@ def onchange_product_id(self):

@api.onchange('lot_name', 'lot_id')
def onchange_serial_number(self):
""" When the user is encoding a move line for a tracked product, we apply some logic to
help him. This includes:
- automatically switch `qty_done` to 1.0
- warn if he has already encoded `lot_name` in another move line
"""
res = {}
if self.product_id.tracking == 'serial':
self.qty_done = 1
move_lines_to_check = self._get_similar_move_lines() - self
message = move_lines_to_check._check_for_duplicated_serial_numbers()
if not self.qty_done:
self.qty_done = 1

message = None
if self.lot_name or self.lot_id:
move_lines_to_check = self._get_similar_move_lines() - self
if self.lot_name:
counter = Counter(move_lines_to_check.mapped('lot_name'))
if counter.get(self.lot_name) and counter[self.lot_name] > 1:
message = _('You cannot use the same serial number twice. Please correct the serial numbers encoded.')
elif self.lot_id:
counter = Counter(move_lines_to_check.mapped('lot_id.id'))
if counter.get(self.lot_id.id) and counter[self.lot_id.id] > 1:
message = _('You cannot use the same serial number twice. Please correct the serial numbers encoded.')

if message:
res['warning'] = {'title': _('Warning'), 'message': message}
return res

@api.onchange('qty_done')
def _onchange_qty_done(self):
""" When the user is encoding a move line for a tracked product, we apply some logic to
help him. This onchange will warn him if he set `qty_done` to a non-supported value.
"""
res = {}
if self.product_id.tracking == 'serial':
if float_compare(self.qty_done, 1.0, precision_rounding=self.move_id.product_id.uom_id.rounding) != 0:
message = _('You can only process 1.0 %s for products with unique serial number.') % self.product_id.uom_id.name
res['warning'] = {'title': _('Warning'), 'message': message}
return res

@api.constrains('qty_done')
def _check_positive_qty_done(self):
if any([ml.qty_done < 0 for ml in self]):
raise ValidationError(_('You can not enter negative quantities!'))

@api.constrains('lot_id', 'lot_name', 'qty_done')
def _check_unique_serial_number(self):
for ml in self.filtered(lambda ml: ml.move_id.product_id.tracking == 'serial' and (ml.lot_id or ml.lot_name)):
move_lines_to_check = ml._get_similar_move_lines()
message = move_lines_to_check._check_for_duplicated_serial_numbers()
if message:
raise ValidationError(message)
if float_compare(ml.qty_done, 1.0, precision_rounding=ml.move_id.product_id.uom_id.rounding) == 1:
raise UserError(_(
'You can only process 1.0 %s for products with unique serial number.') % ml.product_id.uom_id.name)
if ml.lot_name:
already_exist = self.env['stock.production.lot'].search(
[('name', '=', ml.lot_name), ('product_id', '=', ml.product_id.id)])
if already_exist:
return _('You have already assigned this serial number to this product. Please correct the serial numbers encoded.')

def _get_similar_move_lines(self):
self.ensure_one()
lines = self.env['stock.move.line']
Expand All @@ -147,25 +160,6 @@ def _get_similar_move_lines(self):
lines |= picking_id.move_line_ids.filtered(lambda ml: ml.product_id == self.product_id and (ml.lot_id or ml.lot_name))
return lines

def _check_for_duplicated_serial_numbers(self):
""" This method is used in _check_unique_serial_number and in onchange_serial_number to check that a same serial number is not used twice amongst the recordset passed.
:return: an error message directed to the user if needed else False
"""
if self.mapped('lot_id'):
lots_map = [(ml.product_id.id, ml.lot_id.name) for ml in self]
recorded_serials_counter = Counter(lots_map)
for (product_id, lot_id), occurrences in recorded_serials_counter.items():
if occurrences > 1 and lot_id is not False:
return _('You cannot use the same serial number twice. Please correct the serial numbers encoded.')
elif self.mapped('lot_name'):
lots_map = [(ml.product_id.id, ml.lot_name) for ml in self]
recorded_serials_counter = Counter(lots_map)
for (product_id, lot_id), occurrences in recorded_serials_counter.items():
if occurrences > 1 and lot_id is not False:
return _('You cannot use the same serial number twice. Please correct the serial numbers encoded.')
return False

@api.model
def create(self, vals):
vals['ordered_qty'] = vals.get('product_uom_qty')
Expand Down

0 comments on commit 6ef460b

Please sign in to comment.