diff --git a/addons/mrp/data/mrp_demo.xml b/addons/mrp/data/mrp_demo.xml
index b8c2014d6e1..84fe419296f 100644
--- a/addons/mrp/data/mrp_demo.xml
+++ b/addons/mrp/data/mrp_demo.xml
@@ -706,7 +706,7 @@
diff --git a/addons/mrp/models/mrp_abstract_workorder.py b/addons/mrp/models/mrp_abstract_workorder.py
index 494e5b6be13..312889fdd05 100644
--- a/addons/mrp/models/mrp_abstract_workorder.py
+++ b/addons/mrp/models/mrp_abstract_workorder.py
@@ -26,6 +26,10 @@ class MrpAbstractWorkorder(models.AbstractModel):
)
use_create_components_lots = fields.Boolean(related="production_id.picking_type_id.use_create_components_lots")
+ def _workorder_line_ids(self):
+ self.ensure_one()
+ return self.raw_workorder_line_ids | self.finished_workorder_line_ids
+
@api.onchange('qty_producing')
def _onchange_qty_producing(self):
""" Modify the qty currently producing will modify the existing
@@ -35,10 +39,13 @@ class MrpAbstractWorkorder(models.AbstractModel):
if self.qty_producing <= 0:
raise UserError(_('You have to produce at least one %s.') % self.product_uom_id.name)
line_values = self._update_workorder_lines()
- for vals in line_values['to_create']:
- self.workorder_line_ids |= self.workorder_line_ids.new(vals)
- if line_values['to_delete']:
- self.workorder_line_ids -= line_values['to_delete']
+ for values in line_values['to_create']:
+ self.env[self._workorder_line_ids()._name].new(values)
+ for line in line_values['to_delete']:
+ if line in self.raw_workorder_line_ids:
+ self.raw_workorder_line_ids -= line
+ else:
+ self.finished_workorder_line_ids -= line
for line, vals in line_values['to_update'].items():
line.update(vals)
@@ -49,17 +56,19 @@ class MrpAbstractWorkorder(models.AbstractModel):
used in onchange and request that write on db (e.g. workorder creation).
"""
line_values = {'to_create': [], 'to_delete': [], 'to_update': {}}
- for move_raw in self.move_raw_ids.filtered(lambda move: move.state not in ('done', 'cancel')):
- move_workorder_lines = self.workorder_line_ids.filtered(lambda w: w.move_id == move_raw)
+ move_finished_ids = self.move_finished_ids.filtered(lambda move: move.product_id != self.product_id and move.state not in ('done', 'cancel'))
+ move_raw_ids = self.move_raw_ids.filtered(lambda move: move.state not in ('done', 'cancel'))
+ for move in move_raw_ids | move_finished_ids:
+ move_workorder_lines = self._workorder_line_ids().filtered(lambda w: w.move_id == move)
# Compute the new quantity for the current component
- rounding = move_raw.product_uom.rounding
- if move_raw.product_id.tracking == 'serial':
- uom = move_raw.product_id.uom_id
+ rounding = move.product_uom.rounding
+ if move.product_id.tracking == 'serial':
+ uom = move.product_id.uom_id
else:
- uom = move_raw.product_uom
- new_qty = move_raw.product_uom._compute_quantity(
- self.qty_producing * move_raw.unit_factor,
+ uom = move.product_uom
+ new_qty = move.product_uom._compute_quantity(
+ self.qty_producing * move.unit_factor,
uom,
round=False
)
@@ -141,8 +150,8 @@ class MrpAbstractWorkorder(models.AbstractModel):
# if there are still qty_todo, create new wo lines
if float_compare(qty_todo, 0.0, precision_rounding=rounding) > 0:
- for vals in self._generate_lines_values(move_raw, qty_todo):
- line_values['to_create'].append(vals)
+ for values in self._generate_lines_values(move, qty_todo):
+ line_values['to_create'].append(values)
return line_values
@api.model
@@ -155,14 +164,21 @@ class MrpAbstractWorkorder(models.AbstractModel):
"""
lines = []
is_tracked = move.product_id.tracking != 'none'
+ if move in self.move_raw_ids:
+ # Get the inverse_name (many2one on line) of raw_workorder_line_ids
+ initial_line_values = {self.raw_workorder_line_ids._get_raw_workorder_inverse_name(): self.id}
+ else:
+ # Get the inverse_name (many2one on line) of finished_workorder_line_ids
+ initial_line_values = {self.finished_workorder_line_ids._get_finished_workoder_inverse_name(): self.id}
for move_line in move.move_line_ids:
+ line = dict(initial_line_values)
if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) <= 0:
break
# move line already 'used' in workorder (from its lot for instance)
if move_line.lot_produced_id or float_compare(move_line.product_uom_qty, move_line.qty_done, precision_rounding=move.product_uom.rounding) <= 0:
continue
# search wo line on which the lot is not fully consumed or other reserved lot
- linked_wo_line = self.workorder_line_ids.filtered(
+ linked_wo_line = self._workorder_line_ids().filtered(
lambda line: line.product_id == move_line.product_id and
line.lot_id == move_line.lot_id
)
@@ -173,38 +189,39 @@ class MrpAbstractWorkorder(models.AbstractModel):
continue
else:
to_consume_in_line = min(qty_to_consume, move_line.product_uom_qty - move_line.qty_done)
- line = {
+ line.update({
'move_id': move.id,
'product_id': move.product_id.id,
'product_uom_id': is_tracked and move.product_id.uom_id.id or move.product_uom.id,
'qty_to_consume': to_consume_in_line,
'qty_reserved': to_consume_in_line,
'lot_id': move_line.lot_id.id,
- 'qty_done': to_consume_in_line
- }
+ 'qty_done': to_consume_in_line,
+ })
lines.append(line)
qty_to_consume -= to_consume_in_line
# The move has not reserved the whole quantity so we create new wo lines
if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) > 0:
+ line = dict(initial_line_values)
if move.product_id.tracking == 'serial':
while float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) > 0:
- line = {
+ line.update({
'move_id': move.id,
'product_id': move.product_id.id,
'product_uom_id': move.product_id.uom_id.id,
'qty_to_consume': 1,
'qty_done': 1,
- }
+ })
lines.append(line)
qty_to_consume -= 1
else:
- line = {
+ line.update({
'move_id': move.id,
'product_id': move.product_id.id,
'product_uom_id': move.product_uom.id,
'qty_to_consume': qty_to_consume,
- 'qty_done': qty_to_consume
- }
+ 'qty_done': qty_to_consume,
+ })
lines.append(line)
return lines
@@ -245,52 +262,26 @@ class MrpAbstractWorkorder(models.AbstractModel):
float_round(self.qty_producing, precision_rounding=rounding)
)
- by_product_moves = self.production_id.move_finished_ids.filtered(lambda m: m.product_id != self.product_id and m.state not in ('done', 'cancel'))
- for by_product_move in by_product_moves:
- rounding = by_product_move.product_uom.rounding
- quantity = float_round(self.qty_producing * by_product_move.unit_factor, precision_rounding=rounding)
- values = {
- 'move_id': by_product_move.id,
- 'product_id': by_product_move.product_id.id,
- 'production_id': self.production_id.id,
- 'product_uom_id': by_product_move.product_uom.id,
- 'location_id': by_product_move.location_id.id,
- 'location_dest_id': by_product_move.location_dest_id.id,
- }
- if by_product_move.product_id.tracking == 'lot':
- values.update({
- 'product_uom_qty': quantity,
- 'qty_done': quantity,
- })
- self.env['stock.move.line'].create(values)
- else:
- values.update({
- 'product_uom_qty': 1.0,
- 'qty_done': 1.0,
- })
- for i in range(0, int(quantity)):
- self.env['stock.move.line'].create(values)
-
- def _update_raw_moves(self):
+ def _update_moves(self):
""" Once the production is done. Modify the workorder lines into
stock move line with the registered lot and quantity done.
"""
# Before writting produce quantities, we ensure they respect the bom strictness
self._strict_consumption_check()
vals_list = []
- workorder_lines_to_process = self.workorder_line_ids.filtered(lambda line: line.qty_done > 0)
+ workorder_lines_to_process = self._workorder_line_ids().filtered(lambda line: line.product_id != self.product_id and line.qty_done > 0)
for line in workorder_lines_to_process:
line._update_move_lines()
if float_compare(line.qty_done, 0, precision_rounding=line.product_uom_id.rounding) > 0:
vals_list += line._create_extra_move_lines()
- self.workorder_line_ids.unlink()
+ self._workorder_line_ids().filtered(lambda line: line.product_id != self.product_id).unlink()
self.env['stock.move.line'].create(vals_list)
def _strict_consumption_check(self):
if self.consumption == 'strict':
for move in self.move_raw_ids:
- lines = self.workorder_line_ids.filtered(lambda l: l.move_id == move)
+ lines = self._workorder_line_ids().filtered(lambda l: l.move_id == move)
qty_done = sum(lines.mapped('qty_done'))
qty_to_consume = sum(lines.mapped('qty_to_consume'))
rounding = self.product_uom_id.rounding
@@ -444,6 +435,14 @@ class MrpAbstractWorkorderLine(models.AbstractModel):
return self.move_id.move_line_ids.filtered(lambda ml:
ml.lot_id == self.lot_id and ml.product_id == self.product_id)
+ @api.model
+ def _get_raw_workorder_inverse_name(self):
+ raise NotImplementedError('Method _get_raw_workorder_inverse_name() undefined on %s' % self)
+
+ @api.model
+ def _get_finished_workoder_inverse_name(self):
+ raise NotImplementedError('Method _get_finished_workoder_inverse_name() undefined on %s' % self)
+
# To be implemented in specific model
def _get_final_lot(self):
raise NotImplementedError('Method _get_final_lot() undefined on %s' % self)
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index 04c158a63d8..30afe76b3c0 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -463,15 +463,18 @@ class MrpProduction(models.Model):
self.is_locked = not self.is_locked
return True
- def _generate_finished_moves(self):
- move = self.env['stock.move'].create({
+ def _get_finished_move_value(self, product_id, product_uom_qty, product_uom, operation_id=False, subproduct_id=False):
+ return {
+ 'product_id': product_id,
+ 'product_uom_qty': product_uom_qty,
+ 'product_uom': product_uom,
+ 'operation_id': operation_id,
+ 'subproduct_id': subproduct_id,
+ 'unit_factor': product_uom_qty / self.product_qty,
'name': self.name,
'date': self.date_planned_start,
'date_expected': self.date_planned_start,
'picking_type_id': self.picking_type_id.id,
- 'product_id': self.product_id.id,
- 'product_uom': self.product_uom_id.id,
- 'product_uom_qty': self.product_qty,
'location_id': self.product_id.property_stock_production.id,
'location_dest_id': self.location_dest_id.id,
'company_id': self.company_id.id,
@@ -481,11 +484,19 @@ class MrpProduction(models.Model):
'group_id': self.procurement_group_id.id,
'propagate': self.propagate,
'move_dest_ids': [(4, x.id) for x in self.move_dest_ids],
- })
- for production in self.filtered(lambda production: production.bom_id):
- for sub_product in production.bom_id.sub_products:
- production._create_byproduct_move(sub_product)
- return move
+ }
+
+ def _generate_finished_moves(self):
+ moves_values = [self._get_finished_move_value(self.product_id.id, self.product_qty, self.product_uom_id.id)]
+ for sub_product in self.bom_id.sub_products:
+ product_uom_factor = self.product_uom_id._compute_quantity(self.product_qty, self.bom_id.product_uom_id)
+ qty = sub_product.product_qty * (product_uom_factor / self.bom_id.product_qty)
+ move_values = self._get_finished_move_value(sub_product.product_id.id,
+ qty, sub_product.product_uom_id.id, sub_product.operation_id.id,
+ sub_product.id)
+ moves_values.append(move_values)
+ moves = self.env['stock.move'].create(moves_values)
+ return moves
def _get_moves_raw_values(self):
moves = []
@@ -556,33 +567,6 @@ class MrpProduction(models.Model):
move = self.env['stock.move'].create(move_values)
return move, 0, quantity
- def _create_byproduct_move(self, sub_product):
- Move = self.env['stock.move']
- for production in self:
- source = production.product_id.property_stock_production.id
- product_uom_factor = production.product_uom_id._compute_quantity(production.product_qty - production.qty_produced, production.bom_id.product_uom_id)
- qty1 = sub_product.product_qty
- qty1 *= product_uom_factor / production.bom_id.product_qty
- data = {
- 'name': 'PROD:%s' % production.name,
- 'date': production.date_planned_start,
- 'product_id': sub_product.product_id.id,
- 'product_uom_qty': qty1,
- 'product_uom': sub_product.product_uom_id.id,
- 'location_id': source,
- 'location_dest_id': production.location_dest_id.id,
- 'operation_id': sub_product.operation_id.id,
- 'production_id': production.id,
- 'warehouse_id': production.location_dest_id.get_warehouse().id,
- 'origin': production.name,
- 'unit_factor': qty1 / (production.product_qty - production.qty_produced),
- 'propagate': self.propagate,
- 'group_id': self.move_dest_ids and self.move_dest_ids.mapped('group_id')[0].id or self.procurement_group_id.id,
- 'subproduct_id': sub_product.id
- }
- move = Move.create(data)
- move._action_confirm()
-
def _get_ready_to_produce_state(self):
""" returns 'assigned' if enough components are reserved in order to complete
the first operation in the routing. If not returns 'waiting'
@@ -755,11 +739,12 @@ class MrpProduction(models.Model):
# assign moves; last operation receive all unassigned moves (which case ?)
moves_raw = self.move_raw_ids.filtered(lambda move: move.operation_id == operation)
+ moves_finished = self.move_finished_ids.filtered(lambda move: move.operation_id == operation)
if len(workorders) == len(bom.routing_id.operation_ids):
moves_raw |= self.move_raw_ids.filtered(lambda move: not move.operation_id)
- moves_finished = self.move_finished_ids.filtered(lambda move: move.operation_id == operation) #TODO: code does nothing, unless maybe by_products?
+ moves_finished |= self.move_finished_ids.filtered(lambda move: move.product_id != self.product_id and not move.operation_id)
moves_raw.mapped('move_line_ids').write({'workorder_id': workorder.id})
- (moves_finished + moves_raw).write({'workorder_id': workorder.id})
+ (moves_finished | moves_raw).write({'workorder_id': workorder.id})
workorder._generate_wo_lines()
return workorders
diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py
index 3bdb4bd76f3..40661a7ec12 100644
--- a/addons/mrp/models/mrp_workorder.py
+++ b/addons/mrp/models/mrp_workorder.py
@@ -81,7 +81,11 @@ class MrpWorkorder(models.Model):
worksheet = fields.Binary(
'Worksheet', related='operation_id.worksheet', readonly=True)
move_raw_ids = fields.One2many(
- 'stock.move', 'workorder_id', 'Moves')
+ 'stock.move', 'workorder_id', 'Raw Moves',
+ domain=[('raw_material_production_id', '!=', False), ('production_id', '=', False)])
+ move_finished_ids = fields.One2many(
+ 'stock.move', 'workorder_id', 'Finished Moves',
+ domain=[('raw_material_production_id', '=', False), ('production_id', '!=', False)])
move_line_ids = fields.One2many(
'stock.move.line', 'workorder_id', 'Moves to Track',
help="Inventory moves for which you must scan a lot number at this work order")
@@ -104,18 +108,10 @@ class MrpWorkorder(models.Model):
capacity = fields.Float(
'Capacity', default=1.0,
help="Number of pieces that can be produced in parallel.")
- workorder_line_ids = fields.One2many(
- 'mrp.workorder.line',
- 'workorder_id',
- string='Workorder lines',
- domain=[('is_finished', '=', False)]
- )
- finished_workorder_line_ids = fields.One2many(
- 'mrp.workorder.line',
- 'workorder_id',
- string='Final Lot lines',
- domain=[('is_finished', '=', True)]
- )
+ raw_workorder_line_ids = fields.One2many('mrp.workorder.line',
+ 'raw_workorder_id', string='Components')
+ finished_workorder_line_ids = fields.One2many('mrp.workorder.line',
+ 'finished_workorder_id', string='By-products')
allowed_lots_domain = fields.One2many(comodel_name='stock.production.lot', compute="_compute_allowed_lots_domain")
@api.onchange('final_lot_id')
@@ -126,7 +122,7 @@ class MrpWorkorder(models.Model):
('next_work_order_id', '=', self.id)
])
if previous_wo:
- line = previous_wo.finished_workorder_line_ids.filtered(lambda line: line.lot_id == self.final_lot_id)
+ line = previous_wo.finished_workorder_line_ids.filtered(lambda line: line.product_id == self.product_id and line.lot_id == self.final_lot_id)
if line:
self.qty_producing = line.qty_done
@@ -138,7 +134,7 @@ class MrpWorkorder(models.Model):
continue
rounding = production.product_uom_id.rounding
- finished_workorder_lines = production.workorder_ids.mapped('finished_workorder_line_ids')
+ finished_workorder_lines = production.workorder_ids.mapped('finished_workorder_line_ids').filtered(lambda wl: wl.product_id == production.product_id)
qties_done_per_lot = defaultdict(list)
for finished_workorder_line in finished_workorder_lines:
qties_done_per_lot[finished_workorder_line.lot_id.id].append(finished_workorder_line.qty_done)
@@ -155,7 +151,7 @@ class MrpWorkorder(models.Model):
workorders = production.workorder_ids.filtered(lambda wo: wo.state not in ('done', 'cancel'))
for workorder in workorders:
if workorder.product_tracking == 'serial':
- workorder.allowed_lots_domain = allowed_lot_ids - workorder.finished_workorder_line_ids.mapped('lot_id')
+ workorder.allowed_lots_domain = allowed_lot_ids - workorder.finished_workorder_line_ids.filtered(lambda wl: wl.product_id == production.product_id).mapped('lot_id')
else:
workorder.allowed_lots_domain = allowed_lot_ids
@@ -223,17 +219,17 @@ class MrpWorkorder(models.Model):
def _generate_wo_lines(self):
""" Generate workorder line """
self.ensure_one()
- raw_moves = self.move_raw_ids.filtered(
+ moves = (self.move_raw_ids | self.move_finished_ids).filtered(
lambda move: move.state not in ('done', 'cancel')
)
- for move in raw_moves:
+ for move in moves:
qty_to_consume = move.product_uom._compute_quantity(
self.qty_producing * move.unit_factor,
move.product_id.uom_id,
round=False
)
line_values = self._generate_lines_values(move, qty_to_consume)
- self.workorder_line_ids |= self.env['mrp.workorder.line'].create(line_values)
+ self.env['mrp.workorder.line'].create(line_values)
def _apply_update_workorder_lines(self):
""" update existing line on the workorder. It could be trigger manually
@@ -241,13 +237,18 @@ class MrpWorkorder(models.Model):
"""
self.ensure_one()
line_values = self._update_workorder_lines()
- self.workorder_line_ids |= self.workorder_line_ids.create(line_values['to_create'])
+ self.env['mrp.workorder.line'].create(line_values['to_create'])
if line_values['to_delete']:
line_values['to_delete'].unlink()
for line, vals in line_values['to_update'].items():
line.write(vals)
def _refresh_wo_lines(self):
+ """ Modify exisiting workorder line in order to match the reservation on
+ stock move line. The strategy is to remove the line that were not
+ processed yet then call _generate_lines_values that recreate workorder
+ line depending the reservation.
+ """
for workorder in self:
raw_moves = workorder.move_raw_ids.filtered(
lambda move: move.state not in ('done', 'cancel')
@@ -256,7 +257,7 @@ class MrpWorkorder(models.Model):
for move in raw_moves:
rounding = move.product_uom.rounding
qty_already_consumed = 0.0
- workorder_lines = workorder.workorder_line_ids.filtered(lambda w: w.move_id == move)
+ workorder_lines = workorder.raw_workorder_line_ids.filtered(lambda w: w.move_id == move)
for wl in workorder_lines:
if not wl.qty_done:
wl_to_unlink |= wl
@@ -271,7 +272,7 @@ class MrpWorkorder(models.Model):
wl_to_unlink.unlink()
if float_compare(qty_to_consume, qty_already_consumed, precision_rounding=rounding) > 0:
line_values = workorder._generate_lines_values(move, qty_to_consume - qty_already_consumed)
- workorder.workorder_line_ids |= self.env['mrp.workorder.line'].create(line_values)
+ self.env['mrp.workorder.line'].create(line_values)
def _defaults_from_finished_workorder_line(self, reference_lot_lines):
for r_line in reference_lot_lines:
@@ -294,17 +295,6 @@ class MrpWorkorder(models.Model):
return True
return False
- def _get_byproduct_move_line(self, by_product_move, quantity):
- return {
- 'move_id': by_product_move.id,
- 'product_id': by_product_move.product_id.id,
- 'product_uom_qty': quantity,
- 'product_uom_id': by_product_move.product_uom.id,
- 'qty_done': quantity,
- 'location_id': by_product_move.location_id.id,
- 'location_dest_id': by_product_move.location_dest_id.id,
- }
-
@api.multi
def record_production(self):
if not self:
@@ -319,7 +309,7 @@ class MrpWorkorder(models.Model):
self._update_finished_move()
# Transfer quantities from temporary to final move line or make them final
- self._update_raw_moves()
+ self._update_moves()
# Transfert lot and quantity produced to a finished workorder line
if self.product_tracking != 'none' and self.final_lot_id:
@@ -396,8 +386,7 @@ class MrpWorkorder(models.Model):
# Update workorder line that regiter final lot created
if not current_lot_lines:
current_lot_lines = self.env['mrp.workorder.line'].create({
- 'workorder_id': self.id,
- 'is_finished': True,
+ 'finished_workorder_id': self.id,
'product_id': self.product_id.id,
'lot_id': self.final_lot_id.id,
'qty_done': self.qty_producing,
@@ -548,11 +537,19 @@ class MrpWorkorderLine(models.Model):
_inherit = ["mrp.abstract.workorder.line"]
_description = "Workorder move line"
- workorder_id = fields.Many2one('mrp.workorder', 'Workorder')
- is_finished = fields.Boolean('Finished Lot Line', default=False)
+ raw_workorder_id = fields.Many2one('mrp.workorder', 'Component for Workorder')
+ finished_workorder_id = fields.Many2one('mrp.workorder', 'Finished Product for Workorder')
- def _get_final_lot(self):
- return self.workorder_id.final_lot_id
+ @api.model
+ def _get_raw_workorder_inverse_name(self):
+ return 'raw_workorder_id'
+
+ @api.model
+ def _get_finished_workoder_inverse_name(self):
+ return 'finished_workorder_id'
+
+ def _get_final_lots(self):
+ return (self.raw_workorder_id or self.finished_workorder_id).final_lot_id
def _get_production(self):
- return self.workorder_id.production_id
+ return (self.raw_workorder_id or self.finished_workorder_id).production_id
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index b17ef7671a6..436b1bb3f92 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -73,7 +73,7 @@ class StockMove(models.Model):
subproduct_id = fields.Many2one(
'mrp.subproduct', 'Subproduct',
help="Subproduct line that generated the move in a manufacturing order")
- unit_factor = fields.Float('Unit Factor')
+ unit_factor = fields.Float('Unit Factor', default=1)
is_done = fields.Boolean(
'Done', compute='_compute_is_done',
store=True,
diff --git a/addons/mrp/tests/common.py b/addons/mrp/tests/common.py
index c63cec46e04..55e817e68f6 100644
--- a/addons/mrp/tests/common.py
+++ b/addons/mrp/tests/common.py
@@ -54,6 +54,7 @@ class TestMrpCommon(common2.TestStockCommon):
user_group_stock_user = cls.env.ref('stock.group_stock_user')
user_group_mrp_user = cls.env.ref('mrp.group_mrp_user')
user_group_mrp_manager = cls.env.ref('mrp.group_mrp_manager')
+ user_group_mrp_byproducts = cls.env.ref('mrp.group_mrp_byproducts')
# Update demo products
(cls.product_2 | cls.product_3 | cls.product_4 | cls.product_5 | cls.product_6 | cls.product_7 | cls.product_8).write({
@@ -67,13 +68,21 @@ class TestMrpCommon(common2.TestStockCommon):
'login': 'hilda',
'email': 'h.h@example.com',
'notification_type': 'inbox',
- 'groups_id': [(6, 0, [user_group_mrp_user.id, user_group_stock_user.id])]})
+ 'groups_id': [(6, 0, [
+ user_group_mrp_user.id,
+ user_group_stock_user.id,
+ user_group_mrp_byproducts.id
+ ])]})
cls.user_mrp_manager = Users.create({
'name': 'Gary Youngwomen',
'login': 'gary',
'email': 'g.g@example.com',
'notification_type': 'inbox',
- 'groups_id': [(6, 0, [user_group_mrp_manager.id, user_group_stock_user.id])]})
+ 'groups_id': [(6, 0, [
+ user_group_mrp_manager.id,
+ user_group_stock_user.id,
+ user_group_mrp_byproducts.id
+ ])]})
cls.workcenter_1 = cls.env['mrp.workcenter'].create({
'name': 'Nuclear Workcenter',
diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py
index f8aa7f29109..ddde7e6a064 100644
--- a/addons/mrp/tests/test_order.py
+++ b/addons/mrp/tests/test_order.py
@@ -300,21 +300,21 @@ class TestMrpOrder(TestMrpCommon):
'active_ids': [mo.id],
}))
# change the quantity done in one line
- produce_form.workorder_line_ids._records[0]['qty_done'] = 1
+ produce_form.raw_workorder_line_ids._records[0]['qty_done'] = 1
# change the quantity producing
produce_form.qty_producing = 3
# check than all quantities are update correctly
- line1 = produce_form.workorder_line_ids._records[0]
- line2 = produce_form.workorder_line_ids._records[1]
+ line1 = produce_form.raw_workorder_line_ids._records[0]
+ line2 = produce_form.raw_workorder_line_ids._records[1]
self.assertEqual(line1['qty_to_consume'], 3, "Wrong quantity to consume")
self.assertEqual(line1['qty_done'], 3, "Wrong quantity done")
self.assertEqual(line2['qty_to_consume'], 12, "Wrong quantity to consume")
self.assertEqual(line2['qty_done'], 12, "Wrong quantity done")
product_produce = produce_form.save()
- self.assertEqual(len(product_produce.workorder_line_ids), 2, 'You should have produce lines even the consumed products are not tracked.')
+ self.assertEqual(len(product_produce.raw_workorder_line_ids), 2, 'You should have produce lines even the consumed products are not tracked.')
product_produce.do_produce()
def test_product_produce_2(self):
@@ -345,12 +345,12 @@ class TestMrpOrder(TestMrpCommon):
'active_ids': [mo.id],
}))
- self.assertEqual(len(produce_form.workorder_line_ids), 3, 'You should have 3 produce lines. One for each serial to consume and for the untracked product.')
+ self.assertEqual(len(produce_form.raw_workorder_line_ids), 3, 'You should have 3 produce lines. One for each serial to consume and for the untracked product.')
produce_form.qty_producing = 1
# get the proposed lot
consumed_lots = self.env['stock.production.lot']
- for workorder_line in produce_form.workorder_line_ids._records:
+ for workorder_line in produce_form.raw_workorder_line_ids._records:
if workorder_line['product_id'] == p1.id:
consumed_lots |= self.env['stock.production.lot'].browse(workorder_line['lot_id'])
consumed_lots.ensure_one()
@@ -365,8 +365,8 @@ class TestMrpOrder(TestMrpCommon):
'active_ids': [mo.id],
}))
product_produce = produce_form.save()
- self.assertEqual(len(product_produce.workorder_line_ids), 2, 'You should have 2 produce lines left.')
- for line in product_produce.workorder_line_ids.filtered(lambda x: x.lot_id):
+ self.assertEqual(len(product_produce.raw_workorder_line_ids), 2, 'You should have 2 produce lines left.')
+ for line in product_produce.raw_workorder_line_ids.filtered(lambda x: x.lot_id):
self.assertEqual(line.lot_id, remaining_lot, 'Wrong lot proposed.')
def test_product_produce_3(self):
@@ -408,15 +408,15 @@ class TestMrpOrder(TestMrpCommon):
'active_ids': [mo.id],
}))
produce_form.qty_producing = 1.0
- for i in range(len(produce_form.workorder_line_ids)):
- with produce_form.workorder_line_ids.edit(i) as line:
+ for i in range(len(produce_form.raw_workorder_line_ids)):
+ with produce_form.raw_workorder_line_ids.edit(i) as line:
line.qty_done += 1
product_produce = produce_form.save()
product_produce.final_lot_id = final_product_lot.id
# product 1 lot 1 shelf1
# product 1 lot 1 shelf2
# product 1 lot 2
- self.assertEqual(len(product_produce.workorder_line_ids), 4, 'You should have 4 produce lines. lot 1 shelf_1, lot 1 shelf_2, lot2 and for product which have tracking None')
+ self.assertEqual(len(product_produce.raw_workorder_line_ids), 4, 'You should have 4 produce lines. lot 1 shelf_1, lot 1 shelf_2, lot2 and for product which have tracking None')
product_produce.do_produce()
@@ -538,20 +538,20 @@ class TestMrpOrder(TestMrpCommon):
'active_ids': [mo.id],
}))
produce_form.qty_producing = 3
- self.assertEqual(len(produce_form.workorder_line_ids._records), 4, 'Update the produce quantity should change the components quantity.')
- self.assertEqual(sum([x['qty_done'] for x in produce_form.workorder_line_ids._records]), 15, 'Update the produce quantity should change the components quantity.')
- self.assertEqual(sum([x['qty_reserved'] for x in produce_form.workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.')
+ self.assertEqual(len(produce_form.raw_workorder_line_ids._records), 4, 'Update the produce quantity should change the components quantity.')
+ self.assertEqual(sum([x['qty_done'] for x in produce_form.raw_workorder_line_ids._records]), 15, 'Update the produce quantity should change the components quantity.')
+ self.assertEqual(sum([x['qty_reserved'] for x in produce_form.raw_workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.')
produce_form.qty_producing = 4
- self.assertEqual(len(produce_form.workorder_line_ids._records), 4, 'Update the produce quantity should change the components quantity.')
- self.assertEqual(sum([x['qty_done'] for x in produce_form.workorder_line_ids._records]), 20, 'Update the produce quantity should change the components quantity.')
- self.assertEqual(sum([x['qty_reserved'] for x in produce_form.workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.')
+ self.assertEqual(len(produce_form.raw_workorder_line_ids._records), 4, 'Update the produce quantity should change the components quantity.')
+ self.assertEqual(sum([x['qty_done'] for x in produce_form.raw_workorder_line_ids._records]), 20, 'Update the produce quantity should change the components quantity.')
+ self.assertEqual(sum([x['qty_reserved'] for x in produce_form.raw_workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.')
produce_form.qty_producing = 1
- self.assertEqual(len(produce_form.workorder_line_ids._records), 2, 'Update the produce quantity should change the components quantity.')
- self.assertEqual(sum([x['qty_done'] for x in produce_form.workorder_line_ids._records]), 5, 'Update the produce quantity should change the components quantity.')
- self.assertEqual(sum([x['qty_reserved'] for x in produce_form.workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.')
+ self.assertEqual(len(produce_form.raw_workorder_line_ids._records), 2, 'Update the produce quantity should change the components quantity.')
+ self.assertEqual(sum([x['qty_done'] for x in produce_form.raw_workorder_line_ids._records]), 5, 'Update the produce quantity should change the components quantity.')
+ self.assertEqual(sum([x['qty_reserved'] for x in produce_form.raw_workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.')
# try adding another product that doesn't belong to the BoM
- with produce_form.workorder_line_ids.new() as line:
+ with produce_form.raw_workorder_line_ids.new() as line:
line.product_id = self.product_4
line.qty_done = 1
produce_wizard = produce_form.save()
@@ -583,7 +583,7 @@ class TestMrpOrder(TestMrpCommon):
produce_form.qty_producing = 1
produce_wizard = produce_form.save()
- self.assertEqual(len(produce_wizard.workorder_line_ids), 2)
+ self.assertEqual(len(produce_wizard.raw_workorder_line_ids), 2)
produce_wizard.do_produce()
produce_form = Form(self.env['mrp.product.produce'].with_context({
@@ -594,7 +594,7 @@ class TestMrpOrder(TestMrpCommon):
produce_wizard = produce_form.save()
- self.assertEqual(len(produce_wizard.workorder_line_ids), 2)
+ self.assertEqual(len(produce_wizard.raw_workorder_line_ids), 2)
produce_wizard.do_produce()
mo.button_mark_done()
@@ -632,9 +632,9 @@ class TestMrpOrder(TestMrpCommon):
}))
produce_form.qty_producing = 1
produce_wizard = produce_form.save()
- self.assertEqual(len(produce_wizard.workorder_line_ids), 2)
- self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1).qty_reserved, 4)
- self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p2).qty_reserved, 1)
+ self.assertEqual(len(produce_wizard.raw_workorder_line_ids), 2)
+ self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1).qty_reserved, 4)
+ self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p2).qty_reserved, 1)
produce_wizard.do_produce()
produce_form = Form(self.env['mrp.product.produce'].with_context({
@@ -646,11 +646,11 @@ class TestMrpOrder(TestMrpCommon):
# p1 1 1 1
# p1 3 0 3
# p2 1 1 1
- self.assertEqual(len(produce_wizard.workorder_line_ids), 3)
- self.assertEqual(sum(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1).mapped('qty_reserved')), 1)
- self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1 and l.qty_reserved).qty_to_consume, 1)
- self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1 and not l.qty_reserved).qty_to_consume, 3)
- self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p2).qty_reserved, 1)
+ self.assertEqual(len(produce_wizard.raw_workorder_line_ids), 3)
+ self.assertEqual(sum(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1).mapped('qty_reserved')), 1)
+ self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1 and l.qty_reserved).qty_to_consume, 1)
+ self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1 and not l.qty_reserved).qty_to_consume, 3)
+ self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p2).qty_reserved, 1)
with Form(produce_wizard) as produce_form:
produce_form.qty_producing = 2
@@ -658,11 +658,11 @@ class TestMrpOrder(TestMrpCommon):
# p1 7 0 7
# p2 1 1 1
# p2 1 0 1
- self.assertEqual(len(produce_wizard.workorder_line_ids), 4)
- self.assertEqual(sum(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1).mapped('qty_reserved')), 1)
- self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1 and l.qty_reserved).qty_to_consume, 1)
- self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1 and not l.qty_reserved).qty_to_consume, 7)
- self.assertEqual(sum(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p2).mapped('qty_reserved')), 1)
+ self.assertEqual(len(produce_wizard.raw_workorder_line_ids), 4)
+ self.assertEqual(sum(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1).mapped('qty_reserved')), 1)
+ self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1 and l.qty_reserved).qty_to_consume, 1)
+ self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1 and not l.qty_reserved).qty_to_consume, 7)
+ self.assertEqual(sum(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p2).mapped('qty_reserved')), 1)
produce_wizard.do_produce()
@@ -693,7 +693,7 @@ class TestMrpOrder(TestMrpCommon):
with self.assertRaises(UserError):
# try adding another line for a bom product to increase the quantity
produce_form.qty_producing = 1
- with produce_form.workorder_line_ids.new() as line:
+ with produce_form.raw_workorder_line_ids.new() as line:
line.product_id = p1
line.qty_done = 1
product_produce = produce_form.save()
@@ -702,7 +702,7 @@ class TestMrpOrder(TestMrpCommon):
with self.assertRaises(UserError):
# Try updating qty_done
product_produce = produce_form.save()
- product_produce.workorder_line_ids[0].qty_done += 1
+ product_produce.raw_workorder_line_ids[0].qty_done += 1
product_produce.do_produce()
with self.assertRaises(UserError):
@@ -712,7 +712,7 @@ class TestMrpOrder(TestMrpCommon):
'active_ids': [mo.id],
}))
produce_form.qty_producing = 1
- with produce_form.workorder_line_ids.new() as line:
+ with produce_form.raw_workorder_line_ids.new() as line:
line.product_id = self.product_4
line.qty_done = 1
product_produce = produce_form.save()
@@ -725,13 +725,162 @@ class TestMrpOrder(TestMrpCommon):
}))
produce_form.qty_producing = 1
- with produce_form.workorder_line_ids.new() as line:
+ with produce_form.raw_workorder_line_ids.new() as line:
line.product_id = p1
line.qty_done = 1
product_produce = produce_form.save()
- product_produce.workorder_line_ids[1].qty_done -= 1
+ product_produce.raw_workorder_line_ids[1].qty_done -= 1
product_produce.do_produce()
+ def test_product_produce_10(self):
+ """ Produce byproduct with serial, lot and not tracked.
+ byproduct1 serial 1.0
+ byproduct2 lot 2.0
+ byproduct3 none 1.0 dozen
+ Check qty producing update and moves finished values.
+ """
+ dozen = self.env.ref('uom.product_uom_dozen')
+ self.byproduct1 = self.env['product.product'].create({
+ 'name': 'Byproduct 1',
+ 'type': 'product',
+ 'tracking': 'serial'
+ })
+ self.serial_1 = self.env['stock.production.lot'].create({
+ 'product_id': self.byproduct1.id,
+ 'name': 'serial 1'
+ })
+ self.serial_2 = self.env['stock.production.lot'].create({
+ 'product_id': self.byproduct1.id,
+ 'name': 'serial 2'
+ })
+
+ self.byproduct2 = self.env['product.product'].create({
+ 'name': 'Byproduct 2',
+ 'type': 'product',
+ 'tracking': 'lot',
+ })
+ self.lot_1 = self.env['stock.production.lot'].create({
+ 'product_id': self.byproduct2.id,
+ 'name': 'Lot 1'
+ })
+ self.lot_2 = self.env['stock.production.lot'].create({
+ 'product_id': self.byproduct2.id,
+ 'name': 'Lot 2'
+ })
+
+ self.byproduct3 = self.env['product.product'].create({
+ 'name': 'Byproduct 3',
+ 'type': 'product',
+ 'tracking': 'none',
+ })
+
+ with Form(self.bom_1) as bom:
+ bom.product_qty = 1.0
+ with bom.byproduct_ids.new() as bp:
+ bp.product_id = self.byproduct1
+ bp.product_qty = 1.0
+ with bom.byproduct_ids.new() as bp:
+ bp.product_id = self.byproduct2
+ bp.product_qty = 2.0
+ with bom.byproduct_ids.new() as bp:
+ bp.product_id = self.byproduct3
+ bp.product_qty = 2.0
+ bp.product_uom_id = dozen
+
+ self.bom_1.routing_id = False
+
+ mo_form = Form(self.env['mrp.production'])
+ mo_form.product_id = self.product_4
+ mo_form.bom_id = self.bom_1
+ mo_form.product_qty = 2
+ mo = mo_form.save()
+
+ mo.action_confirm()
+ produce_form = Form(self.env['mrp.product.produce'].with_context({
+ 'active_id': mo.id,
+ 'active_ids': [mo.id],
+ }))
+ self.assertEqual(len(produce_form.finished_workorder_line_ids), 4)
+ produce_wizard = produce_form.save()
+ wokorder_lines_byproduct_1 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct1)
+ self.assertEqual(len(wokorder_lines_byproduct_1), 2)
+ self.assertEqual(wokorder_lines_byproduct_1.mapped('qty_to_consume'), [1.0, 1.0])
+ self.assertEqual(wokorder_lines_byproduct_1.mapped('qty_done'), [1.0, 1.0])
+ wokorder_lines_byproduct_2 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct2)
+ self.assertEqual(len(wokorder_lines_byproduct_2), 1)
+ self.assertEqual(wokorder_lines_byproduct_2.qty_to_consume, 4.0)
+ self.assertEqual(wokorder_lines_byproduct_2.qty_done, 4.0)
+
+ wokorder_lines_byproduct_3 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct3)
+ self.assertEqual(wokorder_lines_byproduct_3.qty_to_consume, 4.0)
+ self.assertEqual(wokorder_lines_byproduct_3.qty_done, 4.0)
+ self.assertEqual(wokorder_lines_byproduct_3.product_uom_id, dozen)
+
+ produce_form = Form(produce_wizard)
+ produce_form.qty_producing = 1.0
+ self.assertEqual(len(produce_form.finished_workorder_line_ids), 3)
+ produce_wizard = produce_form.save()
+ wokorder_lines_byproduct_1 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct1)
+ self.assertEqual(len(wokorder_lines_byproduct_1), 1)
+ self.assertEqual(wokorder_lines_byproduct_1.qty_to_consume, 1.0)
+ self.assertEqual(wokorder_lines_byproduct_1.qty_done, 1.0)
+ wokorder_lines_byproduct_2 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct2)
+ self.assertEqual(len(wokorder_lines_byproduct_2), 1)
+ self.assertEqual(wokorder_lines_byproduct_2.qty_to_consume, 2.0)
+ self.assertEqual(wokorder_lines_byproduct_2.qty_done, 2.0)
+
+ wokorder_lines_byproduct_3 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct3)
+ self.assertEqual(wokorder_lines_byproduct_3.qty_to_consume, 2.0)
+ self.assertEqual(wokorder_lines_byproduct_3.qty_done, 2.0)
+ self.assertEqual(wokorder_lines_byproduct_3.product_uom_id, dozen)
+
+ produce_form = Form(produce_wizard)
+ wokorder_lines_byproduct_1.lot_id = self.serial_1
+ wokorder_lines_byproduct_2.lot_id = self.lot_1
+ produce_wizard = produce_form.save()
+ produce_wizard.do_produce()
+
+ produce_form = Form(self.env['mrp.product.produce'].with_context({
+ 'active_id': mo.id,
+ 'active_ids': [mo.id],
+ }))
+ self.assertEqual(produce_form.qty_producing, 1.0)
+ self.assertEqual(len(produce_form.finished_workorder_line_ids), 3)
+ produce_wizard = produce_form.save()
+
+ wokorder_lines_byproduct_1 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct1)
+ self.assertEqual(len(wokorder_lines_byproduct_1), 1)
+ self.assertEqual(wokorder_lines_byproduct_1.qty_to_consume, 1.0)
+ self.assertEqual(wokorder_lines_byproduct_1.qty_done, 1.0)
+ wokorder_lines_byproduct_2 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct2)
+ self.assertEqual(len(wokorder_lines_byproduct_2), 1)
+ self.assertEqual(wokorder_lines_byproduct_2.qty_to_consume, 2.0)
+ self.assertEqual(wokorder_lines_byproduct_2.qty_done, 2.0)
+
+ wokorder_lines_byproduct_3 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct3)
+ self.assertEqual(wokorder_lines_byproduct_3.qty_to_consume, 2.0)
+ self.assertEqual(wokorder_lines_byproduct_3.qty_done, 2.0)
+ self.assertEqual(wokorder_lines_byproduct_3.product_uom_id, dozen)
+
+ produce_form = Form(produce_wizard)
+ wokorder_lines_byproduct_1.lot_id = self.serial_2
+ wokorder_lines_byproduct_2.lot_id = self.lot_2
+ wokorder_lines_byproduct_3.qty_done = 3.0
+ produce_wizard = produce_form.save()
+
+ produce_wizard.do_produce()
+
+ mo.button_mark_done()
+ move_lines_byproduct_1 = mo.move_finished_ids.filtered(lambda l: l.product_id == self.byproduct1).mapped('move_line_ids')
+ move_lines_byproduct_2 = mo.move_finished_ids.filtered(lambda l: l.product_id == self.byproduct2).mapped('move_line_ids')
+ move_lines_byproduct_3 = mo.move_finished_ids.filtered(lambda l: l.product_id == self.byproduct3).mapped('move_line_ids')
+ self.assertEqual(move_lines_byproduct_1.filtered(lambda ml: ml.lot_id == self.serial_1).qty_done, 1.0)
+ self.assertEqual(move_lines_byproduct_1.filtered(lambda ml: ml.lot_id == self.serial_2).qty_done, 1.0)
+ self.assertEqual(move_lines_byproduct_2.filtered(lambda ml: ml.lot_id == self.lot_1).qty_done, 2.0)
+ self.assertEqual(move_lines_byproduct_2.filtered(lambda ml: ml.lot_id == self.lot_2).qty_done, 2.0)
+ self.assertEqual(sum(move_lines_byproduct_3.mapped('qty_done')), 5.0)
+ self.assertEqual(move_lines_byproduct_3.mapped('product_uom_id'), dozen)
+
def test_product_produce_uom(self):
""" Produce a finished product tracked by serial number. Set another
UoM on the bom. The produce wizard should keep the UoM of the product (unit)
@@ -835,8 +984,8 @@ class TestMrpOrder(TestMrpCommon):
}))
product_produce = produce_form.save()
self.assertEqual(product_produce.qty_producing, 1)
- self.assertEqual(len(product_produce.workorder_line_ids), 2, 'Should be 2 lines since the component tracking is serial and quantity 2.')
- self.assertEqual(product_produce.workorder_line_ids[0].qty_to_consume, 1, 'Should be 1 unit since the tracking is serial and quantity 2.')
- self.assertEqual(product_produce.workorder_line_ids[0].product_uom_id, unit, 'Should be the product uom so "unit"')
- self.assertEqual(product_produce.workorder_line_ids[1].qty_to_consume, 1, 'Should be 1 unit since the tracking is serial and quantity 2.')
- self.assertEqual(product_produce.workorder_line_ids[1].product_uom_id, unit, 'should be the product uom so "unit"')
+ self.assertEqual(len(product_produce.raw_workorder_line_ids), 2, 'Should be 2 lines since the component tracking is serial and quantity 2.')
+ self.assertEqual(product_produce.raw_workorder_line_ids[0].qty_to_consume, 1, 'Should be 1 unit since the tracking is serial and quantity 2.')
+ self.assertEqual(product_produce.raw_workorder_line_ids[0].product_uom_id, unit, 'Should be the product uom so "unit"')
+ self.assertEqual(product_produce.raw_workorder_line_ids[1].qty_to_consume, 1, 'Should be 1 unit since the tracking is serial and quantity 2.')
+ self.assertEqual(product_produce.raw_workorder_line_ids[1].product_uom_id, unit, 'should be the product uom so "unit"')
diff --git a/addons/mrp/tests/test_unbuild.py b/addons/mrp/tests/test_unbuild.py
index 28a436c9869..a9c808fd921 100644
--- a/addons/mrp/tests/test_unbuild.py
+++ b/addons/mrp/tests/test_unbuild.py
@@ -422,7 +422,7 @@ class TestUnbuild(TestMrpCommon):
produce_form.qty_producing = 3.0
produce_form.final_lot_id = lot_finished_1
produce_wizard = produce_form.save()
- produce_wizard.workorder_line_ids[0].lot_id = lot_1
+ produce_wizard._workorder_line_ids()[0].lot_id = lot_1
produce_wizard.do_produce()
lot_2 = self.env['stock.production.lot'].create({
@@ -444,7 +444,7 @@ class TestUnbuild(TestMrpCommon):
produce_form.final_lot_id = lot_finished_2
produce_wizard = produce_form.save()
- produce_wizard.workorder_line_ids[0].lot_id = lot_2
+ produce_wizard._workorder_line_ids()[0].lot_id = lot_2
produce_wizard.do_produce()
mo.button_mark_done()
ml = mo.finished_move_line_ids[0].consume_line_ids.filtered(lambda m: m.product_id == p1 and m.lot_produced_id == lot_finished_1)
diff --git a/addons/mrp/tests/test_workorder_operation.py b/addons/mrp/tests/test_workorder_operation.py
index ab4bf83ad8b..94e46df9589 100644
--- a/addons/mrp/tests/test_workorder_operation.py
+++ b/addons/mrp/tests/test_workorder_operation.py
@@ -95,7 +95,7 @@ class TestWorkOrderProcess(TestMrpCommon):
finished_lot =self.env['stock.production.lot'].create({'product_id': production_table.product_id.id})
workorder.write({'final_lot_id': finished_lot.id})
workorder.button_start()
- for workorder_line_id in workorder.workorder_line_ids:
+ for workorder_line_id in workorder._workorder_line_ids():
if workorder_line_id.product_id.id == product_bolt.id:
workorder_line_id.write({'lot_id': lot_bolt.id, 'qty_done': 1})
if workorder_line_id.product_id.id == product_table_sheet.id:
@@ -199,7 +199,7 @@ class TestWorkOrderProcess(TestMrpCommon):
finished_lot = self.env['stock.production.lot'].create({'product_id': production_table.product_id.id})
workorders[0].write({'final_lot_id': finished_lot.id, 'qty_producing': 1.0})
workorders[0].button_start()
- workorders[0].workorder_line_ids[0].write({'lot_id': lot_sheet.id, 'qty_done': 1})
+ workorders[0]._workorder_line_ids()[0].write({'lot_id': lot_sheet.id, 'qty_done': 1})
self.assertEqual(workorders[0].state, 'progress')
workorders[0].record_production()
@@ -211,7 +211,7 @@ class TestWorkOrderProcess(TestMrpCommon):
# ---------------------------------------------------------
workorders[1].button_start()
workorders[1].qty_producing = 1.0
- workorders[1].workorder_line_ids[0].write({'lot_id': lot_leg.id, 'qty_done': 4})
+ workorders[1]._workorder_line_ids()[0].write({'lot_id': lot_leg.id, 'qty_done': 4})
workorders[1].record_production()
move_leg = production_table.move_raw_ids.filtered(lambda p: p.product_id == product_table_leg)
#self.assertEqual(workorders[1].state, 'done')
@@ -222,7 +222,7 @@ class TestWorkOrderProcess(TestMrpCommon):
# ---------------------------------------------------------
workorders[2].button_start()
workorders[2].qty_producing = 1.0
- move_lot = workorders[2].workorder_line_ids[0]
+ move_lot = workorders[2]._workorder_line_ids()[0]
move_lot.write({'lot_id': lot_bolt.id, 'qty_done': 4})
move_table_bolt = production_table.move_raw_ids.filtered(lambda p: p.product_id.id == product_bolt.id)
workorders[2].record_production()
@@ -486,7 +486,7 @@ class TestWorkOrderProcess(TestMrpCommon):
laptop_lot_001 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id})
product_form.final_lot_id = laptop_lot_001
product_consume = product_form.save()
- product_consume.workorder_line_ids[0].qty_done = 12
+ product_consume._workorder_line_ids()[0].qty_done = 12
product_consume.do_produce()
# Check consumed move after produce 6 quantity of customized laptop.
@@ -513,8 +513,8 @@ class TestWorkOrderProcess(TestMrpCommon):
laptop_lot_002 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id})
produce_form.final_lot_id = laptop_lot_002
product_consume = produce_form.save()
- self.assertEquals(len(product_consume.workorder_line_ids), 2)
- product_consume.workorder_line_ids[0].qty_done = 8
+ self.assertEquals(len(product_consume._workorder_line_ids()), 2)
+ product_consume._workorder_line_ids()[0].qty_done = 8
product_consume.do_produce()
charger_move = mo_custom_laptop.move_raw_ids.filtered(lambda x: x.product_id.id == product_charger.id and x.state != 'done')
keybord_move = mo_custom_laptop.move_raw_ids.filtered(lambda x: x.product_id.id == product_keybord.id and x.state !='done')
@@ -652,9 +652,9 @@ class TestWorkOrderProcess(TestMrpCommon):
produce_form.final_lot_id = lot_a
product_consume = produce_form.save()
# laptop_lot_002 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id})
- self.assertEquals(len(product_consume.workorder_line_ids), 2)
- product_consume.workorder_line_ids.filtered(lambda x: x.product_id == product_C).write({'qty_done': 3000})
- product_consume.workorder_line_ids.filtered(lambda x: x.product_id == product_B).write({'qty_done': 20})
+ self.assertEquals(len(product_consume._workorder_line_ids()), 2)
+ product_consume._workorder_line_ids().filtered(lambda x: x.product_id == product_C).write({'qty_done': 3000})
+ product_consume._workorder_line_ids().filtered(lambda x: x.product_id == product_B).write({'qty_done': 20})
product_consume.do_produce()
mo_custom_product.post_inventory()
diff --git a/addons/mrp/views/mrp_bom_views.xml b/addons/mrp/views/mrp_bom_views.xml
index 9431487c2d1..69423abd6d8 100644
--- a/addons/mrp/views/mrp_bom_views.xml
+++ b/addons/mrp/views/mrp_bom_views.xml
@@ -75,11 +75,13 @@
+
diff --git a/addons/mrp/views/mrp_workorder_views.xml b/addons/mrp/views/mrp_workorder_views.xml
index 7ac0c81ffd8..887de8ae1b5 100644
--- a/addons/mrp/views/mrp_workorder_views.xml
+++ b/addons/mrp/views/mrp_workorder_views.xml
@@ -153,10 +153,10 @@
-
-
+ Components
+
-
+
@@ -166,11 +166,16 @@
-
+ Finished Products
+
-
+
+
+
+
+
diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py
index 3a0bcc7df7c..01a3807a70d 100644
--- a/addons/mrp/wizard/change_production_qty.py
+++ b/addons/mrp/wizard/change_production_qty.py
@@ -27,26 +27,17 @@ class ChangeProductionQty(models.TransientModel):
return res
@api.model
- def _update_product_to_produce(self, production, qty, old_qty):
- production_move = production.move_finished_ids.filtered(lambda x: x.product_id.id == production.product_id.id and x.state not in ('done', 'cancel'))
- if production_move:
- production_move.write({'product_uom_qty': qty})
- else:
- production_move = production._generate_finished_moves()
- production_move = production.move_finished_ids.filtered(lambda x: x.state not in ('done', 'cancel') and production.product_id.id == x.product_id.id)
- production_move.write({'product_uom_qty': qty})
- modification = {production_move: (qty, old_qty)}
- for sub_product_line in production.bom_id.sub_products:
- move = production.move_finished_ids.filtered(lambda x: x.subproduct_id == sub_product_line and x.state not in ('done', 'cancel'))
- if move:
- product_uom_factor = production.product_uom_id._compute_quantity(production.product_qty - production.qty_produced, production.bom_id.product_uom_id)
- qty1 = sub_product_line.product_qty
- qty1 *= product_uom_factor / production.bom_id.product_qty
- modification[move[0]] = (qty1, move[0].product_uom_qty)
- move[0].write({'product_uom_qty': qty1})
- else:
- move = production._create_byproduct_move(sub_product_line)
- modification[move] = (move.product_uom_qty, 0)
+ def _update_finished_moves(self, production, qty, old_qty):
+ """ Update finished product and its byproducts. This method only update
+ the finished moves not done or cancel and just increase or decrease
+ their quantity according the unit_ratio. It does not use the BoM, BoM
+ modification during production would not be taken into consideration.
+ """
+ modification = {}
+ for move in production.move_finished_ids.filtered(lambda m: m.state not in ('done', 'cancel')):
+ qty = (qty - old_qty) * move.unit_factor
+ modification[move] = (move.product_uom_qty - qty, move.product_uom_qty)
+ move[0].write({'product_uom_qty': move.product_uom_qty - qty})
return modification
@api.multi
@@ -80,7 +71,7 @@ class ChangeProductionQty(models.TransientModel):
for bom, bom_data in boms:
for operation in bom.routing_id.operation_ids:
operation_bom_qty[operation.id] = bom_data['qty']
- finished_moves_modification = self._update_product_to_produce(production, production.product_qty - qty_produced, old_production_qty)
+ finished_moves_modification = self._update_finished_moves(production, production.product_qty - qty_produced, old_production_qty)
production._log_downside_manufactured_quantity(finished_moves_modification)
moves = production.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
moves._action_assign()
@@ -98,7 +89,7 @@ class ChangeProductionQty(models.TransientModel):
quantity = quantity if (quantity > 0) else 0
if float_is_zero(quantity, precision_digits=precision):
wo.final_lot_id = False
- wo.workorder_line_ids.unlink()
+ wo._workorder_line_ids().unlink()
wo.qty_producing = quantity
if wo.qty_produced < wo.qty_production and wo.state == 'done':
wo.state = 'progress'
@@ -115,7 +106,7 @@ class ChangeProductionQty(models.TransientModel):
(moves_finished + moves_raw).write({'workorder_id': wo.id})
if wo.state not in ('done', 'cancel'):
line_values = wo._update_workorder_lines()
- wo.workorder_line_ids |= wo.workorder_line_ids.create(line_values['to_create'])
+ self._workorder_line_ids().create(line_values['to_create'])
if line_values['to_delete']:
line_values['to_delete'].unlink()
for line, vals in line_values['to_update'].items():
diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py
index 01ef3cea857..038666e9618 100644
--- a/addons/mrp/wizard/mrp_product_produce.py
+++ b/addons/mrp/wizard/mrp_product_produce.py
@@ -42,8 +42,14 @@ class MrpProductProduce(models.TransientModel):
serial = fields.Boolean('Requires Serial')
product_tracking = fields.Selection(related="product_id.tracking")
is_pending_production = fields.Boolean(compute='_compute_pending_production')
- workorder_line_ids = fields.One2many('mrp.product.produce.line', 'product_produce_id')
+
move_raw_ids = fields.One2many(related='production_id.move_raw_ids')
+ move_finished_ids = fields.One2many(related='production_id.move_finished_ids')
+
+ raw_workorder_line_ids = fields.One2many('mrp.product.produce.line',
+ 'raw_product_produce_id', string='Components')
+ finished_workorder_line_ids = fields.One2many('mrp.product.produce.line',
+ 'finished_product_produce_id', string='By-products')
@api.depends('qty_producing')
def _compute_pending_production(self):
@@ -98,27 +104,32 @@ class MrpProductProduce(models.TransientModel):
def _record_production(self):
# Check all the product_produce line have a move id (the user can add product
# to consume directly in the wizard)
- for line in self.workorder_line_ids:
+ for line in self._workorder_line_ids():
if not line.move_id:
- order = self.production_id
# Find move_id that would match
- move_id = order.move_raw_ids.filtered(
- lambda m: m.product_id == line.product_id and m.state not in ('done', 'cancel')
- )
+ if line.raw_product_produce_id:
+ moves = self.move_raw_ids
+ else:
+ moves = self.move_finished_ids
+ move_id = moves.filtered(lambda m: m.product_id == line.product_id and m.state not in ('done', 'cancel'))
if not move_id:
# create a move to assign it to the line
- move_id = self.env['stock.move'].create({
- 'name': order.name,
- 'reference': order.name,
- 'product_id': line.product_id.id,
- 'product_uom': line.product_uom_id.id,
- 'location_id': order.location_src_id.id,
- 'location_dest_id': line.product_id.property_stock_production.id,
- 'raw_material_production_id': order.id,
- 'group_id': order.procurement_group_id.id,
- 'origin': order.name,
- 'state': 'confirmed'
- })
+ if line.raw_product_produce_id:
+ values = {
+ 'name': self.production_id.name,
+ 'reference': self.production_id.name,
+ 'product_id': line.product_id.id,
+ 'product_uom': line.product_uom_id.id,
+ 'location_id': self.production_id.location_src_id.id,
+ 'location_dest_id': line.product_id.property_stock_production.id,
+ 'raw_material_production_id': self.production_id.id,
+ 'group_id': self.production_id.procurement_group_id.id,
+ 'origin': self.production_id.name,
+ 'state': 'confirmed'
+ }
+ else:
+ values = self.production_id._get_finished_move_value(line.product_id.id, 0, line.product_uom_id.id)
+ move_id = self.env['stock.move'].create(values)
line.move_id = move_id.id
# Save product produce lines data into stock moves/move lines
@@ -126,7 +137,7 @@ class MrpProductProduce(models.TransientModel):
if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0:
raise UserError(_("The production order for '%s' has no quantity specified.") % self.product_id.display_name)
self._update_finished_move()
- self._update_raw_moves()
+ self._update_moves()
if self.production_id.state == 'confirmed':
self.production_id.write({
'date_start': datetime.now(),
@@ -138,10 +149,21 @@ class MrpProductProduceLine(models.TransientModel):
_inherit = ["mrp.abstract.workorder.line"]
_description = "Record production line"
- product_produce_id = fields.Many2one('mrp.product.produce', 'Produce wizard')
+ raw_product_produce_id = fields.Many2one('mrp.product.produce', 'Component in Produce wizard')
+ finished_product_produce_id = fields.Many2one('mrp.product.produce', 'Finished Product in Produce wizard')
- def _get_final_lot(self):
- return self.product_produce_id.final_lot_id
+ @api.model
+ def _get_raw_workorder_inverse_name(self):
+ return 'raw_product_produce_id'
+
+ @api.model
+ def _get_finished_workoder_inverse_name(self):
+ return 'finished_product_produce_id'
+
+ def _get_final_lots(self):
+ product_produce_id = self.raw_product_produce_id or self.finished_product_produce_id
+ return product_produce_id.final_lot_id | product_produce_id.finished_workorder_line_ids.mapped('lot_id')
def _get_production(self):
- return self.product_produce_id.production_id
+ product_produce_id = self.raw_product_produce_id or self.finished_product_produce_id
+ return product_produce_id.production_id
diff --git a/addons/mrp/wizard/mrp_product_produce_views.xml b/addons/mrp/wizard/mrp_product_produce_views.xml
index 3864c6384a4..26307620ad3 100644
--- a/addons/mrp/wizard/mrp_product_produce_views.xml
+++ b/addons/mrp/wizard/mrp_product_produce_views.xml
@@ -25,8 +25,9 @@
+ Components
-
+
@@ -39,6 +40,20 @@
+ By-products
+
+
+
+
+
+
+
+
+
+
+
+
+