[REF] mrp: byproduct as a workorder line

Technical refactoring in order to set by-products the same way
than raw materials. Before this commit byproduct were always set
at the last workorder or automaticaly set at the end of produce
wizard with the same quantity than in the BoM. In order to modify
it, the user has to produce the finished product then unlock and edit
the finished move line linked to the byproduct. In this commit, the
user could specify at which workorder the byproduct is created and
he could directly specify another quantity done in the produce wizard
inside a specific tab for by-products.

Technicaly, abstract workorder will add a new many2one key on workorder
line in order to set 2 different one2many (one for finished goods and
the other for raw materials). The key is set depending the many2one
key for production on the linked stock move. On the by-products itself,
it is now managed as a workorder line, thus the code to generate them
and transform them in a finished stock move line is the same than for
the raw components.
This commit is contained in:
Arnold Moyaux
2019-04-12 12:50:34 +00:00
parent 6f25cf1aa7
commit a4c0994cf4
15 changed files with 422 additions and 248 deletions
+1 -1
View File
@@ -706,7 +706,7 @@
<function model="mrp.product.produce.line" name="create"> <function model="mrp.product.produce.line" name="create">
<value model="mrp.product.produce.line" eval="[dict(item, <value model="mrp.product.produce.line" eval="[dict(item,
**{ **{
'product_produce_id': obj().env['mrp.product.produce'].search([('production_id', '=', obj().env.ref('mrp.mrp_production_laptop_cust').id)]).id 'raw_product_produce_id': obj().env['mrp.product.produce'].search([('production_id', '=', obj().env.ref('mrp.mrp_production_laptop_cust').id)]).id
}) for item in obj().env['mrp.product.produce'].search([('production_id', '=', obj().env.ref('mrp.mrp_production_laptop_cust').id)])._update_workorder_lines()['to_create']]"/> }) for item in obj().env['mrp.product.produce'].search([('production_id', '=', obj().env.ref('mrp.mrp_production_laptop_cust').id)])._update_workorder_lines()['to_create']]"/>
</function> </function>
+52 -53
View File
@@ -26,6 +26,10 @@ class MrpAbstractWorkorder(models.AbstractModel):
) )
use_create_components_lots = fields.Boolean(related="production_id.picking_type_id.use_create_components_lots") 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') @api.onchange('qty_producing')
def _onchange_qty_producing(self): def _onchange_qty_producing(self):
""" Modify the qty currently producing will modify the existing """ Modify the qty currently producing will modify the existing
@@ -35,10 +39,13 @@ class MrpAbstractWorkorder(models.AbstractModel):
if self.qty_producing <= 0: if self.qty_producing <= 0:
raise UserError(_('You have to produce at least one %s.') % self.product_uom_id.name) raise UserError(_('You have to produce at least one %s.') % self.product_uom_id.name)
line_values = self._update_workorder_lines() line_values = self._update_workorder_lines()
for vals in line_values['to_create']: for values in line_values['to_create']:
self.workorder_line_ids |= self.workorder_line_ids.new(vals) self.env[self._workorder_line_ids()._name].new(values)
if line_values['to_delete']: for line in line_values['to_delete']:
self.workorder_line_ids -= 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(): for line, vals in line_values['to_update'].items():
line.update(vals) line.update(vals)
@@ -49,17 +56,19 @@ class MrpAbstractWorkorder(models.AbstractModel):
used in onchange and request that write on db (e.g. workorder creation). used in onchange and request that write on db (e.g. workorder creation).
""" """
line_values = {'to_create': [], 'to_delete': [], 'to_update': {}} 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_finished_ids = self.move_finished_ids.filtered(lambda move: move.product_id != self.product_id and move.state not in ('done', 'cancel'))
move_workorder_lines = self.workorder_line_ids.filtered(lambda w: w.move_id == move_raw) 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 # Compute the new quantity for the current component
rounding = move_raw.product_uom.rounding rounding = move.product_uom.rounding
if move_raw.product_id.tracking == 'serial': if move.product_id.tracking == 'serial':
uom = move_raw.product_id.uom_id uom = move.product_id.uom_id
else: else:
uom = move_raw.product_uom uom = move.product_uom
new_qty = move_raw.product_uom._compute_quantity( new_qty = move.product_uom._compute_quantity(
self.qty_producing * move_raw.unit_factor, self.qty_producing * move.unit_factor,
uom, uom,
round=False round=False
) )
@@ -141,8 +150,8 @@ class MrpAbstractWorkorder(models.AbstractModel):
# if there are still qty_todo, create new wo lines # if there are still qty_todo, create new wo lines
if float_compare(qty_todo, 0.0, precision_rounding=rounding) > 0: if float_compare(qty_todo, 0.0, precision_rounding=rounding) > 0:
for vals in self._generate_lines_values(move_raw, qty_todo): for values in self._generate_lines_values(move, qty_todo):
line_values['to_create'].append(vals) line_values['to_create'].append(values)
return line_values return line_values
@api.model @api.model
@@ -155,14 +164,21 @@ class MrpAbstractWorkorder(models.AbstractModel):
""" """
lines = [] lines = []
is_tracked = move.product_id.tracking != 'none' 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: 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: if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) <= 0:
break break
# move line already 'used' in workorder (from its lot for instance) # 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: 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 continue
# search wo line on which the lot is not fully consumed or other reserved lot # 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 lambda line: line.product_id == move_line.product_id and
line.lot_id == move_line.lot_id line.lot_id == move_line.lot_id
) )
@@ -173,38 +189,39 @@ class MrpAbstractWorkorder(models.AbstractModel):
continue continue
else: else:
to_consume_in_line = min(qty_to_consume, move_line.product_uom_qty - move_line.qty_done) to_consume_in_line = min(qty_to_consume, move_line.product_uom_qty - move_line.qty_done)
line = { line.update({
'move_id': move.id, 'move_id': move.id,
'product_id': move.product_id.id, 'product_id': move.product_id.id,
'product_uom_id': is_tracked and move.product_id.uom_id.id or move.product_uom.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_to_consume': to_consume_in_line,
'qty_reserved': to_consume_in_line, 'qty_reserved': to_consume_in_line,
'lot_id': move_line.lot_id.id, 'lot_id': move_line.lot_id.id,
'qty_done': to_consume_in_line 'qty_done': to_consume_in_line,
} })
lines.append(line) lines.append(line)
qty_to_consume -= to_consume_in_line qty_to_consume -= to_consume_in_line
# The move has not reserved the whole quantity so we create new wo lines # 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: 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': if move.product_id.tracking == 'serial':
while float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) > 0: while float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) > 0:
line = { line.update({
'move_id': move.id, 'move_id': move.id,
'product_id': move.product_id.id, 'product_id': move.product_id.id,
'product_uom_id': move.product_id.uom_id.id, 'product_uom_id': move.product_id.uom_id.id,
'qty_to_consume': 1, 'qty_to_consume': 1,
'qty_done': 1, 'qty_done': 1,
} })
lines.append(line) lines.append(line)
qty_to_consume -= 1 qty_to_consume -= 1
else: else:
line = { line.update({
'move_id': move.id, 'move_id': move.id,
'product_id': move.product_id.id, 'product_id': move.product_id.id,
'product_uom_id': move.product_uom.id, 'product_uom_id': move.product_uom.id,
'qty_to_consume': qty_to_consume, 'qty_to_consume': qty_to_consume,
'qty_done': qty_to_consume 'qty_done': qty_to_consume,
} })
lines.append(line) lines.append(line)
return lines return lines
@@ -245,52 +262,26 @@ class MrpAbstractWorkorder(models.AbstractModel):
float_round(self.qty_producing, precision_rounding=rounding) 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')) def _update_moves(self):
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):
""" Once the production is done. Modify the workorder lines into """ Once the production is done. Modify the workorder lines into
stock move line with the registered lot and quantity done. stock move line with the registered lot and quantity done.
""" """
# Before writting produce quantities, we ensure they respect the bom strictness # Before writting produce quantities, we ensure they respect the bom strictness
self._strict_consumption_check() self._strict_consumption_check()
vals_list = [] 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: for line in workorder_lines_to_process:
line._update_move_lines() line._update_move_lines()
if float_compare(line.qty_done, 0, precision_rounding=line.product_uom_id.rounding) > 0: if float_compare(line.qty_done, 0, precision_rounding=line.product_uom_id.rounding) > 0:
vals_list += line._create_extra_move_lines() 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) self.env['stock.move.line'].create(vals_list)
def _strict_consumption_check(self): def _strict_consumption_check(self):
if self.consumption == 'strict': if self.consumption == 'strict':
for move in self.move_raw_ids: 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_done = sum(lines.mapped('qty_done'))
qty_to_consume = sum(lines.mapped('qty_to_consume')) qty_to_consume = sum(lines.mapped('qty_to_consume'))
rounding = self.product_uom_id.rounding rounding = self.product_uom_id.rounding
@@ -444,6 +435,14 @@ class MrpAbstractWorkorderLine(models.AbstractModel):
return self.move_id.move_line_ids.filtered(lambda ml: return self.move_id.move_line_ids.filtered(lambda ml:
ml.lot_id == self.lot_id and ml.product_id == self.product_id) 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 # To be implemented in specific model
def _get_final_lot(self): def _get_final_lot(self):
raise NotImplementedError('Method _get_final_lot() undefined on %s' % self) raise NotImplementedError('Method _get_final_lot() undefined on %s' % self)
+24 -39
View File
@@ -463,15 +463,18 @@ class MrpProduction(models.Model):
self.is_locked = not self.is_locked self.is_locked = not self.is_locked
return True return True
def _generate_finished_moves(self): def _get_finished_move_value(self, product_id, product_uom_qty, product_uom, operation_id=False, subproduct_id=False):
move = self.env['stock.move'].create({ 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, 'name': self.name,
'date': self.date_planned_start, 'date': self.date_planned_start,
'date_expected': self.date_planned_start, 'date_expected': self.date_planned_start,
'picking_type_id': self.picking_type_id.id, '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_id': self.product_id.property_stock_production.id,
'location_dest_id': self.location_dest_id.id, 'location_dest_id': self.location_dest_id.id,
'company_id': self.company_id.id, 'company_id': self.company_id.id,
@@ -481,11 +484,19 @@ class MrpProduction(models.Model):
'group_id': self.procurement_group_id.id, 'group_id': self.procurement_group_id.id,
'propagate': self.propagate, 'propagate': self.propagate,
'move_dest_ids': [(4, x.id) for x in self.move_dest_ids], '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: def _generate_finished_moves(self):
production._create_byproduct_move(sub_product) moves_values = [self._get_finished_move_value(self.product_id.id, self.product_qty, self.product_uom_id.id)]
return move 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): def _get_moves_raw_values(self):
moves = [] moves = []
@@ -556,33 +567,6 @@ class MrpProduction(models.Model):
move = self.env['stock.move'].create(move_values) move = self.env['stock.move'].create(move_values)
return move, 0, quantity 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): def _get_ready_to_produce_state(self):
""" returns 'assigned' if enough components are reserved in order to complete """ returns 'assigned' if enough components are reserved in order to complete
the first operation in the routing. If not returns 'waiting' 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 ?) # assign moves; last operation receive all unassigned moves (which case ?)
moves_raw = self.move_raw_ids.filtered(lambda move: move.operation_id == operation) 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): if len(workorders) == len(bom.routing_id.operation_ids):
moves_raw |= self.move_raw_ids.filtered(lambda move: not move.operation_id) 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_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() workorder._generate_wo_lines()
return workorders return workorders
+38 -41
View File
@@ -81,7 +81,11 @@ class MrpWorkorder(models.Model):
worksheet = fields.Binary( worksheet = fields.Binary(
'Worksheet', related='operation_id.worksheet', readonly=True) 'Worksheet', related='operation_id.worksheet', readonly=True)
move_raw_ids = fields.One2many( 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( move_line_ids = fields.One2many(
'stock.move.line', 'workorder_id', 'Moves to Track', 'stock.move.line', 'workorder_id', 'Moves to Track',
help="Inventory moves for which you must scan a lot number at this work order") 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 = fields.Float(
'Capacity', default=1.0, 'Capacity', default=1.0,
help="Number of pieces that can be produced in parallel.") help="Number of pieces that can be produced in parallel.")
workorder_line_ids = fields.One2many( raw_workorder_line_ids = fields.One2many('mrp.workorder.line',
'mrp.workorder.line', 'raw_workorder_id', string='Components')
'workorder_id', finished_workorder_line_ids = fields.One2many('mrp.workorder.line',
string='Workorder lines', 'finished_workorder_id', string='By-products')
domain=[('is_finished', '=', False)]
)
finished_workorder_line_ids = fields.One2many(
'mrp.workorder.line',
'workorder_id',
string='Final Lot lines',
domain=[('is_finished', '=', True)]
)
allowed_lots_domain = fields.One2many(comodel_name='stock.production.lot', compute="_compute_allowed_lots_domain") allowed_lots_domain = fields.One2many(comodel_name='stock.production.lot', compute="_compute_allowed_lots_domain")
@api.onchange('final_lot_id') @api.onchange('final_lot_id')
@@ -126,7 +122,7 @@ class MrpWorkorder(models.Model):
('next_work_order_id', '=', self.id) ('next_work_order_id', '=', self.id)
]) ])
if previous_wo: 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: if line:
self.qty_producing = line.qty_done self.qty_producing = line.qty_done
@@ -138,7 +134,7 @@ class MrpWorkorder(models.Model):
continue continue
rounding = production.product_uom_id.rounding 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) qties_done_per_lot = defaultdict(list)
for finished_workorder_line in finished_workorder_lines: for finished_workorder_line in finished_workorder_lines:
qties_done_per_lot[finished_workorder_line.lot_id.id].append(finished_workorder_line.qty_done) 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')) workorders = production.workorder_ids.filtered(lambda wo: wo.state not in ('done', 'cancel'))
for workorder in workorders: for workorder in workorders:
if workorder.product_tracking == 'serial': 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: else:
workorder.allowed_lots_domain = allowed_lot_ids workorder.allowed_lots_domain = allowed_lot_ids
@@ -223,17 +219,17 @@ class MrpWorkorder(models.Model):
def _generate_wo_lines(self): def _generate_wo_lines(self):
""" Generate workorder line """ """ Generate workorder line """
self.ensure_one() 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') 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( qty_to_consume = move.product_uom._compute_quantity(
self.qty_producing * move.unit_factor, self.qty_producing * move.unit_factor,
move.product_id.uom_id, move.product_id.uom_id,
round=False round=False
) )
line_values = self._generate_lines_values(move, qty_to_consume) 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): def _apply_update_workorder_lines(self):
""" update existing line on the workorder. It could be trigger manually """ update existing line on the workorder. It could be trigger manually
@@ -241,13 +237,18 @@ class MrpWorkorder(models.Model):
""" """
self.ensure_one() self.ensure_one()
line_values = self._update_workorder_lines() 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']: if line_values['to_delete']:
line_values['to_delete'].unlink() line_values['to_delete'].unlink()
for line, vals in line_values['to_update'].items(): for line, vals in line_values['to_update'].items():
line.write(vals) line.write(vals)
def _refresh_wo_lines(self): 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: for workorder in self:
raw_moves = workorder.move_raw_ids.filtered( raw_moves = workorder.move_raw_ids.filtered(
lambda move: move.state not in ('done', 'cancel') lambda move: move.state not in ('done', 'cancel')
@@ -256,7 +257,7 @@ class MrpWorkorder(models.Model):
for move in raw_moves: for move in raw_moves:
rounding = move.product_uom.rounding rounding = move.product_uom.rounding
qty_already_consumed = 0.0 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: for wl in workorder_lines:
if not wl.qty_done: if not wl.qty_done:
wl_to_unlink |= wl wl_to_unlink |= wl
@@ -271,7 +272,7 @@ class MrpWorkorder(models.Model):
wl_to_unlink.unlink() wl_to_unlink.unlink()
if float_compare(qty_to_consume, qty_already_consumed, precision_rounding=rounding) > 0: 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) 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): def _defaults_from_finished_workorder_line(self, reference_lot_lines):
for r_line in reference_lot_lines: for r_line in reference_lot_lines:
@@ -294,17 +295,6 @@ class MrpWorkorder(models.Model):
return True return True
return False 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 @api.multi
def record_production(self): def record_production(self):
if not self: if not self:
@@ -319,7 +309,7 @@ class MrpWorkorder(models.Model):
self._update_finished_move() self._update_finished_move()
# Transfer quantities from temporary to final move line or make them final # 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 # Transfert lot and quantity produced to a finished workorder line
if self.product_tracking != 'none' and self.final_lot_id: 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 # Update workorder line that regiter final lot created
if not current_lot_lines: if not current_lot_lines:
current_lot_lines = self.env['mrp.workorder.line'].create({ current_lot_lines = self.env['mrp.workorder.line'].create({
'workorder_id': self.id, 'finished_workorder_id': self.id,
'is_finished': True,
'product_id': self.product_id.id, 'product_id': self.product_id.id,
'lot_id': self.final_lot_id.id, 'lot_id': self.final_lot_id.id,
'qty_done': self.qty_producing, 'qty_done': self.qty_producing,
@@ -548,11 +537,19 @@ class MrpWorkorderLine(models.Model):
_inherit = ["mrp.abstract.workorder.line"] _inherit = ["mrp.abstract.workorder.line"]
_description = "Workorder move line" _description = "Workorder move line"
workorder_id = fields.Many2one('mrp.workorder', 'Workorder') raw_workorder_id = fields.Many2one('mrp.workorder', 'Component for Workorder')
is_finished = fields.Boolean('Finished Lot Line', default=False) finished_workorder_id = fields.Many2one('mrp.workorder', 'Finished Product for Workorder')
def _get_final_lot(self): @api.model
return self.workorder_id.final_lot_id 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): def _get_production(self):
return self.workorder_id.production_id return (self.raw_workorder_id or self.finished_workorder_id).production_id
+1 -1
View File
@@ -73,7 +73,7 @@ class StockMove(models.Model):
subproduct_id = fields.Many2one( subproduct_id = fields.Many2one(
'mrp.subproduct', 'Subproduct', 'mrp.subproduct', 'Subproduct',
help="Subproduct line that generated the move in a manufacturing order") 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( is_done = fields.Boolean(
'Done', compute='_compute_is_done', 'Done', compute='_compute_is_done',
store=True, store=True,
+11 -2
View File
@@ -54,6 +54,7 @@ class TestMrpCommon(common2.TestStockCommon):
user_group_stock_user = cls.env.ref('stock.group_stock_user') 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_user = cls.env.ref('mrp.group_mrp_user')
user_group_mrp_manager = cls.env.ref('mrp.group_mrp_manager') 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 # 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({ (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', 'login': 'hilda',
'email': 'h.h@example.com', 'email': 'h.h@example.com',
'notification_type': 'inbox', '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({ cls.user_mrp_manager = Users.create({
'name': 'Gary Youngwomen', 'name': 'Gary Youngwomen',
'login': 'gary', 'login': 'gary',
'email': 'g.g@example.com', 'email': 'g.g@example.com',
'notification_type': 'inbox', '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({ cls.workcenter_1 = cls.env['mrp.workcenter'].create({
'name': 'Nuclear Workcenter', 'name': 'Nuclear Workcenter',
+195 -46
View File
@@ -300,21 +300,21 @@ class TestMrpOrder(TestMrpCommon):
'active_ids': [mo.id], 'active_ids': [mo.id],
})) }))
# change the quantity done in one line # 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 # change the quantity producing
produce_form.qty_producing = 3 produce_form.qty_producing = 3
# check than all quantities are update correctly # check than all quantities are update correctly
line1 = produce_form.workorder_line_ids._records[0] line1 = produce_form.raw_workorder_line_ids._records[0]
line2 = produce_form.workorder_line_ids._records[1] line2 = produce_form.raw_workorder_line_ids._records[1]
self.assertEqual(line1['qty_to_consume'], 3, "Wrong quantity to consume") self.assertEqual(line1['qty_to_consume'], 3, "Wrong quantity to consume")
self.assertEqual(line1['qty_done'], 3, "Wrong quantity done") self.assertEqual(line1['qty_done'], 3, "Wrong quantity done")
self.assertEqual(line2['qty_to_consume'], 12, "Wrong quantity to consume") self.assertEqual(line2['qty_to_consume'], 12, "Wrong quantity to consume")
self.assertEqual(line2['qty_done'], 12, "Wrong quantity done") self.assertEqual(line2['qty_done'], 12, "Wrong quantity done")
product_produce = produce_form.save() 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() product_produce.do_produce()
def test_product_produce_2(self): def test_product_produce_2(self):
@@ -345,12 +345,12 @@ class TestMrpOrder(TestMrpCommon):
'active_ids': [mo.id], '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 produce_form.qty_producing = 1
# get the proposed lot # get the proposed lot
consumed_lots = self.env['stock.production.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: if workorder_line['product_id'] == p1.id:
consumed_lots |= self.env['stock.production.lot'].browse(workorder_line['lot_id']) consumed_lots |= self.env['stock.production.lot'].browse(workorder_line['lot_id'])
consumed_lots.ensure_one() consumed_lots.ensure_one()
@@ -365,8 +365,8 @@ class TestMrpOrder(TestMrpCommon):
'active_ids': [mo.id], 'active_ids': [mo.id],
})) }))
product_produce = produce_form.save() product_produce = produce_form.save()
self.assertEqual(len(product_produce.workorder_line_ids), 2, 'You should have 2 produce lines left.') self.assertEqual(len(product_produce.raw_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): 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.') self.assertEqual(line.lot_id, remaining_lot, 'Wrong lot proposed.')
def test_product_produce_3(self): def test_product_produce_3(self):
@@ -408,15 +408,15 @@ class TestMrpOrder(TestMrpCommon):
'active_ids': [mo.id], 'active_ids': [mo.id],
})) }))
produce_form.qty_producing = 1.0 produce_form.qty_producing = 1.0
for i in range(len(produce_form.workorder_line_ids)): for i in range(len(produce_form.raw_workorder_line_ids)):
with produce_form.workorder_line_ids.edit(i) as line: with produce_form.raw_workorder_line_ids.edit(i) as line:
line.qty_done += 1 line.qty_done += 1
product_produce = produce_form.save() product_produce = produce_form.save()
product_produce.final_lot_id = final_product_lot.id product_produce.final_lot_id = final_product_lot.id
# product 1 lot 1 shelf1 # product 1 lot 1 shelf1
# product 1 lot 1 shelf2 # product 1 lot 1 shelf2
# product 1 lot 2 # 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() product_produce.do_produce()
@@ -538,20 +538,20 @@ class TestMrpOrder(TestMrpCommon):
'active_ids': [mo.id], 'active_ids': [mo.id],
})) }))
produce_form.qty_producing = 3 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(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.workorder_line_ids._records]), 15, '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.workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved 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 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(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.workorder_line_ids._records]), 20, '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.workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved 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 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(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.workorder_line_ids._records]), 5, '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.workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved 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 # 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.product_id = self.product_4
line.qty_done = 1 line.qty_done = 1
produce_wizard = produce_form.save() produce_wizard = produce_form.save()
@@ -583,7 +583,7 @@ class TestMrpOrder(TestMrpCommon):
produce_form.qty_producing = 1 produce_form.qty_producing = 1
produce_wizard = produce_form.save() 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_wizard.do_produce()
produce_form = Form(self.env['mrp.product.produce'].with_context({ produce_form = Form(self.env['mrp.product.produce'].with_context({
@@ -594,7 +594,7 @@ class TestMrpOrder(TestMrpCommon):
produce_wizard = produce_form.save() 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_wizard.do_produce()
mo.button_mark_done() mo.button_mark_done()
@@ -632,9 +632,9 @@ class TestMrpOrder(TestMrpCommon):
})) }))
produce_form.qty_producing = 1 produce_form.qty_producing = 1
produce_wizard = produce_form.save() produce_wizard = produce_form.save()
self.assertEqual(len(produce_wizard.workorder_line_ids), 2) self.assertEqual(len(produce_wizard.raw_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.raw_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(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p2).qty_reserved, 1)
produce_wizard.do_produce() produce_wizard.do_produce()
produce_form = Form(self.env['mrp.product.produce'].with_context({ produce_form = Form(self.env['mrp.product.produce'].with_context({
@@ -646,11 +646,11 @@ class TestMrpOrder(TestMrpCommon):
# p1 1 1 1 # p1 1 1 1
# p1 3 0 3 # p1 3 0 3
# p2 1 1 1 # p2 1 1 1
self.assertEqual(len(produce_wizard.workorder_line_ids), 3) self.assertEqual(len(produce_wizard.raw_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(sum(produce_wizard.raw_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.raw_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.raw_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(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p2).qty_reserved, 1)
with Form(produce_wizard) as produce_form: with Form(produce_wizard) as produce_form:
produce_form.qty_producing = 2 produce_form.qty_producing = 2
@@ -658,11 +658,11 @@ class TestMrpOrder(TestMrpCommon):
# p1 7 0 7 # p1 7 0 7
# p2 1 1 1 # p2 1 1 1
# p2 1 0 1 # p2 1 0 1
self.assertEqual(len(produce_wizard.workorder_line_ids), 4) self.assertEqual(len(produce_wizard.raw_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(sum(produce_wizard.raw_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.raw_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(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.workorder_line_ids.filtered(lambda l: l.product_id == p2).mapped('qty_reserved')), 1) self.assertEqual(sum(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p2).mapped('qty_reserved')), 1)
produce_wizard.do_produce() produce_wizard.do_produce()
@@ -693,7 +693,7 @@ class TestMrpOrder(TestMrpCommon):
with self.assertRaises(UserError): with self.assertRaises(UserError):
# try adding another line for a bom product to increase the quantity # try adding another line for a bom product to increase the quantity
produce_form.qty_producing = 1 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.product_id = p1
line.qty_done = 1 line.qty_done = 1
product_produce = produce_form.save() product_produce = produce_form.save()
@@ -702,7 +702,7 @@ class TestMrpOrder(TestMrpCommon):
with self.assertRaises(UserError): with self.assertRaises(UserError):
# Try updating qty_done # Try updating qty_done
product_produce = produce_form.save() 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() product_produce.do_produce()
with self.assertRaises(UserError): with self.assertRaises(UserError):
@@ -712,7 +712,7 @@ class TestMrpOrder(TestMrpCommon):
'active_ids': [mo.id], 'active_ids': [mo.id],
})) }))
produce_form.qty_producing = 1 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.product_id = self.product_4
line.qty_done = 1 line.qty_done = 1
product_produce = produce_form.save() product_produce = produce_form.save()
@@ -725,13 +725,162 @@ class TestMrpOrder(TestMrpCommon):
})) }))
produce_form.qty_producing = 1 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.product_id = p1
line.qty_done = 1 line.qty_done = 1
product_produce = produce_form.save() 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() 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): def test_product_produce_uom(self):
""" Produce a finished product tracked by serial number. Set another """ 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) 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() product_produce = produce_form.save()
self.assertEqual(product_produce.qty_producing, 1) 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(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.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].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.raw_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.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.workorder_line_ids[1].product_uom_id, unit, 'should be the product uom so "unit"') self.assertEqual(product_produce.raw_workorder_line_ids[1].product_uom_id, unit, 'should be the product uom so "unit"')
+2 -2
View File
@@ -422,7 +422,7 @@ class TestUnbuild(TestMrpCommon):
produce_form.qty_producing = 3.0 produce_form.qty_producing = 3.0
produce_form.final_lot_id = lot_finished_1 produce_form.final_lot_id = lot_finished_1
produce_wizard = produce_form.save() 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() produce_wizard.do_produce()
lot_2 = self.env['stock.production.lot'].create({ lot_2 = self.env['stock.production.lot'].create({
@@ -444,7 +444,7 @@ class TestUnbuild(TestMrpCommon):
produce_form.final_lot_id = lot_finished_2 produce_form.final_lot_id = lot_finished_2
produce_wizard = produce_form.save() 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() produce_wizard.do_produce()
mo.button_mark_done() 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) 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)
+10 -10
View File
@@ -95,7 +95,7 @@ class TestWorkOrderProcess(TestMrpCommon):
finished_lot =self.env['stock.production.lot'].create({'product_id': production_table.product_id.id}) finished_lot =self.env['stock.production.lot'].create({'product_id': production_table.product_id.id})
workorder.write({'final_lot_id': finished_lot.id}) workorder.write({'final_lot_id': finished_lot.id})
workorder.button_start() 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: if workorder_line_id.product_id.id == product_bolt.id:
workorder_line_id.write({'lot_id': lot_bolt.id, 'qty_done': 1}) workorder_line_id.write({'lot_id': lot_bolt.id, 'qty_done': 1})
if workorder_line_id.product_id.id == product_table_sheet.id: 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}) 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].write({'final_lot_id': finished_lot.id, 'qty_producing': 1.0})
workorders[0].button_start() 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') self.assertEqual(workorders[0].state, 'progress')
workorders[0].record_production() workorders[0].record_production()
@@ -211,7 +211,7 @@ class TestWorkOrderProcess(TestMrpCommon):
# --------------------------------------------------------- # ---------------------------------------------------------
workorders[1].button_start() workorders[1].button_start()
workorders[1].qty_producing = 1.0 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() workorders[1].record_production()
move_leg = production_table.move_raw_ids.filtered(lambda p: p.product_id == product_table_leg) move_leg = production_table.move_raw_ids.filtered(lambda p: p.product_id == product_table_leg)
#self.assertEqual(workorders[1].state, 'done') #self.assertEqual(workorders[1].state, 'done')
@@ -222,7 +222,7 @@ class TestWorkOrderProcess(TestMrpCommon):
# --------------------------------------------------------- # ---------------------------------------------------------
workorders[2].button_start() workorders[2].button_start()
workorders[2].qty_producing = 1.0 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_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) move_table_bolt = production_table.move_raw_ids.filtered(lambda p: p.product_id.id == product_bolt.id)
workorders[2].record_production() 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}) laptop_lot_001 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id})
product_form.final_lot_id = laptop_lot_001 product_form.final_lot_id = laptop_lot_001
product_consume = product_form.save() 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() product_consume.do_produce()
# Check consumed move after produce 6 quantity of customized laptop. # 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}) laptop_lot_002 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id})
produce_form.final_lot_id = laptop_lot_002 produce_form.final_lot_id = laptop_lot_002
product_consume = produce_form.save() product_consume = produce_form.save()
self.assertEquals(len(product_consume.workorder_line_ids), 2) self.assertEquals(len(product_consume._workorder_line_ids()), 2)
product_consume.workorder_line_ids[0].qty_done = 8 product_consume._workorder_line_ids()[0].qty_done = 8
product_consume.do_produce() 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') 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') 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 produce_form.final_lot_id = lot_a
product_consume = produce_form.save() product_consume = produce_form.save()
# laptop_lot_002 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id}) # laptop_lot_002 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id})
self.assertEquals(len(product_consume.workorder_line_ids), 2) 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_C).write({'qty_done': 3000})
product_consume.workorder_line_ids.filtered(lambda x: x.product_id == product_B).write({'qty_done': 20}) product_consume._workorder_line_ids().filtered(lambda x: x.product_id == product_B).write({'qty_done': 20})
product_consume.do_produce() product_consume.do_produce()
mo_custom_product.post_inventory() mo_custom_product.post_inventory()
+2
View File
@@ -75,11 +75,13 @@
<field name="product_id"/> <field name="product_id"/>
<field name="product_qty"/> <field name="product_qty"/>
<field name="product_uom_id" groups="uom.group_uom"/> <field name="product_uom_id" groups="uom.group_uom"/>
<field name="operation_id" groups="mrp.group_mrp_routings" domain="[('routing_id', '=', parent.routing_id)]" options="{'no_quick_create':True,'no_create_edit':True}"/>
</tree> </tree>
<form string="Byproducts"> <form string="Byproducts">
<field name="product_id"/> <field name="product_id"/>
<field name="product_qty"/> <field name="product_qty"/>
<field name="product_uom_id" groups="uom.group_uom"/> <field name="product_uom_id" groups="uom.group_uom"/>
<field name="operation_id" groups="mrp.group_mrp_routings" domain="[('routing_id', '=', parent.routing_id)]" options="{'no_quick_create':True,'no_create_edit':True}"/>
</form> </form>
</field> </field>
</page> </page>
+10 -5
View File
@@ -153,10 +153,10 @@
<field name="product_tracking" invisible="1"/> <field name="product_tracking" invisible="1"/>
</group> </group>
</group> </group>
<field name="move_raw_ids" invisible="1"/> <h4 attrs="{'invisible': [('raw_workorder_line_ids', '=', [])]}">Components</h4>
<field name="workorder_line_ids" attrs="{'invisible': [('workorder_line_ids', '=', [])], 'readonly': [('state', 'in', 'cancel', 'done')]}"> <field name="raw_workorder_line_ids" attrs="{'invisible': [('raw_workorder_line_ids', '=', [])], 'readonly': [('state', 'in', 'cancel', 'done')]}">
<tree editable="bottom" create="0" delete="0"> <tree editable="bottom" create="0" delete="0">
<field name="product_id" string="Components"/> <field name="product_id"/>
<field name="product_tracking" invisible="1"/> <field name="product_tracking" invisible="1"/>
<field name="lot_id" attrs="{'readonly': [('product_tracking', '=', 'none')]}" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id, 'active_mo_id': parent.production_id}"/> <field name="lot_id" attrs="{'readonly': [('product_tracking', '=', 'none')]}" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id, 'active_mo_id': parent.production_id}"/>
<field name="qty_to_consume" readonly="1"/> <field name="qty_to_consume" readonly="1"/>
@@ -166,11 +166,16 @@
<field name="move_id" invisible="1"/> <field name="move_id" invisible="1"/>
</tree> </tree>
</field> </field>
<field name="finished_workorder_line_ids" attrs="{'invisible': [('finished_workorder_line_ids', '=', [])]}" readonly="1"> <h4 attrs="{'invisible': [('finished_workorder_line_ids', '=', [])]}">Finished Products</h4>
<field name="finished_workorder_line_ids" attrs="{'invisible': [('finished_workorder_line_ids', '=', [])], 'readonly': [('state', 'in', 'cancel', 'done')]}">
<tree editable="bottom" create="0" delete="0"> <tree editable="bottom" create="0" delete="0">
<field name="product_id"/> <field name="product_id"/>
<field name="lot_id"/> <field name="product_tracking" invisible="1"/>
<field name="lot_id" attrs="{'readonly': [('product_tracking', '=', 'none')]}" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id}"/>
<field name="qty_to_consume" readonly="1" string="To Produce"/>
<field name="qty_done" string="Done"/> <field name="qty_done" string="Done"/>
<field name="product_uom_id" invisible="1"/>
<field name="move_id" invisible="1"/>
</tree> </tree>
</field> </field>
</page> </page>
+14 -23
View File
@@ -27,26 +27,17 @@ class ChangeProductionQty(models.TransientModel):
return res return res
@api.model @api.model
def _update_product_to_produce(self, production, qty, old_qty): def _update_finished_moves(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')) """ Update finished product and its byproducts. This method only update
if production_move: the finished moves not done or cancel and just increase or decrease
production_move.write({'product_uom_qty': qty}) their quantity according the unit_ratio. It does not use the BoM, BoM
else: modification during production would not be taken into consideration.
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) modification = {}
production_move.write({'product_uom_qty': qty}) for move in production.move_finished_ids.filtered(lambda m: m.state not in ('done', 'cancel')):
modification = {production_move: (qty, old_qty)} qty = (qty - old_qty) * move.unit_factor
for sub_product_line in production.bom_id.sub_products: modification[move] = (move.product_uom_qty - qty, move.product_uom_qty)
move = production.move_finished_ids.filtered(lambda x: x.subproduct_id == sub_product_line and x.state not in ('done', 'cancel')) move[0].write({'product_uom_qty': move.product_uom_qty - qty})
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)
return modification return modification
@api.multi @api.multi
@@ -80,7 +71,7 @@ class ChangeProductionQty(models.TransientModel):
for bom, bom_data in boms: for bom, bom_data in boms:
for operation in bom.routing_id.operation_ids: for operation in bom.routing_id.operation_ids:
operation_bom_qty[operation.id] = bom_data['qty'] 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) production._log_downside_manufactured_quantity(finished_moves_modification)
moves = production.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel')) moves = production.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
moves._action_assign() moves._action_assign()
@@ -98,7 +89,7 @@ class ChangeProductionQty(models.TransientModel):
quantity = quantity if (quantity > 0) else 0 quantity = quantity if (quantity > 0) else 0
if float_is_zero(quantity, precision_digits=precision): if float_is_zero(quantity, precision_digits=precision):
wo.final_lot_id = False wo.final_lot_id = False
wo.workorder_line_ids.unlink() wo._workorder_line_ids().unlink()
wo.qty_producing = quantity wo.qty_producing = quantity
if wo.qty_produced < wo.qty_production and wo.state == 'done': if wo.qty_produced < wo.qty_production and wo.state == 'done':
wo.state = 'progress' wo.state = 'progress'
@@ -115,7 +106,7 @@ class ChangeProductionQty(models.TransientModel):
(moves_finished + moves_raw).write({'workorder_id': wo.id}) (moves_finished + moves_raw).write({'workorder_id': wo.id})
if wo.state not in ('done', 'cancel'): if wo.state not in ('done', 'cancel'):
line_values = wo._update_workorder_lines() 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']: if line_values['to_delete']:
line_values['to_delete'].unlink() line_values['to_delete'].unlink()
for line, vals in line_values['to_update'].items(): for line, vals in line_values['to_update'].items():
+45 -23
View File
@@ -42,8 +42,14 @@ class MrpProductProduce(models.TransientModel):
serial = fields.Boolean('Requires Serial') serial = fields.Boolean('Requires Serial')
product_tracking = fields.Selection(related="product_id.tracking") product_tracking = fields.Selection(related="product_id.tracking")
is_pending_production = fields.Boolean(compute='_compute_pending_production') 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_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') @api.depends('qty_producing')
def _compute_pending_production(self): def _compute_pending_production(self):
@@ -98,27 +104,32 @@ class MrpProductProduce(models.TransientModel):
def _record_production(self): def _record_production(self):
# Check all the product_produce line have a move id (the user can add product # Check all the product_produce line have a move id (the user can add product
# to consume directly in the wizard) # 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: if not line.move_id:
order = self.production_id
# Find move_id that would match # Find move_id that would match
move_id = order.move_raw_ids.filtered( if line.raw_product_produce_id:
lambda m: m.product_id == line.product_id and m.state not in ('done', 'cancel') 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: if not move_id:
# create a move to assign it to the line # create a move to assign it to the line
move_id = self.env['stock.move'].create({ if line.raw_product_produce_id:
'name': order.name, values = {
'reference': order.name, 'name': self.production_id.name,
'product_id': line.product_id.id, 'reference': self.production_id.name,
'product_uom': line.product_uom_id.id, 'product_id': line.product_id.id,
'location_id': order.location_src_id.id, 'product_uom': line.product_uom_id.id,
'location_dest_id': line.product_id.property_stock_production.id, 'location_id': self.production_id.location_src_id.id,
'raw_material_production_id': order.id, 'location_dest_id': line.product_id.property_stock_production.id,
'group_id': order.procurement_group_id.id, 'raw_material_production_id': self.production_id.id,
'origin': order.name, 'group_id': self.production_id.procurement_group_id.id,
'state': 'confirmed' '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 line.move_id = move_id.id
# Save product produce lines data into stock moves/move lines # 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: 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) raise UserError(_("The production order for '%s' has no quantity specified.") % self.product_id.display_name)
self._update_finished_move() self._update_finished_move()
self._update_raw_moves() self._update_moves()
if self.production_id.state == 'confirmed': if self.production_id.state == 'confirmed':
self.production_id.write({ self.production_id.write({
'date_start': datetime.now(), 'date_start': datetime.now(),
@@ -138,10 +149,21 @@ class MrpProductProduceLine(models.TransientModel):
_inherit = ["mrp.abstract.workorder.line"] _inherit = ["mrp.abstract.workorder.line"]
_description = "Record production 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): @api.model
return self.product_produce_id.final_lot_id 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): 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
@@ -25,8 +25,9 @@
</div> </div>
</group> </group>
</group> </group>
<h4 attrs="{'invisible': [('raw_workorder_line_ids', '=', [])]}">Components</h4>
<group> <group>
<field name="workorder_line_ids" attrs="{'invisible': [('workorder_line_ids', '=', [])]}" nolabel="1" context="{'w_production': True, 'active_id': production_id, 'default_final_lot_id': final_lot_id}"> <field name="raw_workorder_line_ids" attrs="{'invisible': [('raw_workorder_line_ids', '=', [])]}" nolabel="1" context="{'w_production': True, 'active_id': production_id, 'default_final_lot_id': final_lot_id}">
<tree editable="top" delete="0" decoration-danger="(qty_to_consume &lt; qty_done)"> <tree editable="top" delete="0" decoration-danger="(qty_to_consume &lt; qty_done)">
<field name="product_id" attrs="{'readonly': [('move_id', '!=', False)]}" required="1" domain="[('id', '!=', parent.product_id)]" force_save="1"/> <field name="product_id" attrs="{'readonly': [('move_id', '!=', False)]}" required="1" domain="[('id', '!=', parent.product_id)]" force_save="1"/>
<field name="product_tracking" invisible="1"/> <field name="product_tracking" invisible="1"/>
@@ -39,6 +40,20 @@
</tree> </tree>
</field> </field>
</group> </group>
<h4 attrs="{'invisible': [('finished_workorder_line_ids', '=', [])]}">By-products</h4>
<group>
<field name="finished_workorder_line_ids" attrs="{'invisible': [('finished_workorder_line_ids', '=', [])]}" nolabel="1" context="{'w_production': True, 'active_id': production_id, 'default_final_lot_id': final_lot_id}">
<tree editable="top" delete="0" decoration-danger="(qty_to_consume &lt; qty_done)">
<field name="product_id" attrs="{'readonly': [('move_id', '!=', False)]}" required="1" domain="[('id', '!=', parent.product_id)]" force_save="1"/>
<field name="product_tracking" invisible="1"/>
<field name="lot_id" attrs="{'readonly': [('product_tracking', '=', 'none')]}" context="{'default_product_id': product_id}" domain="[('product_id', '=', product_id)]" groups="stock.group_production_lot"/>
<field name="qty_to_consume" string="To Produce" readonly="1" force_save="1"/>
<field name="qty_done" string="Produced"/>
<field name="product_uom_id" readonly="1" force_save="1" groups="uom.group_uom"/>
<field name="move_id" invisible="1"/>
</tree>
</field>
</group>
<footer> <footer>
<button name="continue_production" attrs="{'invisible':[('is_pending_production', '=', False)]}" type="object" string="Continue" class="btn-primary"/> <button name="continue_production" attrs="{'invisible':[('is_pending_production', '=', False)]}" type="object" string="Continue" class="btn-primary"/>
<button name="do_produce" type="object" string="Save" class="btn-default btn-secondary" attrs="{'invisible':[('is_pending_production', '=', False)]}"/> <button name="do_produce" type="object" string="Save" class="btn-default btn-secondary" attrs="{'invisible':[('is_pending_production', '=', False)]}"/>
@@ -502,7 +502,7 @@ class TestSubcontractingTracking(TransactionCase):
'active_ids': [mo.id], 'active_ids': [mo.id],
})) }))
produce_form.final_lot_id = lot_id produce_form.final_lot_id = lot_id
produce_form.workorder_line_ids._records[0]['lot_id'] = serial_id.id produce_form.raw_workorder_line_ids._records[0]['lot_id'] = serial_id.id
wiz_produce = produce_form.save() wiz_produce = produce_form.save()
wiz_produce.do_produce() wiz_produce.do_produce()