From f3b34fecad84e9df09493a3f4e4968fd6401ce5c Mon Sep 17 00:00:00 2001 From: Pierre Masereel Date: Thu, 24 Aug 2017 11:14:46 +0200 Subject: [PATCH] [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. --- addons/mrp/data/mrp_lot_demo.yml | 2 +- addons/mrp/tests/test_workorder_operation.py | 13 +- addons/mrp/wizard/mrp_product_produce.py | 145 +++++++++++------- .../mrp/wizard/mrp_product_produce_views.xml | 10 +- 4 files changed, 103 insertions(+), 67 deletions(-) diff --git a/addons/mrp/data/mrp_lot_demo.yml b/addons/mrp/data/mrp_lot_demo.yml index 0f5c5c987d2..bb0c54e238e 100644 --- a/addons/mrp/data/mrp_lot_demo.yml +++ b/addons/mrp/data/mrp_lot_demo.yml @@ -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() diff --git a/addons/mrp/tests/test_workorder_operation.py b/addons/mrp/tests/test_workorder_operation.py index 15adcd236c8..04b112c31b3 100644 --- a/addons/mrp/tests/test_workorder_operation.py +++ b/addons/mrp/tests/test_workorder_operation.py @@ -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() diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py index 8b08771e6e6..61bdffc4618 100644 --- a/addons/mrp/wizard/mrp_product_produce.py +++ b/addons/mrp/wizard/mrp_product_produce.py @@ -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 diff --git a/addons/mrp/wizard/mrp_product_produce_views.xml b/addons/mrp/wizard/mrp_product_produce_views.xml index ddcde4b3065..7f1dc53fea6 100644 --- a/addons/mrp/wizard/mrp_product_produce_views.xml +++ b/addons/mrp/wizard/mrp_product_produce_views.xml @@ -14,20 +14,20 @@