[FIX] mrp: production wizard
The production wizard raised a traceback when you were trying to consume product tracked by lot without reservation. To fix this issue and some potential others, we have removed the move lines from the wizard and create produce lines that are used on validation to create the move lines. This also let us show the 'To Consume' quantity of a lot instead of the reserved quantity which has no sense when there is no reservation.
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
wiz_obj = self.env['mrp.product.produce']
|
||||
ctx = dict(self.env.context, active_id=self.id)
|
||||
produce_wiz = wiz_obj.with_context(ctx).create({'product_qty': self.product_qty, 'lot_id': ref('lot_product_27_0')})
|
||||
produce_wiz.consume_line_ids.write({'qty_done': produce_wiz.product_qty})
|
||||
produce_wiz.produce_line_ids.write({'qty_done': produce_wiz.product_qty})
|
||||
#Produce Product,Post Inventory and Set MO Done.
|
||||
produce_wiz.do_produce()
|
||||
self.post_inventory()
|
||||
|
||||
@@ -229,12 +229,11 @@ class TestWorkOrderProcess(common.TransactionCase):
|
||||
# -----------------
|
||||
|
||||
# Produce 6 Unit of custom laptop will consume ( 12 Unit of keybord and 12 Unit of charger)
|
||||
|
||||
context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id}
|
||||
product_consume = self.env['mrp.product.produce'].with_context(context).create({'product_qty': 6.00})
|
||||
laptop_lot_001 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id})
|
||||
product_consume.lot_id = laptop_lot_001.id
|
||||
product_consume.consume_line_ids.write({'qty_done': 12})
|
||||
product_consume.produce_line_ids.write({'qty_done': 12})
|
||||
product_consume.do_produce()
|
||||
|
||||
# Check consumed move after produce 6 quantity of customized laptop.
|
||||
@@ -259,8 +258,8 @@ class TestWorkOrderProcess(common.TransactionCase):
|
||||
product_consume = self.env['mrp.product.produce'].with_context(context).create({'product_qty': 4.00})
|
||||
laptop_lot_002 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id})
|
||||
product_consume.lot_id = laptop_lot_002.id
|
||||
self.assertEquals(len(product_consume.consume_line_ids), 2)
|
||||
product_consume.consume_line_ids.write({'qty_done': 8})
|
||||
self.assertEquals(len(product_consume.produce_line_ids), 2)
|
||||
product_consume.produce_line_ids.write({'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')
|
||||
@@ -394,9 +393,9 @@ class TestWorkOrderProcess(common.TransactionCase):
|
||||
product_consume = self.env['mrp.product.produce'].with_context(context).create({'product_qty': 10})
|
||||
# laptop_lot_002 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id})
|
||||
product_consume.lot_id = lot_a.id
|
||||
self.assertEquals(len(product_consume.consume_line_ids), 2)
|
||||
product_consume.consume_line_ids.filtered(lambda x : x.product_id == product_C).write({'qty_done': 3000})
|
||||
product_consume.consume_line_ids.filtered(lambda x : x.product_id == product_B).write({'qty_done': 20})
|
||||
self.assertEquals(len(product_consume.produce_line_ids), 2)
|
||||
product_consume.produce_line_ids.filtered(lambda x : x.product_id == product_C).write({'qty_done': 3000})
|
||||
product_consume.produce_line_ids.filtered(lambda x : x.product_id == product_B).write({'qty_done': 20})
|
||||
product_consume.do_produce()
|
||||
mo_custom_product.post_inventory()
|
||||
|
||||
|
||||
@@ -17,53 +17,61 @@ class MrpProductProduce(models.TransientModel):
|
||||
res = super(MrpProductProduce, self).default_get(fields)
|
||||
if self._context and self._context.get('active_id'):
|
||||
production = self.env['mrp.production'].browse(self._context['active_id'])
|
||||
#serial_raw = production.move_raw_ids.filtered(lambda x: x.product_id.tracking == 'serial')
|
||||
main_product_moves = production.move_finished_ids.filtered(lambda x: x.product_id.id == production.product_id.id)
|
||||
serial_finished = (production.product_id.tracking == 'serial')
|
||||
serial = bool(serial_finished)
|
||||
if serial_finished:
|
||||
quantity = 1.0
|
||||
todo_quantity = 1.0
|
||||
else:
|
||||
quantity = production.product_qty - sum(main_product_moves.mapped('quantity_done'))
|
||||
quantity = quantity if (quantity > 0) else 0
|
||||
lines = []
|
||||
existing_lines = []
|
||||
for move in production.move_raw_ids.filtered(lambda x: (x.product_id.tracking != 'none') and x.state not in ('done', 'cancel')):
|
||||
if not move.move_line_ids.filtered(lambda x: not x.lot_produced_id):
|
||||
qty = quantity / move.bom_line_id.bom_id.product_qty * move.bom_line_id.product_qty
|
||||
if move.product_id.tracking == 'serial':
|
||||
while float_compare(qty, 0.0, precision_rounding=move.product_uom.rounding) > 0:
|
||||
main_product_moves = production.move_finished_ids.filtered(lambda x: x.product_id.id == production.product_id.id)
|
||||
todo_quantity = production.product_qty - sum(main_product_moves.mapped('quantity_done'))
|
||||
todo_quantity = todo_quantity if (todo_quantity > 0) else 0
|
||||
if 'production_id' in fields:
|
||||
res['production_id'] = production.id
|
||||
if 'product_id' in fields:
|
||||
res['product_id'] = production.product_id.id
|
||||
if 'product_uom_id' in fields:
|
||||
res['product_uom_id'] = production.product_uom_id.id
|
||||
if 'serial' in fields:
|
||||
res['serial'] = bool(serial_finished)
|
||||
if 'product_qty' in fields:
|
||||
res['product_qty'] = todo_quantity
|
||||
if 'produce_line_ids' in fields:
|
||||
lines = []
|
||||
for move in production.move_raw_ids.filtered(lambda x: (x.product_id.tracking != 'none') and x.state not in ('done', 'cancel')):
|
||||
qty_to_consume = todo_quantity / move.bom_line_id.bom_id.product_qty * move.bom_line_id.product_qty
|
||||
for move_line in move.move_line_ids:
|
||||
if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) < 0:
|
||||
break
|
||||
to_consume_in_line = min(qty_to_consume, move_line.product_uom_qty)
|
||||
lines.append({
|
||||
'move_id': move.id,
|
||||
'qty_to_consume': to_consume_in_line,
|
||||
'qty_done': 0.0,
|
||||
'lot_id': move_line.lot_id.id,
|
||||
'product_uom_id': move.product_uom.id,
|
||||
'product_id': move.product_id.id,
|
||||
})
|
||||
qty_to_consume -= to_consume_in_line
|
||||
if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) > 0:
|
||||
if move.product_id.tracking == 'serial':
|
||||
while float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) > 0:
|
||||
lines.append({
|
||||
'move_id': move.id,
|
||||
'qty_to_consume': 1,
|
||||
'qty_done': 0.0,
|
||||
'product_uom_id': move.product_uom.id,
|
||||
'product_id': move.product_id.id,
|
||||
})
|
||||
qty_to_consume -= 1
|
||||
else:
|
||||
lines.append({
|
||||
'move_id': move.id,
|
||||
'product_qty': min(1,qty),
|
||||
'qty_to_consume': qty_to_consume,
|
||||
'qty_done': 0.0,
|
||||
'product_uom_id': move.product_uom.id,
|
||||
'product_id': move.product_id.id,
|
||||
'production_id': production.id,
|
||||
'location_id': move.location_id.id,
|
||||
'location_dest_id': move.location_dest_id.id,
|
||||
})
|
||||
qty -= 1
|
||||
else:
|
||||
lines.append({
|
||||
'move_id': move.id,
|
||||
'product_qty': qty,
|
||||
'qty_done': 0.0,
|
||||
'product_uom_id': move.product_uom.id,
|
||||
'product_id': move.product_id.id,
|
||||
'production_id': production.id,
|
||||
'location_id': move.location_id.id,
|
||||
'location_dest_id': move.location_dest_id.id,
|
||||
})
|
||||
else:
|
||||
existing_lines += move.move_line_ids.filtered(lambda x: not x.lot_produced_id).ids
|
||||
|
||||
res['serial'] = serial
|
||||
res['production_id'] = production.id
|
||||
res['product_qty'] = quantity
|
||||
res['product_id'] = production.product_id.id
|
||||
res['product_uom_id'] = production.product_uom_id.id
|
||||
res['consume_line_ids'] = (existing_lines and [(6, 0, [x for x in existing_lines])] or []) + [(0, 0, x) for x in lines]
|
||||
res['produce_line_ids'] = [(0, 0, x) for x in lines]
|
||||
return res
|
||||
|
||||
serial = fields.Boolean('Requires Serial')
|
||||
@@ -72,7 +80,7 @@ class MrpProductProduce(models.TransientModel):
|
||||
product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True)
|
||||
product_uom_id = fields.Many2one('product.uom', 'Unit of Measure')
|
||||
lot_id = fields.Many2one('stock.production.lot', string='Lot')
|
||||
consume_line_ids = fields.Many2many('stock.move.line', 'mrp_produce_stock_move_line', string='Product to Track')
|
||||
produce_line_ids = fields.One2many('mrp.product.produce.line', 'product_produce_id', string='Product to Track')
|
||||
product_tracking = fields.Selection(related="product_id.tracking")
|
||||
|
||||
@api.multi
|
||||
@@ -104,14 +112,13 @@ class MrpProductProduce(models.TransientModel):
|
||||
|
||||
@api.multi
|
||||
def check_finished_move_lots(self):
|
||||
packs = self.env['stock.move.line']
|
||||
produce_move = self.production_id.move_finished_ids.filtered(lambda x: x.product_id == self.product_id and x.state not in ('done', 'cancel'))
|
||||
if produce_move and produce_move.product_id.tracking != 'none':
|
||||
if not self.lot_id:
|
||||
raise UserError(_('You need to provide a lot for the finished product'))
|
||||
existing_move_line = produce_move.move_line_ids.filtered(lambda x: x.lot_id == self.lot_id)
|
||||
if existing_move_line:
|
||||
existing_move_line.product_qty += self.product_qty
|
||||
existing_move_line.product_uom_qty += self.product_qty
|
||||
existing_move_line.qty_done += self.product_qty
|
||||
else:
|
||||
vals = {
|
||||
@@ -125,18 +132,48 @@ class MrpProductProduce(models.TransientModel):
|
||||
'location_id': produce_move.location_id.id,
|
||||
'location_dest_id': produce_move.location_dest_id.id,
|
||||
}
|
||||
packs.create(vals)
|
||||
for move in self.production_id.move_raw_ids:
|
||||
for moveline in move.move_line_ids.filtered(lambda x: not x.lot_produced_id):
|
||||
if moveline.qty_done and self.lot_id:
|
||||
#Possibly the entire move is selected
|
||||
remaining_qty = moveline.product_uom_qty - moveline.qty_done
|
||||
if remaining_qty > 0:
|
||||
default = {'product_uom_qty': moveline.qty_done,
|
||||
'qty_done': moveline.qty_done,
|
||||
'lot_produced_id': self.lot_id.id}
|
||||
new_move_line = moveline.copy(default=default)
|
||||
moveline.with_context(bypass_reservation_update=True).write({'product_uom_qty': remaining_qty, 'qty_done': 0})
|
||||
else:
|
||||
moveline.write({'lot_produced_id': self.lot_id.id})
|
||||
self.env['stock.move.line'].create(vals)
|
||||
|
||||
for pl in self.produce_line_ids:
|
||||
if pl.qty_done and pl.lot_id:
|
||||
ml = pl.move_id.move_line_ids.filtered(lambda ml: ml.lot_id == pl.lot_id and not ml.lot_produced_id)
|
||||
if ml:
|
||||
if (ml.qty_done + pl.qty_done) >= ml.product_uom_qty:
|
||||
ml.write({'qty_done': ml.qty_done + pl.qty_done, 'lot_produced_id': self.lot_id.id})
|
||||
else:
|
||||
new_qty_todo = ml.product_uom_qty - (ml.qty_done + pl.qty_done)
|
||||
default = {'product_uom_qty': ml.qty_done + pl.qty_done,
|
||||
'qty_done': ml.qty_done + pl.qty_done,
|
||||
'lot_produced_id': self.lot_id.id}
|
||||
ml.copy(default=default)
|
||||
ml.with_context(bypass_reservation_update=True).write({'product_uom_qty': new_qty_todo, 'qty_done': 0})
|
||||
else:
|
||||
self.env['stock.move.line'].create({
|
||||
'move_id': pl.move_id.id,
|
||||
'product_id': pl.product_id.id,
|
||||
'location_id': pl.move_id.location_id.id,
|
||||
'location_dest_id': pl.move_id.location_dest_id.id,
|
||||
'product_uom_qty': 0,
|
||||
'product_uom_id': pl.product_uom_id.id,
|
||||
'qty_done': pl.qty_done,
|
||||
'lot_id': pl.lot_id.id,
|
||||
'lot_produced_id': self.lot_id.id,
|
||||
})
|
||||
return True
|
||||
|
||||
class MrpProductProduceLine(models.TransientModel):
|
||||
_name = "mrp.product.produce.line"
|
||||
_description = "Record Production Line"
|
||||
|
||||
product_produce_id = fields.Many2one('mrp.product.produce')
|
||||
product_id = fields.Many2one('product.product', 'Product')
|
||||
lot_id = fields.Many2one('stock.production.lot', 'Lot')
|
||||
qty_to_consume = fields.Float('To Consume')
|
||||
product_uom_id = fields.Many2one('product.uom', 'Unit of Measure')
|
||||
qty_done = fields.Float('Done')
|
||||
move_id = fields.Many2one('stock.move')
|
||||
|
||||
@api.onchange('lot_id')
|
||||
def _onchange_lot_id(self):
|
||||
if self.product_id.tracking == 'serial':
|
||||
self.qty_done = 1
|
||||
|
||||
@@ -14,20 +14,20 @@
|
||||
<label for="product_qty"/>
|
||||
<div class="o_row">
|
||||
<field name="product_qty" attrs="{'readonly': [('serial', '=', True)]}"/>
|
||||
<field name="product_uom_id" readonly="1"/>
|
||||
<field name="product_uom_id" readonly="1" groups="product.group_uom"/>
|
||||
</div>
|
||||
<field name="product_tracking" invisible="1"/>
|
||||
<field name="lot_id" attrs="{'invisible': [('product_tracking', '=', 'none')], 'required': [('product_tracking', '!=', 'none')]}" domain="[('product_id','=',product_id)]" context="{'default_product_id': product_id}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="consume_line_ids" attrs="{'invisible': [('consume_line_ids', '=', [])]}" nolabel="1" context="{'w_production': True, 'active_id': production_id, 'default_lot_id': lot_id}">
|
||||
<field name="produce_line_ids" attrs="{'invisible': [('produce_line_ids', '=', [])]}" nolabel="1" context="{'w_production': True, 'active_id': production_id, 'default_lot_id': lot_id}">
|
||||
<tree editable="top" delete="0" create="0">
|
||||
<field name="product_id" readonly="1"/>
|
||||
<field name="product_id" readonly="1" force_save="1"/>
|
||||
<field name="lot_id" context="{'default_product_id': product_id}" domain="[('product_id', '=', product_id)]"/>
|
||||
<field name="product_uom_qty" string="To Consume"/>
|
||||
<field name="product_qty" readonly="1" string="Reserved"/>
|
||||
<field name="qty_to_consume" readonly="1"/>
|
||||
<field name="qty_done"/>
|
||||
<field name="product_uom_id" readonly="1" force_save="1" groups="product.group_uom"/>
|
||||
<field name="move_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
Reference in New Issue
Block a user