[IMP] mrp: add components in workorders
This commit extend the flexible consumption. On the workorder form view. It's now possible to add directly new workorder line with any product (even one not in any BoM line). For those new wo line. A confirmed stock move is create in the record_production step. Task : 2082448
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<field name="working_state" invisible="1"/>
|
||||
<field name="production_state" invisible="1"/>
|
||||
<field name="allowed_lots_domain" invisible="1"/>
|
||||
<field name="is_finished_lines_editable" invisible="1"/>
|
||||
<header>
|
||||
<button name="button_finish" type="object" string="Finish Order" attrs="{'invisible': ['|', ('state', '!=', 'progress'), ('is_produced', '=', False)]}" class="btn-info"/>
|
||||
<button name="button_start" type="object" string="Start Working" attrs="{'invisible': ['|', ('working_state', '=', 'blocked'), ('state', '!=', 'pending')]}"/>
|
||||
@@ -152,11 +153,12 @@
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="finished_lot_id" context="{'default_product_id': product_id, 'default_company_id': company_id}" attrs="{'invisible': [('product_tracking', '=', 'none')]}" groups="stock.group_production_lot"/>
|
||||
<field name="product_tracking" invisible="1"/>
|
||||
<field name="consumption" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<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">
|
||||
<h4 attrs="{'invisible': ['&', ('consumption', '=', 'strict'), ('raw_workorder_line_ids', '=', [])]}">Components</h4>
|
||||
<field name="raw_workorder_line_ids" attrs="{'invisible': ['&', ('consumption', '=', 'strict'), ('raw_workorder_line_ids', '=', [])], 'readonly': [('state', 'in', ('cancel', 'done'))]}">
|
||||
<tree editable="bottom" create="1" delete="0">
|
||||
<field name="product_id"/>
|
||||
<field name="product_tracking" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
@@ -168,9 +170,9 @@
|
||||
<field name="move_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
<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">
|
||||
<h4>Finished Products</h4>
|
||||
<field name="finished_workorder_line_ids" attrs="{'readonly': [('is_finished_lines_editable', '=', False)]}">
|
||||
<tree editable="bottom" create="1" delete="0">
|
||||
<field name="product_id"/>
|
||||
<field name="product_tracking" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
|
||||
@@ -109,34 +109,6 @@ class MrpProductProduce(models.TransientModel):
|
||||
# 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():
|
||||
if not line.move_id:
|
||||
# Find move_id that would match
|
||||
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
|
||||
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',
|
||||
'company_id': self.production_id.company_id.id,
|
||||
}
|
||||
else:
|
||||
values = self.production_id._get_finished_move_value(line.product_id.id, 0, line.product_uom_id.id)
|
||||
move_id = self.env['stock.move'].create(values)
|
||||
line.move_id = move_id.id
|
||||
|
||||
line._check_line_sn_uniqueness()
|
||||
# because of an ORM limitation (fields on transient models are not
|
||||
# recomputed by updates in non-transient models), the related fields on
|
||||
|
||||
Reference in New Issue
Block a user