diff --git a/mrp_bom_dismantling/__init__.py b/mrp_bom_dismantling/__init__.py index 3e4baab2f..83c963b2a 100644 --- a/mrp_bom_dismantling/__init__.py +++ b/mrp_bom_dismantling/__init__.py @@ -3,3 +3,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import models +from . import wizards diff --git a/mrp_bom_dismantling/__openerp__.py b/mrp_bom_dismantling/__openerp__.py index a48e84058..53f0dc284 100644 --- a/mrp_bom_dismantling/__openerp__.py +++ b/mrp_bom_dismantling/__openerp__.py @@ -18,5 +18,6 @@ "data": [ "views/mrp_bom.xml", "views/product_template.xml", + "wizards/mrp_product_produce.xml", ], } diff --git a/mrp_bom_dismantling/models/__init__.py b/mrp_bom_dismantling/models/__init__.py index f486e2abe..07cc00ac6 100644 --- a/mrp_bom_dismantling/models/__init__.py +++ b/mrp_bom_dismantling/models/__init__.py @@ -5,3 +5,4 @@ from . import mrp_bom from . import product_product from . import product_template +from . import stock_move diff --git a/mrp_bom_dismantling/models/stock_move.py b/mrp_bom_dismantling/models/stock_move.py new file mode 100644 index 000000000..64e4e743d --- /dev/null +++ b/mrp_bom_dismantling/models/stock_move.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# © 2016 Cyril Gaudin (Camptocamp) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import api, models + + +class StockMove(models.Model): + _inherit = 'stock.move' + + @api.multi + def action_consume(self, product_qty, location_id=False, + restrict_lot_id=False, restrict_partner_id=False, + consumed_for=False): + """ Override restrict_lot_id if user define one for this move's + product in wizard. + """ + # If user define a lot_id for this move's product we override + restrict_lot_id = self.env.context.get('mapping_move_lot', {}).pop( + self.id, restrict_lot_id + ) + + return super(StockMove, self).action_consume( + product_qty, location_id, restrict_lot_id, + restrict_partner_id, consumed_for + ) diff --git a/mrp_bom_dismantling/tests/__init__.py b/mrp_bom_dismantling/tests/__init__.py index 426c588c6..66be24cc2 100644 --- a/mrp_bom_dismantling/tests/__init__.py +++ b/mrp_bom_dismantling/tests/__init__.py @@ -5,4 +5,5 @@ from . import test_bom from . import test_product +from . import test_product_produce from . import test_template diff --git a/mrp_bom_dismantling/tests/test_product_produce.py b/mrp_bom_dismantling/tests/test_product_produce.py new file mode 100644 index 000000000..9680c2143 --- /dev/null +++ b/mrp_bom_dismantling/tests/test_product_produce.py @@ -0,0 +1,96 @@ +# -*- coding: utf8 -*- + +from openerp.tests import TransactionCase + + +class TestProductProduce(TransactionCase): + + def test_produced_products_lots(self): + produce_model = self.env['mrp.product.produce'] + product_model = self.env['product.product'] + lot_model = self.env['stock.production.lot'] + quant_model = self.env['stock.quant'] + + unit_uom = self.browse_ref('product.product_uom_unit') + + wh_main = self.browse_ref('stock.warehouse0') + + # Create simple bom with by products + p1 = product_model.create({'name': 'Test P1'}) + p2 = product_model.create({'name': 'Test P2'}) + p3 = product_model.create({'name': 'Test P3'}) + + # We have 1 P2 in stock + inventory = self.env['stock.inventory'].create({ + 'name': 'P2 inventory', + 'location_id': wh_main.lot_stock_id.id, + 'filter': 'partial' + }) + inventory.prepare_inventory() + + self.env['stock.inventory.line'].create({ + 'inventory_id': inventory.id, + 'product_id': p2.id, + 'location_id': wh_main.lot_stock_id.id, + 'product_qty': 1 + }) + inventory.action_done() + + # P1 need P2 and generates one byproduct P3 + bom = self.env['mrp.bom'].create({ + 'product_tmpl_id': p1.product_tmpl_id.id, + 'product_id': p1.id, + 'product_qty': 1, + 'product_uom': unit_uom.id, + }) + + self.env['mrp.bom.line'].create({ + 'bom_id': bom.id, + 'product_id': p2.id, + 'product_qty': 1, + 'product_uom': unit_uom.id, + }) + + self.env['mrp.subproduct'].create({ + 'bom_id': bom.id, + 'product_id': p3.id, + 'product_qty': 1, + 'product_uom': unit_uom.id, + }) + + # Create MRP Order + mrp_order_id = bom.create_mrp_production()['res_id'] + mrp_order = self.env['mrp.production'].browse(mrp_order_id) + mrp_order.action_confirm() + mrp_order.action_assign() + + # Wizard simulation + wizard = produce_model.with_context(active_id=mrp_order_id).create({}) + wizard.on_change_product_id() + self.assertEqual(2, len(wizard.move_lot_ids)) + self.assertEqual([p1, p3], [x.product_id for x in wizard.move_lot_ids]) + + lot_p1 = lot_model.create({'name': 'LOT_01', 'product_id': p1.id}) + wizard.move_lot_ids[0].lot_id = lot_p1 + + lot_p3 = lot_model.create({'name': 'LOT_03', 'product_id': p3.id}) + wizard.move_lot_ids[1].lot_id = lot_p3 + + wizard.do_produce() + + # Check created move in mrp.production + mrp_order.refresh() + self.assertEqual(lot_p1, + mrp_order.move_created_ids2[0].restrict_lot_id) + self.assertEqual(lot_p3, + mrp_order.move_created_ids2[1].restrict_lot_id) + + # Check stock.quants + p1_quants = quant_model.search([('product_id', '=', p1.id)]) + self.assertEqual(1, len(p1_quants)) + self.assertEqual(lot_p1, p1_quants.lot_id) + self.assertEqual(1, p1_quants.qty) + + p3_quants = quant_model.search([('product_id', '=', p3.id)]) + self.assertEqual(1, len(p3_quants)) + self.assertEqual(1, p3_quants.qty) diff --git a/mrp_bom_dismantling/wizards/__init__.py b/mrp_bom_dismantling/wizards/__init__.py new file mode 100644 index 000000000..4a94536b7 --- /dev/null +++ b/mrp_bom_dismantling/wizards/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2016 Cyril Gaudin (Camptocamp) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import mrp_product_produce diff --git a/mrp_bom_dismantling/wizards/mrp_product_produce.py b/mrp_bom_dismantling/wizards/mrp_product_produce.py new file mode 100644 index 000000000..2995a6bb7 --- /dev/null +++ b/mrp_bom_dismantling/wizards/mrp_product_produce.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# © 2016 Cyril Gaudin (Camptocamp) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import api, fields, models + + +class MrpByProductLine(models.TransientModel): + _name = "mrp.product.produced.line" + + produce_id = fields.Many2one('mrp.product.produce', required=True, + string="Produce") + move_id = fields.Many2one('stock.move', required=True) + product_id = fields.Many2one('product.product', + related='move_id.product_id') + lot_id = fields.Many2one('stock.production.lot', string='Lot') + + +class MrpProductProduce(models.TransientModel): + _inherit = "mrp.product.produce" + move_lot_ids = fields.One2many( + 'mrp.product.produced.line', + inverse_name='produce_id', + ) + + @api.onchange("product_id") + def on_change_product_id(self): + """ Listen to product_id changes just for filling byproducts_lot_ids. + """ + if not self.move_lot_ids: + mrp_prod = self.env["mrp.production"].browse( + self.env.context['active_id'] + ) + + self.move_lot_ids = [ + (0, None, {'move_id': move}) + for move in mrp_prod.move_created_ids + ] + + @api.multi + def do_produce(self): + """ Stock produced products lot_id and call parent do_produce + """ + mapping_move_lot = {} + for move_lot in self.move_lot_ids: + if move_lot.lot_id: + mapping_move_lot[move_lot.move_id.id] = move_lot.lot_id.id + + super(MrpProductProduce, self.with_context( + mapping_move_lot=mapping_move_lot + )).do_produce() diff --git a/mrp_bom_dismantling/wizards/mrp_product_produce.xml b/mrp_bom_dismantling/wizards/mrp_product_produce.xml new file mode 100644 index 000000000..2bea3adc7 --- /dev/null +++ b/mrp_bom_dismantling/wizards/mrp_product_produce.xml @@ -0,0 +1,30 @@ + + + + + MRP Product Produce + mrp.product.produce + + + + + + True + + + + + + + + + + + + + + + + +