diff --git a/mrp_production_grouped_by_product/README.rst b/mrp_production_grouped_by_product/README.rst index 1833e03d7..40d01b606 100644 --- a/mrp_production_grouped_by_product/README.rst +++ b/mrp_production_grouped_by_product/README.rst @@ -6,11 +6,14 @@ Production Grouped By Product ============================= -Groups pending productions by product. - -Configuration -============= +When you have several sales orders with make to (MTO) order products that +require to be manufactured, you end up with one manufacturing order for each of +these sales orders, which is very bad for the management. +With this module, each time an MTO manufacturing order is required to be +created, it first checks that there's no other existing order not yet started +for the same product and bill of materials, and if there's one, then the +quantity of that order is increased instead of creating a new one. Usage ===== @@ -19,6 +22,11 @@ Usage :alt: Try me on Runbot :target: https://runbot.odoo-community.org/runbot/129/11.0 +Known issues / Roadmap +====================== + +* Add a check in the product form for excluding it from being grouped. + Bug Tracker =========== @@ -38,7 +46,12 @@ Images Contributors ------------ -* David Vidal +* Tecnativa _ + + * David Vidal + * Pedro M. Baeza + +Do not contact contributors directly about support or help with technical issues. Maintainer ---------- diff --git a/mrp_production_grouped_by_product/__manifest__.py b/mrp_production_grouped_by_product/__manifest__.py index 3815ef003..78138907c 100644 --- a/mrp_production_grouped_by_product/__manifest__.py +++ b/mrp_production_grouped_by_product/__manifest__.py @@ -1,4 +1,5 @@ # Copyright 2018 Tecnativa - David Vidal +# Copyright 2018 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { diff --git a/mrp_production_grouped_by_product/models/mrp_production.py b/mrp_production_grouped_by_product/models/mrp_production.py index d6e97b1a0..f9572f142 100644 --- a/mrp_production_grouped_by_product/models/mrp_production.py +++ b/mrp_production_grouped_by_product/models/mrp_production.py @@ -1,15 +1,51 @@ # Copyright 2018 Tecnativa - David Vidal +# Copyright 2018 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models +from odoo import api, models +from odoo.tools import config class MrpProduction(models.Model): _inherit = 'mrp.production' + def _post_mo_merging_adjustments(self, vals): + """Called when a new MO is merged onto existing one for adjusting the + needed values according this merging. + + :param self: Single record of the target record where merging. + :param vals: Dictionary with the new record values. + """ + self.ensure_one() + new_vals = { + 'origin': (self.origin or '') + ",%s" % vals['origin'], + } + if vals.get('move_dest_ids'): + new_vals['move_dest_ids'] = vals['move_dest_ids'] + self.move_finished_ids.move_dest_ids = vals['move_dest_ids'] + self.write(new_vals) + + def _find_grouping_target(self, vals): + mo = self.env['mrp.production'].search([ + ('product_id', '=', vals['product_id']), + ('bom_id', '=', vals.get('bom_id', False)), + ('routing_id', '=', vals.get('routing_id', False)), + ('company_id', '=', vals.get('company_id', False)), + ('state', '=', 'confirmed'), + ], limit=1) + return mo + @api.model def create(self, vals): - if not self._context.get('merge_products_into_mo'): - return super(MrpProduction, self).create(vals) - # We return the MO to merge into - return self._context.get('merge_products_into_mo') + context = self.env.context + if (context.get('group_mo_by_product') and + (not config['test_enable'] or context.get('test_group_mo'))): + mo = self._find_grouping_target(vals) + if mo: + self.env['change.production.qty'].create({ + 'mo_id': mo.id, + 'product_qty': mo.product_qty + vals['product_qty'], + }).change_prod_qty() + mo._post_mo_merging_adjustments(vals) + return mo + return super(MrpProduction, self).create(vals) diff --git a/mrp_production_grouped_by_product/models/procurement.py b/mrp_production_grouped_by_product/models/procurement.py index de99e50d7..19368adc0 100644 --- a/mrp_production_grouped_by_product/models/procurement.py +++ b/mrp_production_grouped_by_product/models/procurement.py @@ -1,44 +1,18 @@ # Copyright 2018 Tecnativa - David Vidal +# Copyright 2018 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models +from odoo import models -class ProcuermentRule(models.Model): +class ProcurementRule(models.Model): _inherit = 'procurement.rule' def _run_manufacture(self, product_id, product_qty, product_uom, location_id, name, origin, values): - bom = self._get_matching_bom(product_id, values) - # Send to super for exception - if not bom: - return super(ProcuermentRule, self)._run_manufacture( - product_id, product_qty, product_uom, location_id, - name, origin, values) - open_mo = self._find_equal_open_mo( - product_id, bom, location_id, values) - # Create mo as usual - if not open_mo: - return super(ProcuermentRule, self)._run_manufacture( - product_id, product_qty, product_uom, location_id, - name, origin, values) - # Add product qty to mo - self.env['change.production.qty'].create({ - 'mo_id': open_mo.id, - 'product_qty': open_mo.product_qty + product_qty, - }).change_prod_qty() - # We pass the record in the context so the chatter is correctly written - additional_context={'merge_products_into_mo': open_mo} - return super(ProcuermentRule, self.with_context(**additional_context) - )._run_manufacture(product_id, product_qty, product_uom, - location_id,name, origin, values) - - def _find_equal_open_mo(self, product_id, bom, location_id, values): - """Returns the first occurrence according to conditions""" - return self.env['mrp.production'].search([ - ('state', 'not in', ['progress', 'done', 'cancel']), - ('product_id', '=', product_id.id), - ('bom_id', '=', bom.id), - ('location_dest_id', '=', location_id.id), - ('company_id', '=', values.get('company_id').id), - ], limit=1) + return super( + ProcurementRule, self.with_context(group_mo_by_product=True), + )._run_manufacture( + product_id, product_qty, product_uom, location_id, name, origin, + values, + ) diff --git a/mrp_production_grouped_by_product/tests/test_mrp_production_grouped_by_product.py b/mrp_production_grouped_by_product/tests/test_mrp_production_grouped_by_product.py index 7ffa9fc64..517d9cfc6 100644 --- a/mrp_production_grouped_by_product/tests/test_mrp_production_grouped_by_product.py +++ b/mrp_production_grouped_by_product/tests/test_mrp_production_grouped_by_product.py @@ -1,11 +1,13 @@ -# -*- coding: utf-8 -*- -# Copyright 2017 Tecnativa - David Vidal +# Copyright 2018 Tecnativa - David Vidal +# Copyright 2018 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo.tests import common class TestProductionGroupedByProduct(common.SavepointCase): + at_install = False + post_install = True @classmethod def setUpClass(cls): @@ -36,14 +38,6 @@ class TestProductionGroupedByProduct(common.SavepointCase): 'product_qty': 0.2, })] }) - cls.env['stock.change.product.qty'].create({ - 'product_id': cls.product2.id, - 'new_quantity': 100.0, - }).change_product_qty() - cls.env['stock.change.product.qty'].create({ - 'product_id': cls.product3.id, - 'new_quantity': 100.0, - }).change_product_qty() cls.stock_picking_type = cls.env.ref('stock.picking_type_out') cls.procurement_rule = cls.env['stock.warehouse.orderpoint'].create({ 'name': 'XXX/00000', @@ -51,35 +45,43 @@ class TestProductionGroupedByProduct(common.SavepointCase): 'product_min_qty': 10, 'product_max_qty': 100, }) + cls.mo = cls.env['mrp.production'].create({ + 'bom_id': cls.bom.id, + 'product_id': cls.product1.id, + 'product_qty': 2, + 'product_uom_id': cls.product1.uom_id.id, + }) + cls.warehouse = cls.env['stock.warehouse'].search([ + ('company_id', '=', cls.env.user.company_id.id), + ], limit=1) + cls.ProcurementGroup = cls.env['procurement.group'] + cls.MrpProduction = cls.env['mrp.production'] def test_mo_by_product(self): - self.env['procurement.group'].run_scheduler() - mo = self.env['mrp.production'].search([ - ('product_id', '=', self.product1.id), - ]) - self.assertTrue(mo) - # - picking = self.env['stock.picking'].create({ - 'picking_type_id': self.stock_picking_type.id, - 'location_id': self.env.ref('stock.stock_location_stock').id, - 'location_dest_id': self.env.ref( - 'stock.stock_location_customers').id, - }) + self.ProcurementGroup.with_context(test_group_mo=True).run_scheduler() + mo = self.MrpProduction.search([('product_id', '=', self.product1.id)]) + self.assertEqual(len(mo), 1) + self.assertEqual(mo.product_qty, 100) + # Add an MTO move move = self.env['stock.move'].create({ 'name': self.product1.name, 'product_id': self.product1.id, - 'product_uom_qty': 1000, + 'product_uom_qty': 10, 'product_uom': self.product1.uom_id.id, - 'picking_id': picking.id, - 'picking_type_id': self.stock_picking_type.id, - 'location_id': picking.location_id.id, - 'location_dest_id': picking.location_id.id, + 'location_id': self.warehouse.lot_stock_id.id, + 'location_dest_id': ( + self.env.ref('stock.stock_location_customers').id + ), + 'procure_method': 'make_to_order', + 'warehouse_id': self.warehouse.id, }) - move.quantity_done = 1000 - picking.action_assign() - self.product1.virtual_available = -500 - self.env['procurement.group'].run_scheduler() - mo = self.env['mrp.production'].search([ - ('product_id', '=', self.product1.id), - ]) + move.with_context(test_group_mo=True)._action_confirm(merge=False) + self.ProcurementGroup.with_context(test_group_mo=True).run_scheduler() + mo = self.MrpProduction.search([('product_id', '=', self.product1.id)]) self.assertEqual(len(mo), 1) + self.assertEqual(mo.product_qty, 110) + # Run again the scheduler to see if quantities are altered + self.ProcurementGroup.with_context(test_group_mo=True).run_scheduler() + mo = self.MrpProduction.search([('product_id', '=', self.product1.id)]) + self.assertEqual(len(mo), 1) + self.assertEqual(mo.product_qty, 110)