[REF] mrp: byproduct onchange
Create the byproduct moves at the onchange. task-2241471
This commit is contained in:
committed by
Simon Lejeune
parent
ed7012e8fd
commit
556a905166
@@ -625,6 +625,10 @@
|
||||
<value model="stock.move" eval="obj().env.ref('mrp.mrp_production_laptop_cust')._get_moves_raw_values()"/>
|
||||
</function>
|
||||
|
||||
<function model="stock.move" name="create">
|
||||
<value model="stock.move" eval="obj().env.ref('mrp.mrp_production_laptop_cust')._get_moves_finished_values()"/>
|
||||
</function>
|
||||
|
||||
<!-- Run Scheduler -->
|
||||
<function model="procurement.group" name="run_scheduler"/>
|
||||
|
||||
|
||||
@@ -521,7 +521,7 @@ class MrpAbstractWorkorderLine(models.AbstractModel):
|
||||
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)
|
||||
values = mo._get_move_finished_values(self.product_id.id, self.qty_done, self.product_uom_id.id)
|
||||
else:
|
||||
# The line is neither a component nor a byproduct
|
||||
return
|
||||
|
||||
@@ -494,7 +494,8 @@ class MrpProduction(models.Model):
|
||||
order.move_byproduct_ids = order.move_finished_ids.filtered(lambda m: m.product_id != order.product_id)
|
||||
|
||||
def _set_move_byproduct_ids(self):
|
||||
self.move_finished_ids |= self.move_byproduct_ids
|
||||
move_finished_ids = self.move_finished_ids.filtered(lambda m: m.product_id == self.product_id)
|
||||
self.move_finished_ids = move_finished_ids | self.move_byproduct_ids
|
||||
|
||||
@api.depends('state')
|
||||
def _compute_show_lock(self):
|
||||
@@ -558,8 +559,14 @@ class MrpProduction(models.Model):
|
||||
|
||||
@api.onchange('date_planned_start')
|
||||
def _onchange_date_planned_start(self):
|
||||
if not self.workorder_ids:
|
||||
self.date_planned_finished = self.date_planned_start + datetime.timedelta(hours=1)
|
||||
if not self.is_planned:
|
||||
date_planned_finished = self.date_planned_start + relativedelta(days=self.product_id.produce_delay)
|
||||
date_planned_finished = date_planned_finished + relativedelta(days=self.company_id.manufacturing_lead)
|
||||
if date_planned_finished == self.date_planned_start:
|
||||
date_planned_finished = date_planned_finished + relativedelta(hours=1)
|
||||
self.date_planned_finished = date_planned_finished
|
||||
self.move_raw_ids = [(1, m.id, {'date_expected': self.date_planned_start, 'date': self.date_planned_start}) for m in self.move_raw_ids]
|
||||
self.move_finished_ids = [(1, m.id, {'date_expected': date_planned_finished, 'date': date_planned_finished}) for m in self.move_finished_ids]
|
||||
|
||||
@api.onchange('bom_id', 'product_id', 'product_qty', 'product_uom_id')
|
||||
def _onchange_move_raw(self):
|
||||
@@ -579,6 +586,28 @@ class MrpProduction(models.Model):
|
||||
else:
|
||||
self.move_raw_ids = [(2, move.id) for move in self.move_raw_ids.filtered(lambda m: m.bom_line_id)]
|
||||
|
||||
@api.onchange('bom_id', 'product_id', 'product_qty', 'product_uom_id')
|
||||
def _onchange_move_finished(self):
|
||||
if self.product_id and self.product_qty > 0:
|
||||
# keep manual entries
|
||||
list_move_finished = [(4, move.id) for move in self.move_finished_ids.filtered(
|
||||
lambda m: not m.byproduct_id and m.product_id != self.product_id)]
|
||||
moves_finished_values = self._get_moves_finished_values()
|
||||
moves_byproduct_dict = {move.byproduct_id.id: move for move in self.move_finished_ids.filtered(lambda m: m.byproduct_id)}
|
||||
move_finished = self.move_finished_ids.filtered(lambda m: m.product_id == self.product_id)
|
||||
for move_finished_values in moves_finished_values:
|
||||
if move_finished_values.get('byproduct_id') in moves_byproduct_dict:
|
||||
# update existing entries
|
||||
list_move_finished += [(1, moves_byproduct_dict[move_finished_values['byproduct_id']].id, move_finished_values)]
|
||||
elif move_finished_values.get('product_id') == self.product_id.id and move_finished:
|
||||
list_move_finished += [(1, move_finished.id, move_finished_values)]
|
||||
else:
|
||||
# add new entries
|
||||
list_move_finished += [(0, 0, move_finished_values)]
|
||||
self.move_finished_ids = list_move_finished
|
||||
else:
|
||||
self.move_finished_ids = [(2, move.id) for move in self.move_finished_ids.filtered(lambda m: m.bom_line_id)]
|
||||
|
||||
@api.onchange('location_src_id', 'move_raw_ids', 'bom_id')
|
||||
def _onchange_location(self):
|
||||
source_location = self.location_src_id
|
||||
@@ -587,6 +616,17 @@ class MrpProduction(models.Model):
|
||||
'location_id': source_location.id,
|
||||
})
|
||||
|
||||
@api.onchange('location_dest_id', 'move_finished_ids', 'bom_id')
|
||||
def _onchange_location_dest(self):
|
||||
destination_location = self.location_dest_id
|
||||
update_value_list = []
|
||||
for move in self.move_finished_ids:
|
||||
update_value_list += [(1, move.id, ({
|
||||
'warehouse_id': destination_location.get_warehouse().id,
|
||||
'location_dest_id': destination_location.id,
|
||||
}))]
|
||||
self.move_finished_ids = update_value_list
|
||||
|
||||
@api.onchange('picking_type_id')
|
||||
def onchange_picking_type(self):
|
||||
location = self.env.ref('stock.stock_location_stock')
|
||||
@@ -595,6 +635,7 @@ class MrpProduction(models.Model):
|
||||
except (AttributeError, AccessError):
|
||||
location = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1).lot_stock_id
|
||||
self.move_raw_ids.update({'picking_type_id': self.picking_type_id})
|
||||
self.move_finished_ids.update({'picking_type_id': self.picking_type_id})
|
||||
self.location_src_id = self.picking_type_id.default_location_src_id.id or location.id
|
||||
self.location_dest_id = self.picking_type_id.default_location_dest_id.id or location.id
|
||||
|
||||
@@ -608,6 +649,8 @@ class MrpProduction(models.Model):
|
||||
self._create_workorder()
|
||||
|
||||
def write(self, vals):
|
||||
if 'workorder_ids' in self:
|
||||
production_to_replan = self.filtered(lambda p: p.is_planned)
|
||||
res = super(MrpProduction, self).write(vals)
|
||||
|
||||
for production in self:
|
||||
@@ -616,7 +659,7 @@ class MrpProduction(models.Model):
|
||||
raise UserError(_('You cannot move a manufacturing order once it is cancelled or done.'))
|
||||
if production.is_partially_planned:
|
||||
raise UserError(_('You cannot move a manufacturing order once it has a planned workorder, move related workorder(s) instead.'))
|
||||
if ('move_raw_ids' in vals or 'move_finished_ids' in vals) and production.state != 'draft':
|
||||
if any(field in ['move_raw_ids', 'move_finished_ids', 'workorder_ids'] for field in vals) and production.state != 'draft':
|
||||
if production.state == 'done':
|
||||
# for some reason moves added after state = 'done' won't save group_id, reference if added in
|
||||
# "stock_move.default_get()"
|
||||
@@ -630,6 +673,8 @@ class MrpProduction(models.Model):
|
||||
'date_expected': production.date_planned_finished,
|
||||
})
|
||||
production._autoconfirm_production()
|
||||
if production in production_to_replan:
|
||||
production._plan_workorders(replan=True)
|
||||
if production.state == 'done' and ('lot_producing_id' in vals or 'qty_producing' in vals):
|
||||
finished_move_lines = production.move_finished_ids.filtered(
|
||||
lambda move: move.product_id == self.product_id and move.state == 'done').mapped('move_line_ids')
|
||||
@@ -637,6 +682,7 @@ class MrpProduction(models.Model):
|
||||
finished_move_lines.write({'lot_id': vals.get('lot_producing_id')})
|
||||
if 'qty_producing' in vals:
|
||||
finished_move_lines.write({'qty_done': vals.get('qty_producing')})
|
||||
|
||||
if not production.bom_id.operation_ids and vals.get('date_planned_start') and not vals.get('date_planned_finished'):
|
||||
new_date_planned_start = fields.Datetime.to_datetime(vals.get('date_planned_start'))
|
||||
if not production.date_planned_finished or new_date_planned_start >= production.date_planned_finished:
|
||||
@@ -656,12 +702,13 @@ class MrpProduction(models.Model):
|
||||
procurement_group_vals = self._prepare_procurement_group_vals(values)
|
||||
values['procurement_group_id'] = self.env["procurement.group"].create(procurement_group_vals).id
|
||||
production = super(MrpProduction, self).create(values)
|
||||
production.move_raw_ids.write({
|
||||
(production.move_raw_ids | production.move_finished_ids).write({
|
||||
'group_id': production.procurement_group_id.id,
|
||||
})
|
||||
# Trigger move_raw creation when importing a file
|
||||
if 'import_file' in self.env.context:
|
||||
production._onchange_move_raw()
|
||||
production._onchange_move_finished()
|
||||
return production
|
||||
|
||||
def unlink(self):
|
||||
@@ -710,7 +757,7 @@ class MrpProduction(models.Model):
|
||||
for workorder in production.workorder_ids:
|
||||
workorder.duration_expected = workorder._get_duration_expected()
|
||||
|
||||
def _get_finished_move_value(self, product_id, product_uom_qty, product_uom, operation_id=False, byproduct_id=False):
|
||||
def _get_move_finished_values(self, product_id, product_uom_qty, product_uom, operation_id=False, byproduct_id=False):
|
||||
group_orders = self.procurement_group_id.mrp_production_ids
|
||||
move_dest_ids = self.move_dest_ids
|
||||
if len(group_orders) > 1:
|
||||
@@ -726,7 +773,7 @@ class MrpProduction(models.Model):
|
||||
'operation_id': operation_id,
|
||||
'byproduct_id': byproduct_id,
|
||||
'name': self.name,
|
||||
'date': self.date_planned_start,
|
||||
'date': date_planned_finished,
|
||||
'date_expected': date_planned_finished,
|
||||
'picking_type_id': self.picking_type_id.id,
|
||||
'location_id': self.product_id.with_company(self.company_id).property_stock_production.id,
|
||||
@@ -743,18 +790,18 @@ class MrpProduction(models.Model):
|
||||
'move_dest_ids': [(4, x.id) for x in move_dest_ids],
|
||||
}
|
||||
|
||||
def _generate_finished_moves(self):
|
||||
if self.product_id in self.bom_id.byproduct_ids.mapped('product_id'):
|
||||
raise UserError(_("You cannot have %s as the finished product and in the Byproducts") % self.product_id.name)
|
||||
moves_values = [self._get_finished_move_value(self.product_id.id, self.product_qty, self.product_uom_id.id)]
|
||||
for byproduct in self.bom_id.byproduct_ids:
|
||||
product_uom_factor = self.product_uom_id._compute_quantity(self.product_qty, self.bom_id.product_uom_id)
|
||||
qty = byproduct.product_qty * (product_uom_factor / self.bom_id.product_qty)
|
||||
move_values = self._get_finished_move_value(byproduct.product_id.id,
|
||||
qty, byproduct.product_uom_id.id, byproduct.operation_id.id,
|
||||
byproduct.id)
|
||||
moves_values.append(move_values)
|
||||
moves = self.env['stock.move'].create(moves_values)
|
||||
def _get_moves_finished_values(self):
|
||||
moves = []
|
||||
for production in self:
|
||||
if production.product_id in production.bom_id.byproduct_ids.mapped('product_id'):
|
||||
raise UserError(_("You cannot have %s as the finished product and in the Byproducts") % self.product_id.name)
|
||||
moves = [production._get_move_finished_values(production.product_id.id, production.product_qty, production.product_uom_id.id)]
|
||||
for byproduct in production.bom_id.byproduct_ids:
|
||||
product_uom_factor = production.product_uom_id._compute_quantity(production.product_qty, production.bom_id.product_uom_id)
|
||||
qty = byproduct.product_qty * (product_uom_factor / production.bom_id.product_qty)
|
||||
moves.append(production._get_move_finished_values(
|
||||
byproduct.product_id.id, qty, byproduct.product_uom_id.id,
|
||||
byproduct.operation_id.id, byproduct.id))
|
||||
return moves
|
||||
|
||||
def _get_moves_raw_values(self):
|
||||
@@ -983,7 +1030,6 @@ class MrpProduction(models.Model):
|
||||
production.consumption = production.bom_id.consumption
|
||||
if not production.move_raw_ids:
|
||||
raise UserError(_("Add some materials to consume before marking this MO as to do."))
|
||||
production._generate_finished_moves()
|
||||
production.move_raw_ids._adjust_procure_method()
|
||||
(production.move_raw_ids | production.move_finished_ids)._action_confirm()
|
||||
production.workorder_ids._action_confirm()
|
||||
@@ -998,7 +1044,7 @@ class MrpProduction(models.Model):
|
||||
""" Create work orders. And probably do stuff, like things. """
|
||||
orders_to_plan = self.filtered(lambda order: not order.is_planned)
|
||||
for order in orders_to_plan:
|
||||
order.move_raw_ids.filtered(lambda m: m.state == 'draft')._action_confirm()
|
||||
(order.move_raw_ids | order.move_finished_ids).filtered(lambda m: m.state == 'draft')._action_confirm()
|
||||
# `propagate_date` enables the automatic rescheduling which could lead to hard to
|
||||
# understand behavior if a manufacturing order is planned, i.e. if the work orders do
|
||||
# have their leaves booked in the workcenter calendar. We thus disable the
|
||||
@@ -1273,7 +1319,6 @@ class MrpProduction(models.Model):
|
||||
for production in self:
|
||||
if production.backorder_sequence == 0: # Activate backorder naming
|
||||
production.backorder_sequence = 1
|
||||
|
||||
backorder_mo = production.copy(default=self._get_backorder_mo_vals(production))
|
||||
for wo in backorder_mo.workorder_ids:
|
||||
wo.qty_produced = 0
|
||||
@@ -1322,23 +1367,14 @@ class MrpProduction(models.Model):
|
||||
if self.env.context.get('mo_ids_to_backorder'):
|
||||
productions_to_backorder = self.browse(self.env.context['mo_ids_to_backorder'])
|
||||
productions_not_to_backorder = self - productions_to_backorder
|
||||
# As a backorder will be created to produce the remaining quantity, we don't want
|
||||
# action_done (in _post_inventory) create any new move for the finished one and the
|
||||
# byproduct with the delta quantity (product_uom_qty - quantity_done)
|
||||
for production in productions_to_backorder:
|
||||
for move in production.move_finished_ids:
|
||||
if move.product_id == production.product_id:
|
||||
move.product_uom_qty = production.qty_producing
|
||||
else:
|
||||
move.product_uom_qty = move.quantity_done
|
||||
else:
|
||||
productions_not_to_backorder = self
|
||||
productions_to_backorder = self.env['mrp.production']
|
||||
|
||||
self.workorder_ids.button_finish()
|
||||
|
||||
productions_not_to_backorder._post_inventory(cancel_backorder=False)
|
||||
productions_to_backorder._post_inventory(cancel_backorder=True)
|
||||
productions_not_to_backorder._post_inventory(cancel_backorder=True)
|
||||
productions_to_backorder._post_inventory(cancel_backorder=False)
|
||||
backorders = productions_to_backorder._generate_backorder_productions()
|
||||
|
||||
# Moves without quantity done are not posted => set them as done instead of canceling. In
|
||||
|
||||
@@ -55,6 +55,7 @@ class StockRule(models.Model):
|
||||
# create the MO as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example)
|
||||
productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(productions_values)
|
||||
self.env['stock.move'].sudo().create(productions._get_moves_raw_values())
|
||||
self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
|
||||
productions._create_workorder()
|
||||
productions.filtered(lambda p: p.move_raw_ids).action_confirm()
|
||||
|
||||
|
||||
@@ -132,7 +132,6 @@ class TestMrpProductionBackorder(TestMrpCommon):
|
||||
"""
|
||||
with Form(self.warehouse) as warehouse:
|
||||
warehouse.manufacture_steps = 'pbm_sam'
|
||||
|
||||
production, _, product_to_build, product_to_use_1, product_to_use_2 = self.generate_mo(qty_base_1=4, qty_final=4, picking_type_id=self.warehouse.manu_type_id)
|
||||
|
||||
move_raw_ids = production.move_raw_ids
|
||||
@@ -221,7 +220,7 @@ class TestMrpProductionBackorder(TestMrpCommon):
|
||||
backorder.save().action_backorder()
|
||||
active_production = active_production.procurement_group_id.mrp_production_ids[-1]
|
||||
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot_final), nb_product_todo, f'You should have the {nb_product_todo} final product in stock')
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot_final), nb_product_todo, f'You should have the {nb_product_todo} final product in stock')
|
||||
self.assertEqual(len(production.procurement_group_id.mrp_production_ids), nb_product_todo)
|
||||
|
||||
def test_tracking_backorder_series_serial_1(self):
|
||||
|
||||
@@ -78,7 +78,7 @@ class TestMrpOrder(TestMrpCommon):
|
||||
|
||||
# check production move
|
||||
production_move = man_order.move_finished_ids
|
||||
self.assertEqual(production_move.date, test_date_planned)
|
||||
self.assertAlmostEqual(production_move.date, test_date_planned + timedelta(hours=1), delta=timedelta(seconds=10))
|
||||
self.assertEqual(production_move.product_id, self.product_4)
|
||||
self.assertEqual(production_move.product_uom, man_order.product_uom_id)
|
||||
self.assertEqual(production_move.product_qty, man_order.product_qty)
|
||||
|
||||
@@ -171,6 +171,51 @@
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}" attrs="{'readonly': [('state', '!=', 'draft')]}" force_save="1"/>
|
||||
<field name="show_final_lots" invisible="1"/>
|
||||
<field name="production_location_id" invisible="1" readonly="1"/>
|
||||
<field name="move_finished_ids" invisible="1" attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}">
|
||||
<tree editable="bottom">
|
||||
<field name="product_id"/>
|
||||
<!-- <field name="move_line_ids" invisible="1">
|
||||
<tree>
|
||||
<field name="lot_id" invisible="1"/>
|
||||
<field name="owner_id" invisible="1"/>
|
||||
<field name="package_id" invisible="1"/>
|
||||
<field name="result_package_id" invisible="1"/>
|
||||
<field name="location_id" invisible="1"/>
|
||||
<field name="location_dest_id" invisible="1"/>
|
||||
<field name="qty_done" invisible="1"/>
|
||||
<field name="product_id" invisible="1"/>
|
||||
<field name="product_uom_id" invisible="1"/>
|
||||
<field name="product_uom_qty" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="move_id" invisible="1"/>
|
||||
<field name="id" invisible="1"/>
|
||||
</tree>
|
||||
</field> -->
|
||||
<field name="product_uom_qty"/>
|
||||
<field name="product_uom"/>
|
||||
<field name="operation_id"/>
|
||||
<field name="byproduct_id"/>
|
||||
<field name="name"/>
|
||||
<field name="date"/>
|
||||
<field name="date_expected"/>
|
||||
<field name="picking_type_id"/>
|
||||
<field name="location_id"/>
|
||||
<field name="location_dest_id"/>
|
||||
<field name="company_id"/>
|
||||
<field name="warehouse_id"/>
|
||||
<field name="origin"/>
|
||||
<field name="group_id"/>
|
||||
<field name="propagate_date"/>
|
||||
<field name="propagate_cancel"/>
|
||||
<field name="delay_alert"/>
|
||||
<field name="propagate_date_minimum_delta"/>
|
||||
<field name="move_dest_ids"/>
|
||||
<field name="state"/>
|
||||
<!-- Useless as the editable in tree declaration -> For Form Test-->
|
||||
<field name="product_uom_category_id"/>
|
||||
<field name="allowed_operation_ids"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
|
||||
@@ -138,6 +138,7 @@ class StockPicking(models.Model):
|
||||
for move, bom in subcontract_details:
|
||||
mo = self.env['mrp.production'].with_company(move.company_id).create(self._prepare_subcontract_mo_vals(move, bom))
|
||||
self.env['stock.move'].create(mo._get_moves_raw_values())
|
||||
self.env['stock.move'].create(mo._get_moves_finished_values())
|
||||
mo.action_confirm()
|
||||
|
||||
# Link the finished to the receipt move.
|
||||
|
||||
Reference in New Issue
Block a user