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

Components

+ - + @@ -166,11 +166,16 @@ - +

Finished Products

+ - + + + + + diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py index 3a0bcc7df7c..01a3807a70d 100644 --- a/addons/mrp/wizard/change_production_qty.py +++ b/addons/mrp/wizard/change_production_qty.py @@ -27,26 +27,17 @@ class ChangeProductionQty(models.TransientModel): return res @api.model - def _update_product_to_produce(self, production, qty, old_qty): - production_move = production.move_finished_ids.filtered(lambda x: x.product_id.id == production.product_id.id and x.state not in ('done', 'cancel')) - if production_move: - production_move.write({'product_uom_qty': qty}) - else: - production_move = production._generate_finished_moves() - production_move = production.move_finished_ids.filtered(lambda x: x.state not in ('done', 'cancel') and production.product_id.id == x.product_id.id) - production_move.write({'product_uom_qty': qty}) - modification = {production_move: (qty, old_qty)} - for sub_product_line in production.bom_id.sub_products: - move = production.move_finished_ids.filtered(lambda x: x.subproduct_id == sub_product_line and x.state not in ('done', 'cancel')) - if move: - product_uom_factor = production.product_uom_id._compute_quantity(production.product_qty - production.qty_produced, production.bom_id.product_uom_id) - qty1 = sub_product_line.product_qty - qty1 *= product_uom_factor / production.bom_id.product_qty - modification[move[0]] = (qty1, move[0].product_uom_qty) - move[0].write({'product_uom_qty': qty1}) - else: - move = production._create_byproduct_move(sub_product_line) - modification[move] = (move.product_uom_qty, 0) + def _update_finished_moves(self, production, qty, old_qty): + """ Update finished product and its byproducts. This method only update + the finished moves not done or cancel and just increase or decrease + their quantity according the unit_ratio. It does not use the BoM, BoM + modification during production would not be taken into consideration. + """ + modification = {} + for move in production.move_finished_ids.filtered(lambda m: m.state not in ('done', 'cancel')): + qty = (qty - old_qty) * move.unit_factor + modification[move] = (move.product_uom_qty - qty, move.product_uom_qty) + move[0].write({'product_uom_qty': move.product_uom_qty - qty}) return modification @api.multi @@ -80,7 +71,7 @@ class ChangeProductionQty(models.TransientModel): for bom, bom_data in boms: for operation in bom.routing_id.operation_ids: operation_bom_qty[operation.id] = bom_data['qty'] - finished_moves_modification = self._update_product_to_produce(production, production.product_qty - qty_produced, old_production_qty) + finished_moves_modification = self._update_finished_moves(production, production.product_qty - qty_produced, old_production_qty) production._log_downside_manufactured_quantity(finished_moves_modification) moves = production.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel')) moves._action_assign() @@ -98,7 +89,7 @@ class ChangeProductionQty(models.TransientModel): quantity = quantity if (quantity > 0) else 0 if float_is_zero(quantity, precision_digits=precision): wo.final_lot_id = False - wo.workorder_line_ids.unlink() + wo._workorder_line_ids().unlink() wo.qty_producing = quantity if wo.qty_produced < wo.qty_production and wo.state == 'done': wo.state = 'progress' @@ -115,7 +106,7 @@ class ChangeProductionQty(models.TransientModel): (moves_finished + moves_raw).write({'workorder_id': wo.id}) if wo.state not in ('done', 'cancel'): line_values = wo._update_workorder_lines() - wo.workorder_line_ids |= wo.workorder_line_ids.create(line_values['to_create']) + self._workorder_line_ids().create(line_values['to_create']) if line_values['to_delete']: line_values['to_delete'].unlink() for line, vals in line_values['to_update'].items(): diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py index 01ef3cea857..038666e9618 100644 --- a/addons/mrp/wizard/mrp_product_produce.py +++ b/addons/mrp/wizard/mrp_product_produce.py @@ -42,8 +42,14 @@ class MrpProductProduce(models.TransientModel): serial = fields.Boolean('Requires Serial') product_tracking = fields.Selection(related="product_id.tracking") is_pending_production = fields.Boolean(compute='_compute_pending_production') - workorder_line_ids = fields.One2many('mrp.product.produce.line', 'product_produce_id') + move_raw_ids = fields.One2many(related='production_id.move_raw_ids') + move_finished_ids = fields.One2many(related='production_id.move_finished_ids') + + raw_workorder_line_ids = fields.One2many('mrp.product.produce.line', + 'raw_product_produce_id', string='Components') + finished_workorder_line_ids = fields.One2many('mrp.product.produce.line', + 'finished_product_produce_id', string='By-products') @api.depends('qty_producing') def _compute_pending_production(self): @@ -98,27 +104,32 @@ class MrpProductProduce(models.TransientModel): def _record_production(self): # Check all the product_produce line have a move id (the user can add product # to consume directly in the wizard) - for line in self.workorder_line_ids: + for line in self._workorder_line_ids(): if not line.move_id: - order = self.production_id # Find move_id that would match - move_id = order.move_raw_ids.filtered( - lambda m: m.product_id == line.product_id and m.state not in ('done', 'cancel') - ) + if line.raw_product_produce_id: + moves = self.move_raw_ids + else: + moves = self.move_finished_ids + move_id = moves.filtered(lambda m: m.product_id == line.product_id and m.state not in ('done', 'cancel')) if not move_id: # create a move to assign it to the line - move_id = self.env['stock.move'].create({ - 'name': order.name, - 'reference': order.name, - 'product_id': line.product_id.id, - 'product_uom': line.product_uom_id.id, - 'location_id': order.location_src_id.id, - 'location_dest_id': line.product_id.property_stock_production.id, - 'raw_material_production_id': order.id, - 'group_id': order.procurement_group_id.id, - 'origin': order.name, - 'state': 'confirmed' - }) + if line.raw_product_produce_id: + values = { + 'name': self.production_id.name, + 'reference': self.production_id.name, + 'product_id': line.product_id.id, + 'product_uom': line.product_uom_id.id, + 'location_id': self.production_id.location_src_id.id, + 'location_dest_id': line.product_id.property_stock_production.id, + 'raw_material_production_id': self.production_id.id, + 'group_id': self.production_id.procurement_group_id.id, + 'origin': self.production_id.name, + 'state': 'confirmed' + } + else: + values = self.production_id._get_finished_move_value(line.product_id.id, 0, line.product_uom_id.id) + move_id = self.env['stock.move'].create(values) line.move_id = move_id.id # Save product produce lines data into stock moves/move lines @@ -126,7 +137,7 @@ class MrpProductProduce(models.TransientModel): if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0: raise UserError(_("The production order for '%s' has no quantity specified.") % self.product_id.display_name) self._update_finished_move() - self._update_raw_moves() + self._update_moves() if self.production_id.state == 'confirmed': self.production_id.write({ 'date_start': datetime.now(), @@ -138,10 +149,21 @@ class MrpProductProduceLine(models.TransientModel): _inherit = ["mrp.abstract.workorder.line"] _description = "Record production line" - product_produce_id = fields.Many2one('mrp.product.produce', 'Produce wizard') + raw_product_produce_id = fields.Many2one('mrp.product.produce', 'Component in Produce wizard') + finished_product_produce_id = fields.Many2one('mrp.product.produce', 'Finished Product in Produce wizard') - def _get_final_lot(self): - return self.product_produce_id.final_lot_id + @api.model + def _get_raw_workorder_inverse_name(self): + return 'raw_product_produce_id' + + @api.model + def _get_finished_workoder_inverse_name(self): + return 'finished_product_produce_id' + + def _get_final_lots(self): + product_produce_id = self.raw_product_produce_id or self.finished_product_produce_id + return product_produce_id.final_lot_id | product_produce_id.finished_workorder_line_ids.mapped('lot_id') def _get_production(self): - return self.product_produce_id.production_id + product_produce_id = self.raw_product_produce_id or self.finished_product_produce_id + return product_produce_id.production_id diff --git a/addons/mrp/wizard/mrp_product_produce_views.xml b/addons/mrp/wizard/mrp_product_produce_views.xml index 3864c6384a4..26307620ad3 100644 --- a/addons/mrp/wizard/mrp_product_produce_views.xml +++ b/addons/mrp/wizard/mrp_product_produce_views.xml @@ -25,8 +25,9 @@ +

Components

- + @@ -39,6 +40,20 @@ +

By-products

+ + + + + + + + + + + + +