[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">
<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>
+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")
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)
+24 -39
View File
@@ -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
+38 -41
View File
@@ -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
+1 -1
View File
@@ -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,
+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_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
View File
@@ -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"')
+2 -2
View File
@@ -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)
+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})
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()
+2
View File
@@ -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>
+10 -5
View File
@@ -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>
+14 -23
View File
@@ -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():
+45 -23
View File
@@ -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 &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"/>
@@ -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 &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>
<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()