[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:
@@ -706,7 +706,7 @@
|
||||
<function model="mrp.product.produce.line" name="create">
|
||||
<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']]"/>
|
||||
</function>
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
+195
-46
@@ -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"')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -75,11 +75,13 @@
|
||||
<field name="product_id"/>
|
||||
<field name="product_qty"/>
|
||||
<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>
|
||||
<form string="Byproducts">
|
||||
<field name="product_id"/>
|
||||
<field name="product_qty"/>
|
||||
<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>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
@@ -153,10 +153,10 @@
|
||||
<field name="product_tracking" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<field name="move_raw_ids" invisible="1"/>
|
||||
<field name="workorder_line_ids" attrs="{'invisible': [('workorder_line_ids', '=', [])], 'readonly': [('state', 'in', 'cancel', 'done')]}">
|
||||
<h4 attrs="{'invisible': [('raw_workorder_line_ids', '=', [])]}">Components</h4>
|
||||
<field name="raw_workorder_line_ids" attrs="{'invisible': [('raw_workorder_line_ids', '=', [])], 'readonly': [('state', 'in', 'cancel', 'done')]}">
|
||||
<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="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"/>
|
||||
@@ -166,11 +166,16 @@
|
||||
<field name="move_id" invisible="1"/>
|
||||
</tree>
|
||||
</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">
|
||||
<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="product_uom_id" invisible="1"/>
|
||||
<field name="move_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,8 +25,9 @@
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<h4 attrs="{'invisible': [('raw_workorder_line_ids', '=', [])]}">Components</h4>
|
||||
<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 < 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"/>
|
||||
@@ -39,6 +40,20 @@
|
||||
</tree>
|
||||
</field>
|
||||
</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 < 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>
|
||||
<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)]}"/>
|
||||
|
||||
@@ -502,7 +502,7 @@ class TestSubcontractingTracking(TransactionCase):
|
||||
'active_ids': [mo.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.do_produce()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user