[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:
Pierre Masereel
2017-09-11 14:48:08 +02:00
parent bd08d4f052
commit f3b34fecad
4 changed files with 103 additions and 67 deletions
+1 -1
View File
@@ -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()
+6 -7
View File
@@ -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()
+91 -54
View File
@@ -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>