From 2678eba0be0593e9d5857025200d6b7b264162ce Mon Sep 17 00:00:00 2001 From: Pratima Gupta Date: Fri, 16 Feb 2018 15:24:30 +0530 Subject: [PATCH 1/2] [IMP] mrp: produce wizard improvement. This commit makes several improvement in the 'Produce' wizard on Manufacturing Orders such as : 1) In produce wizard, all components will be visible. (Regardless of tracking) 2) Quantity done will be equal to expected consumption. 3) Produce wizard line data filled from an onchange on final product 4) Reserved column is added to 'produce' wizard 5) When production order is done, we hide the column 'reserved' 6) In produce wizard, if consuming products are not available or in too small quantity, the lines is displayed red Task #55494. --- addons/mrp/views/mrp_production_views.xml | 2 +- addons/mrp/wizard/mrp_product_produce.py | 100 ++++++++---------- .../mrp/wizard/mrp_product_produce_views.xml | 8 +- 3 files changed, 53 insertions(+), 57 deletions(-) diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml index 2b88ce333e6..4292b50b88a 100644 --- a/addons/mrp/views/mrp_production_views.xml +++ b/addons/mrp/views/mrp_production_views.xml @@ -126,7 +126,7 @@ - + diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py index 24415fa90b9..26b58e0f6fd 100644 --- a/addons/mrp/wizard/mrp_product_produce.py +++ b/addons/mrp/wizard/mrp_product_produce.py @@ -35,47 +35,6 @@ class MrpProductProduce(models.TransientModel): 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') and x.bom_line_id): - qty_to_consume = float_round(todo_quantity / move.bom_line_id.bom_id.product_qty * move.bom_line_id.product_qty, - precision_rounding=move.product_uom.rounding, rounding_method="UP") - for move_line in move.move_line_ids: - if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) <= 0: - break - if move_line.lot_produced_id or float_compare(move_line.product_uom_qty, move_line.qty_done, precision_rounding=move.product_uom.rounding) <= 0: - continue - 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, - 'qty_to_consume': qty_to_consume, - 'qty_done': 0.0, - 'product_uom_id': move.product_uom.id, - 'product_id': move.product_id.id, - }) - - res['produce_line_ids'] = [(0, 0, x) for x in lines] return res serial = fields.Boolean('Requires Serial') @@ -93,15 +52,6 @@ class MrpProductProduce(models.TransientModel): quantity = self.product_qty if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0: raise UserError(_("The production order for '%s' has no quantity specified.") % self.product_id.display_name) - for move in self.production_id.move_raw_ids: - # TODO currently not possible to guess if the user updated quantity by hand or automatically by the produce wizard. - if move.product_id.tracking == 'none' and move.state not in ('done', 'cancel') and move.unit_factor: - rounding = move.product_uom.rounding - if self.product_id.tracking != 'none': - qty_to_add = float_round(quantity * move.unit_factor, precision_rounding=rounding) - move._generate_consumed_move_line(qty_to_add, self.lot_id) - else: - move.quantity_done += float_round(quantity * move.unit_factor, precision_rounding=rounding) for move in self.production_id.move_finished_ids: if move.product_id.tracking == 'none' and move.state not in ('done', 'cancel'): rounding = move.product_uom.rounding @@ -146,7 +96,7 @@ class MrpProductProduce(models.TransientModel): for pl in self.produce_line_ids: if pl.qty_done: - if not pl.lot_id: + if pl.product_id.tracking != 'none' and not pl.lot_id: raise UserError(_('Please enter a lot or serial number for %s !' % pl.product_id.name)) if not pl.move_id: # Find move_id that would match @@ -169,6 +119,48 @@ class MrpProductProduce(models.TransientModel): pl.move_id._generate_consumed_move_line(pl.qty_done, self.lot_id, lot=pl.lot_id) return True + @api.onchange('product_qty') + def _onchange_product_qty(self): + lines = [] + for move in self.production_id.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel') and x.bom_line_id): + qty_to_consume = float_round(self.product_qty * move.unit_factor, precision_rounding=move.product_uom.rounding) + for move_line in move.move_line_ids: + if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) <= 0: + break + if move_line.lot_produced_id or float_compare(move_line.product_uom_qty, move_line.qty_done, precision_rounding=move.product_uom.rounding) <= 0: + continue + 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': to_consume_in_line, + 'lot_id': move_line.lot_id.id, + 'product_uom_id': move.product_uom.id, + 'product_id': move.product_id.id, + 'qty_reserved': min(to_consume_in_line, move_line.product_uom_qty), + }) + 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': 1, + 'product_uom_id': move.product_uom.id, + 'product_id': move.product_id.id, + }) + qty_to_consume -= 1 + else: + lines.append({ + 'move_id': move.id, + 'qty_to_consume': qty_to_consume, + 'qty_done': qty_to_consume, + 'product_uom_id': move.product_uom.id, + 'product_id': move.product_id.id, + }) + + self.produce_line_ids = [(0, 0, x) for x in lines] class MrpProductProduceLine(models.TransientModel): _name = "mrp.product.produce.line" @@ -176,11 +168,13 @@ class MrpProductProduceLine(models.TransientModel): product_produce_id = fields.Many2one('mrp.product.produce') product_id = fields.Many2one('product.product', 'Product') + product_tracking = fields.Selection(related="product_id.tracking") lot_id = fields.Many2one('stock.production.lot', 'Lot') qty_to_consume = fields.Float('To Consume', digits=dp.get_precision('Product Unit of Measure')) product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure') - qty_done = fields.Float('Done', digits=dp.get_precision('Product Unit of Measure')) + qty_done = fields.Float('Consumed') move_id = fields.Many2one('stock.move') + qty_reserved = fields.Float('Reserved') @api.onchange('lot_id') def _onchange_lot_id(self): @@ -198,7 +192,7 @@ class MrpProductProduceLine(models.TransientModel): help him. This onchange will warn him if he set `qty_done` to a non-supported value. """ res = {} - if self.product_id.tracking == 'serial': + if self.product_id.tracking == 'serial' and self.qty_done: if float_compare(self.qty_done, 1.0, precision_rounding=self.move_id.product_id.uom_id.rounding) != 0: message = _('You can only process 1.0 %s of products with unique serial number.') % self.product_id.uom_id.name res['warning'] = {'title': _('Warning'), 'message': message} diff --git a/addons/mrp/wizard/mrp_product_produce_views.xml b/addons/mrp/wizard/mrp_product_produce_views.xml index fb9518918e8..f17e5dc9c0e 100644 --- a/addons/mrp/wizard/mrp_product_produce_views.xml +++ b/addons/mrp/wizard/mrp_product_produce_views.xml @@ -22,10 +22,12 @@ - - - + + + + + From 3935bdc3decfa82306f6e286baec53cb0144f671 Mon Sep 17 00:00:00 2001 From: Pratima Gupta Date: Thu, 22 Feb 2018 12:09:14 +0530 Subject: [PATCH 2/2] [IMP] mrp: update mrp tests to the new 'Produce' wizard New test : - Create a MO with a finish product tracked by SN with a dozen as quantity - Click on produce button in order to open the produce produce wizard The quantity to do is 1 dozen although the tracking is by serial number Task #55494. --- addons/mrp/tests/test_order.py | 106 +++++++++++++----- addons/mrp/tests/test_procurement.py | 22 ++-- addons/mrp/tests/test_unbuild.py | 86 +++++++------- addons/mrp/tests/test_workorder_operation.py | 23 ++-- addons/mrp/wizard/mrp_product_produce.py | 24 ++-- .../mrp_byproduct/tests/test_mrp_byproduct.py | 5 +- addons/sale_mrp/tests/test_sale_mrp_flow.py | 8 +- 7 files changed, 172 insertions(+), 102 deletions(-) diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py index 6c56fa22275..30a6fb49c44 100644 --- a/addons/mrp/tests/test_order.py +++ b/addons/mrp/tests/test_order.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo.tests import Form from datetime import datetime, timedelta from odoo.fields import Datetime as Dt @@ -140,12 +141,12 @@ class TestMrpOrder(TestMrpCommon): # #self.assertEqual(self.mrp_production_test1.availability, 'assigned', 'Production order availability should be set as available') # produce product - produce_wizard = self.env['mrp.product.produce'].sudo(self.user_mrp_user).with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': man_order.id, 'active_ids': [man_order.id], - }).create({ - 'product_qty': 1.0, - }) + })) + produce_form.product_qty = 1.0 + produce_wizard = produce_form.save() produce_wizard.do_produce() # man_order.button_mark_done() @@ -412,7 +413,9 @@ class TestMrpOrder(TestMrpCommon): # produce one item, call `post_inventory` context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id} - custom_laptop_produce = self.env['mrp.product.produce'].with_context(context).create({'product_qty': 1.00}) + produce_form = Form(self.env['mrp.product.produce'].with_context(context)) + produce_form.product_qty = 1.00 + custom_laptop_produce = produce_form.save() custom_laptop_produce.do_produce() mo_custom_laptop.post_inventory() @@ -423,7 +426,9 @@ class TestMrpOrder(TestMrpCommon): # produce the second item, call `post_inventory` context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id} - custom_laptop_produce = self.env['mrp.product.produce'].with_context(context).create({'product_qty': 1.00}) + produce_form = Form(self.env['mrp.product.produce'].with_context(context)) + produce_form.product_qty = 1.00 + custom_laptop_produce = produce_form.save() custom_laptop_produce.do_produce() mo_custom_laptop.post_inventory() @@ -449,12 +454,12 @@ class TestMrpOrder(TestMrpCommon): self.assertEqual(production.move_raw_ids[1].product_qty, 84, 'The quantity should be rounded up') # produce product - produce_wizard = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': production.id, 'active_ids': [production.id], - }).create({ - 'product_qty': 8, - }) + })) + produce_form.product_qty = 8 + produce_wizard = produce_form.save() produce_wizard.do_produce() self.assertEqual(production.move_raw_ids[0].quantity_done, 16, 'Should use half-up rounding when producing') self.assertEqual(production.move_raw_ids[1].quantity_done, 34, 'Should use half-up rounding when producing') @@ -470,12 +475,14 @@ class TestMrpOrder(TestMrpCommon): mo.action_assign() - product_produce = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], - }).create({}) + })) + product_produce = produce_form.save() + product_produce.do_produce() - self.assertEqual(len(product_produce.produce_line_ids), 0, 'You should not have any produce lines since the consumed products are not tracked.') + self.assertEqual(len(product_produce.produce_line_ids), 2, 'You should have produce lines even the consumed products are not tracked.') def test_product_produce_2(self): """ Check that line are created when the consumed products are @@ -498,24 +505,27 @@ class TestMrpOrder(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() - product_produce = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], - }).create({}) + })) + product_produce = produce_form.save() - self.assertEqual(len(product_produce.produce_line_ids), 2, 'You should have 2 produce lines. One for each serial to consume') + self.assertEqual(len(product_produce.produce_line_ids), 3, 'You should have 3 produce lines. One for each serial to consume') product_produce.product_qty = 1 produce_line_1 = product_produce.produce_line_ids[0] produce_line_1.qty_done = 1 remaining_lot = (lot_p1_1 | lot_p1_2) - produce_line_1.lot_id product_produce.do_produce() - product_produce = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], - }).create({}) - self.assertEqual(len(product_produce.produce_line_ids), 1, 'You should have 1 produce lines since one has already be consumed.') - self.assertEqual(product_produce.produce_line_ids[0].lot_id, remaining_lot, 'Wrong lot proposed.') + })) + product_produce = produce_form.save() + self.assertEqual(len(product_produce.produce_line_ids), 2, 'You should have 2 produce lines since one has already be consumed.') + for line in product_produce.produce_line_ids.filtered(lambda x: x.lot_id): + self.assertEqual(line.lot_id, remaining_lot, 'Wrong lot proposed.') def test_product_produce_3(self): """ Check that line are created when the consumed products are @@ -546,17 +556,17 @@ class TestMrpOrder(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() - product_produce = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], - }).create({ - 'product_qty': 1.0, - 'lot_id': final_product_lot.id, - }) + })) + produce_form.product_qty = 1.0 + product_produce = produce_form.save() + product_produce.lot_id = final_product_lot.id # product 1 lot 1 shelf1 # product 1 lot 1 shelf2 # product 1 lot 2 - self.assertEqual(len(product_produce.produce_line_ids), 3, 'You should have 3 produce lines. lot 1 shelf_1, lot 1 shelf_2, lot2') + self.assertEqual(len(product_produce.produce_line_ids), 4, 'You should have 4 produce lines. lot 1 shelf_1, lot 1 shelf_2, lot2 and for product which have tracking None') for produce_line in product_produce.produce_line_ids: produce_line.qty_done = produce_line.qty_to_consume + 1 @@ -578,3 +588,47 @@ class TestMrpOrder(TestMrpCommon): mo.button_mark_done() self.assertEqual(mo.state, 'done', "Production order should be in done state.") + + def test_product_produce_uom(self): + hdd_on_demand = self.env.ref('mrp.product_product_19') + bom = self.env.ref('mrp.mrp_bom_3') + dozen = self.env.ref('uom.product_uom_dozen') + unit = self.env.ref('uom.product_uom_unit') + + hdd_on_demand.tracking = 'serial' + + mo = self.env['mrp.production'].create({ + 'name': 'A dozen pls', + 'product_id': hdd_on_demand.id, + 'product_uom_id': dozen.id, + 'product_qty': 1, + 'bom_id': bom.id, + }) + + final_product_lot = self.env['stock.production.lot'].create({ + 'name': 'lot1', + 'product_id': hdd_on_demand.id, + }) + + mo.action_assign() + self.assertEqual(mo.move_raw_ids.product_qty, 12, '12 units should be reserved.') + + # produce product + produce_form = Form(self.env['mrp.product.produce'].with_context({ + 'active_id': mo.id, + 'active_ids': [mo.id], + })) + produce_form.lot_id = final_product_lot + product_produce = produce_form.save() + self.assertEqual(product_produce.product_qty, 1) + self.assertEqual(product_produce.product_uom_id, unit, 'Should be 1 unit since the tracking is serial.') + product_produce.lot_id = final_product_lot.id + + product_produce.do_produce() + move_line_raw = mo.move_raw_ids.mapped('move_line_ids').filtered(lambda m: m.qty_done) + self.assertEqual(move_line_raw.qty_done, 1) + self.assertEqual(move_line_raw.product_uom_id, unit, 'Should be 1 unit since the tracking is serial.') + + move_line_finished = mo.move_finished_ids.mapped('move_line_ids').filtered(lambda m: m.qty_done) + self.assertEqual(move_line_finished.qty_done, 1) + self.assertEqual(move_line_finished.product_uom_id, unit, 'Should be 1 unit since the tracking is serial.') diff --git a/addons/mrp/tests/test_procurement.py b/addons/mrp/tests/test_procurement.py index 16c6dbe4a32..859bdf16312 100644 --- a/addons/mrp/tests/test_procurement.py +++ b/addons/mrp/tests/test_procurement.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo.tests import Form from odoo.addons.mrp.tests.common import TestMrpCommon from odoo.exceptions import UserError @@ -65,13 +66,13 @@ class TestProcurement(TestMrpCommon): # produce product4 # --------------- - produce_wizard = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': produce_product_4.id, 'active_ids': [produce_product_4.id], - }).create({ - 'product_qty': produce_product_4.product_qty, - }) - produce_wizard.do_produce() + })) + produce_form.product_qty = produce_product_4.product_qty + product_produce = produce_form.save() + product_produce.do_produce() produce_product_4.post_inventory() # Check procurement and Production state for product 4. produce_product_4.button_mark_done() @@ -91,14 +92,13 @@ class TestProcurement(TestMrpCommon): # ------------------------------------ self.assertEqual(production_product_6.availability, 'assigned', "Consume material not available") - produce_wizard = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': production_product_6.id, 'active_ids': [production_product_6.id], - }).create({ - 'product_qty': production_product_6.product_qty, - }) - produce_wizard.do_produce() - + })) + produce_form.product_qty = production_product_6.product_qty + product_produce = produce_form.save() + product_produce.do_produce() production_product_6.post_inventory() # Check procurement and Production state for product 6. production_product_6.button_mark_done() diff --git a/addons/mrp/tests/test_unbuild.py b/addons/mrp/tests/test_unbuild.py index 0c98b64b386..505e47c4b13 100644 --- a/addons/mrp/tests/test_unbuild.py +++ b/addons/mrp/tests/test_unbuild.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo.tests import Form from odoo.addons.mrp.tests.common import TestMrpCommon from odoo.exceptions import UserError @@ -23,12 +24,12 @@ class TestUnbuild(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() - produce_wizard = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], - }).create({ - 'product_qty': 5.0, - }) + })) + produce_form.product_qty = 5.0 + produce_wizard = produce_form.save() produce_wizard.do_produce() mo.button_mark_done() @@ -95,13 +96,14 @@ class TestUnbuild(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() - produce_wizard = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], - }).create({ - 'product_qty': 5.0, - 'lot_id': lot.id, - }) + })) + produce_form.product_qty = 5.0 + produce_form.lot_id = lot + produce_wizard = produce_form.save() + produce_wizard.do_produce() mo.button_mark_done() @@ -176,17 +178,16 @@ class TestUnbuild(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() for ml in mo.move_raw_ids.mapped('move_line_ids'): - if ml.product_id.tracking != 'none': - ml.qty_done = ml.product_qty if ml.product_id.tracking != 'none': self.assertEqual(ml.lot_id, lot, 'Wrong reserved lot.') - produce_wizard = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], - }).create({ - 'product_qty': 5.0, - }) + })) + produce_form.product_qty = 5.0 + produce_wizard = produce_form.save() + produce_wizard.do_produce() mo.button_mark_done() @@ -270,15 +271,14 @@ class TestUnbuild(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5, lot_id=lot_2) mo.action_assign() - produce_wizard = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], - }).create({ - 'product_qty': 5.0, - 'lot_id': lot_final.id, - }) - for pl in produce_wizard.produce_line_ids: - pl.qty_done = pl.qty_to_consume + })) + produce_form.product_qty = 5.0 + produce_form.lot_id = lot_final + produce_wizard = produce_form.save() + produce_wizard.do_produce() mo.button_mark_done() @@ -367,15 +367,14 @@ class TestUnbuild(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 3, lot_id=lot_2) self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 2, lot_id=lot_3) mo.action_assign() - for ml in mo.move_raw_ids.mapped('move_line_ids').filtered(lambda m: m.product_id.tracking != 'none'): - ml.qty_done = ml.product_qty - produce_wizard = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], - }).create({ - 'product_qty': 5.0, - }) + })) + produce_form.product_qty = 5.0 + produce_wizard = produce_form.save() + produce_wizard.do_produce() mo.button_mark_done() self.assertEqual(mo.state, 'done', "Production order should be in done state.") @@ -416,15 +415,14 @@ class TestUnbuild(TestMrpCommon): 'product_id': p_final.id, }) - produce_wizard = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], - }).create({ - 'product_qty': 3.0, - 'lot_id': lot_finished_1.id, - }) - - produce_wizard.produce_line_ids[0].lot_id = lot_1.id + })) + produce_form.product_qty = 3.0 + produce_form.lot_id = lot_finished_1 + produce_wizard = produce_form.save() + produce_wizard.produce_line_ids[0].lot_id = lot_1 produce_wizard.do_produce() lot_2 = self.env['stock.production.lot'].create({ @@ -438,18 +436,18 @@ class TestUnbuild(TestMrpCommon): 'product_id': p_final.id, }) - produce_wizard = self.env['mrp.product.produce'].with_context({ + produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], - }).create({ - 'product_qty': 2.0, - 'lot_id': lot_finished_2.id, - }) - - produce_wizard.produce_line_ids[0].lot_id = lot_2.id + })) + produce_form.product_qty = 2.0 + produce_form.lot_id = lot_finished_2 + + produce_wizard = produce_form.save() + produce_wizard.produce_line_ids[0].lot_id = lot_2 produce_wizard.do_produce() mo.button_mark_done() ml = mo.finished_move_line_ids[0].consume_line_ids.filtered(lambda m: m.product_id == p1 and m.lot_produced_id == lot_finished_1) - self.assertEqual(ml.qty_done, 12.0, 'Should have consumed 12 for the first lot') + self.assertEqual(ml[0].qty_done, 12.0, 'Should have consumed 12 for the first lot') ml = mo.finished_move_line_ids[1].consume_line_ids.filtered(lambda m: m.product_id == p1 and m.lot_produced_id == lot_finished_2) - self.assertEqual(ml.qty_done, 8.0, 'Should have consumed 8 for the second lot') \ No newline at end of file + self.assertEqual(ml[0].qty_done, 8.0, 'Should have consumed 8 for the second lot') \ No newline at end of file diff --git a/addons/mrp/tests/test_workorder_operation.py b/addons/mrp/tests/test_workorder_operation.py index 2e62315d83c..d137356b11a 100644 --- a/addons/mrp/tests/test_workorder_operation.py +++ b/addons/mrp/tests/test_workorder_operation.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import unittest +from odoo.tests import Form from odoo.tests import common @@ -329,10 +330,12 @@ 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}) + product_form = Form(self.env['mrp.product.produce'].with_context(context)) + product_form.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.produce_line_ids.write({'qty_done': 12}) + product_form.lot_id = laptop_lot_001 + product_consume = product_form.save() + product_consume.produce_line_ids[0].qty_done = 12 product_consume.do_produce() # Check consumed move after produce 6 quantity of customized laptop. @@ -354,11 +357,13 @@ class TestWorkOrderProcess(common.TransactionCase): # Produce 4 Unit of custom laptop will consume ( 8 Unit of keybord and 8 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': 4.00}) + produce_form = Form(self.env['mrp.product.produce'].with_context(context)) + produce_form.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 + produce_form.lot_id = laptop_lot_002 + product_consume = produce_form.save() self.assertEquals(len(product_consume.produce_line_ids), 2) - product_consume.produce_line_ids.write({'qty_done': 8}) + product_consume.produce_line_ids[0].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') @@ -489,9 +494,11 @@ class TestWorkOrderProcess(common.TransactionCase): mo_custom_product.action_assign() context = {"active_ids": [mo_custom_product.id], "active_id": mo_custom_product.id} - product_consume = self.env['mrp.product.produce'].with_context(context).create({'product_qty': 10}) + produce_form = Form(self.env['mrp.product.produce'].with_context(context)) + produce_form.product_qty = 10.00 + produce_form.lot_id = lot_a + product_consume = produce_form.save() # 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.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}) diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py index 26b58e0f6fd..afcfb7a78c1 100644 --- a/addons/mrp/wizard/mrp_product_produce.py +++ b/addons/mrp/wizard/mrp_product_produce.py @@ -1,12 +1,11 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from collections import Counter from datetime import datetime from odoo import api, fields, models, _ from odoo.addons import decimal_precision as dp -from odoo.exceptions import UserError, ValidationError +from odoo.exceptions import UserError from odoo.tools import float_compare, float_round class MrpProductProduce(models.TransientModel): @@ -19,8 +18,11 @@ class MrpProductProduce(models.TransientModel): if self._context and self._context.get('active_id'): production = self.env['mrp.production'].browse(self._context['active_id']) serial_finished = (production.product_id.tracking == 'serial') + todo_uom = production.product_uom_id.id if serial_finished: todo_quantity = 1.0 + if production.product_uom_id.uom_type != 'reference': + todo_uom = self.env['uom.uom'].search([('category_id', '=', production.product_uom_id.category_id.id), ('uom_type', '=', 'reference')]).id else: 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')) @@ -30,7 +32,7 @@ class MrpProductProduce(models.TransientModel): 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 + res['product_uom_id'] = todo_uom if 'serial' in fields: res['serial'] = bool(serial_finished) if 'product_qty' in fields: @@ -78,15 +80,16 @@ class MrpProductProduce(models.TransientModel): if existing_move_line: if self.product_id.tracking == 'serial': raise UserError(_('You cannot produce the same serial number twice.')) - existing_move_line.product_uom_qty += self.product_qty - existing_move_line.qty_done += self.product_qty + produced_qty = self.product_uom_id._compute_quantity(self.product_qty, existing_move_line.product_uom_id) + existing_move_line.product_uom_qty += produced_qty + existing_move_line.qty_done += produced_qty else: vals = { 'move_id': produce_move.id, 'product_id': produce_move.product_id.id, 'production_id': self.production_id.id, 'product_uom_qty': self.product_qty, - 'product_uom_id': produce_move.product_uom.id, + 'product_uom_id': self.product_uom_id.id, 'qty_done': self.product_qty, 'lot_id': self.lot_id.id, 'location_id': produce_move.location_id.id, @@ -97,10 +100,10 @@ class MrpProductProduce(models.TransientModel): for pl in self.produce_line_ids: if pl.qty_done: if pl.product_id.tracking != 'none' and not pl.lot_id: - raise UserError(_('Please enter a lot or serial number for %s !' % pl.product_id.name)) + raise UserError(_('Please enter a lot or serial number for %s !' % pl.product_id.display_name)) if not pl.move_id: # Find move_id that would match - move_id = self.production_id.move_raw_ids.filtered(lambda x: x.product_id == pl.product_id and x.state not in ('done', 'cancel')) + move_id = self.production_id.move_raw_ids.filtered(lambda m: m.product_id == pl.product_id and m.state not in ('done', 'cancel')) if move_id: pl.move_id = move_id else: @@ -122,8 +125,9 @@ class MrpProductProduce(models.TransientModel): @api.onchange('product_qty') def _onchange_product_qty(self): lines = [] - for move in self.production_id.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel') and x.bom_line_id): - qty_to_consume = float_round(self.product_qty * move.unit_factor, precision_rounding=move.product_uom.rounding) + qty_todo = self.product_uom_id._compute_quantity(self.product_qty, self.production_id.product_uom_id, round=False) + for move in self.production_id.move_raw_ids.filtered(lambda m: m.state not in ('done', 'cancel') and m.bom_line_id): + qty_to_consume = float_round(qty_todo * move.unit_factor, precision_rounding=move.product_uom.rounding) for move_line in move.move_line_ids: if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) <= 0: break diff --git a/addons/mrp_byproduct/tests/test_mrp_byproduct.py b/addons/mrp_byproduct/tests/test_mrp_byproduct.py index 7f930a79346..eedcbb18c7a 100644 --- a/addons/mrp_byproduct/tests/test_mrp_byproduct.py +++ b/addons/mrp_byproduct/tests/test_mrp_byproduct.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo.tests import Form from odoo.tests import common @@ -70,7 +71,9 @@ class TestMrpByProduct(common.TransactionCase): # I consume and produce the production of products. # I create record for selecting mode and quantity of products to produce. - product_consume = self.env['mrp.product.produce'].with_context(context).create({'product_qty': 2.00}) + produce_form = Form(self.env['mrp.product.produce'].with_context(context)) + produce_form.product_qty = 2.00 + product_consume = produce_form.save() # I finish the production order. self.assertEqual(len(mnf_product_a.move_raw_ids), 1, "Wrong consume move on production order.") product_consume.do_produce() diff --git a/addons/sale_mrp/tests/test_sale_mrp_flow.py b/addons/sale_mrp/tests/test_sale_mrp_flow.py index 46e9bbe9d01..14a94657f83 100644 --- a/addons/sale_mrp/tests/test_sale_mrp_flow.py +++ b/addons/sale_mrp/tests/test_sale_mrp_flow.py @@ -267,8 +267,12 @@ class TestSaleMrpFlow(common.TransactionCase): # produce product D. # ------------------ - produce_d = self.ProductProduce.with_context({'active_ids': [mnf_product_d.id], 'active_id': mnf_product_d.id}).create({ - 'product_qty': 20}) + produce_form = Form(self.ProductProduce.with_context({ + 'active_id': mnf_product_d.id, + 'active_ids': [mnf_product_d.id], + })) + produce_form.product_qty = 20 + produce_d = produce_form.save() # produce_d.on_change_qty() produce_d.do_produce() mnf_product_d.post_inventory()