diff --git a/addons/mrp/models/mrp_abstract_workorder.py b/addons/mrp/models/mrp_abstract_workorder.py index 07fb61a4336..3e535d3d2a6 100644 --- a/addons/mrp/models/mrp_abstract_workorder.py +++ b/addons/mrp/models/mrp_abstract_workorder.py @@ -277,6 +277,10 @@ class MrpAbstractWorkorder(models.AbstractModel): """ Once the production is done. Modify the workorder lines into stock move line with the registered lot and quantity done. """ + # add missing move for extra component/byproduct + for line in self._workorder_line_ids(): + if not line.move_id: + line._set_move_id() # Before writting produce quantities, we ensure they respect the bom strictness self._strict_consumption_check() vals_list = [] @@ -500,6 +504,29 @@ class MrpAbstractWorkorderLine(models.AbstractModel): if duplicates: raise UserError(message) + def _set_move_id(self): + """ Check the line has a stock_move on which on the quantity will be + transfered at the end of the production """ + self.ensure_one() + # Find move_id that would match + mo = self._get_production() + if self[self._get_raw_workorder_inverse_name()]: + moves = mo.move_raw_ids + else: + moves = mo.move_finished_ids + move_id = moves.filtered(lambda m: m.product_id == self.product_id and m.state not in ('done', 'cancel')) + if not move_id: + # create a move to assign it to the line + if self[self._get_raw_workorder_inverse_name()]: + values = mo._get_move_raw_values(self.product_id, self.qty_done, self.product_uom_id) + elif self.product_id != mo.product_id: + values = mo._get_finished_move_value(self.product_id.id, self.qty_done, self.product_uom_id.id) + else: + # The line is neither a component nor a byproduct + return + move_id = self.env['stock.move'].create(values) + self.move_id = move_id.id + def _unreserve_order(self): """ Unreserve line with lower reserved quantity first """ self.ensure_one() diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 1acbe32a3dc..e7547f22e34 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -574,31 +574,35 @@ class MrpProduction(models.Model): if bom_line.child_bom_id and bom_line.child_bom_id.type == 'phantom' or\ bom_line.product_id.type not in ['product', 'consu']: continue - moves.append(production._get_move_raw_values(bom_line, line_data)) + operation = bom_line.operation_id.id or line_data['parent_line'] and line_data['parent_line'].operation_id.id + moves.append(production._get_move_raw_values( + bom_line.product_id, + line_data['qty'], + bom_line.product_uom_id, + operation, + bom_line + )) return moves - def _get_move_raw_values(self, bom_line, line_data): - quantity = line_data['qty'] - # alt_op needed for the case when you explode phantom bom and all the lines will be consumed in the operation given by the parent bom line - alt_op = line_data['parent_line'] and line_data['parent_line'].operation_id.id or False + def _get_move_raw_values(self, product_id, product_uom_qty, product_uom, operation_id=False, bom_line=False): source_location = self.location_src_id data = { - 'sequence': bom_line.sequence, + 'sequence': bom_line.sequence if bom_line else 10, 'name': self.name, 'reference': self.name, 'date': self.date_planned_start, 'date_expected': self.date_planned_start, - 'bom_line_id': bom_line.id, + 'bom_line_id': bom_line.id if bom_line else False, 'picking_type_id': self.picking_type_id.id, - 'product_id': bom_line.product_id.id, - 'product_uom_qty': quantity, - 'product_uom': bom_line.product_uom_id.id, + 'product_id': product_id.id, + 'product_uom_qty': product_uom_qty, + 'product_uom': product_uom.id, 'location_id': source_location.id, 'location_dest_id': self.product_id.with_company(self.company_id).property_stock_production.id, 'raw_material_production_id': self.id, 'company_id': self.company_id.id, - 'operation_id': bom_line.operation_id.id or alt_op, - 'price_unit': bom_line.product_id.standard_price, + 'operation_id': operation_id, + 'price_unit': product_id.standard_price, 'procure_method': 'make_to_stock', 'origin': self.name, 'state': 'draft', @@ -630,7 +634,14 @@ class MrpProduction(models.Model): move[0].unlink() return self.env['stock.move'], old_qty, quantity else: - move_values = self._get_move_raw_values(bom_line, line_data) + operation = bom_line.operation_id.id or line_data['parent_line'] and line_data['parent_line'].operation_id.id + move_values = self._get_move_raw_values( + bom_line.product_id, + line_data['qty'], + bom_line.product_uom_id, + operation, + bom_line + ) move = self.env['stock.move'].create(move_values) return move, 0, quantity diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index af018139036..b2fac788733 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -143,6 +143,7 @@ class MrpWorkorder(models.Model): 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") + is_finished_lines_editable = fields.Boolean(compute='_compute_is_finished_lines_editable') # Both `date_planned_start` and `date_planned_finished` are related fields on `leave_id`. Let's say # we slide a workorder on a gantt view, a single call to write is made with both @@ -164,6 +165,14 @@ class MrpWorkorder(models.Model): 'date_to': date_to, }) + @api.depends('state') + def _compute_is_finished_lines_editable(self): + for workorder in self: + if self.user_has_groups('mrp.group_mrp_byproducts') and workorder.state not in ('cancel', 'done'): + workorder.is_finished_lines_editable = True + else: + workorder.is_finished_lines_editable = False + @api.onchange('finished_lot_id') def _onchange_finished_lot_id(self): """When the user changes the lot being currently produced, suggest @@ -663,6 +672,18 @@ class MrpWorkorderLine(models.Model): finished_workorder_id = fields.Many2one('mrp.workorder', 'Finished Product for Workorder', ondelete='cascade') + @api.model_create_multi + def create(self, vals_list): + res = super().create(vals_list) + for line in res: + wo = line.raw_workorder_id + if wo and\ + wo.consumption == 'strict' and\ + wo.state == 'progress' and\ + line.product_id.id not in wo.production_id.bom_id.bom_line_ids.product_id.ids: + raise UserError(_('You cannot consume additional component as the consumption defined on the Bill of Material is set to "strict"')) + return res + @api.model def _get_raw_workorder_inverse_name(self): return 'raw_workorder_id' diff --git a/addons/mrp/views/mrp_workorder_views.xml b/addons/mrp/views/mrp_workorder_views.xml index f63282751d1..da281ed27cf 100644 --- a/addons/mrp/views/mrp_workorder_views.xml +++ b/addons/mrp/views/mrp_workorder_views.xml @@ -105,6 +105,7 @@ +