diff --git a/mrp_production_grouped_by_product/README.rst b/mrp_production_grouped_by_product/README.rst new file mode 100644 index 000000000..1833e03d7 --- /dev/null +++ b/mrp_production_grouped_by_product/README.rst @@ -0,0 +1,56 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +============================= +Production Grouped By Product +============================= + +Groups pending productions by product. + +Configuration +============= + + +Usage +===== + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/129/11.0 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, +please check there if your issue has already been reported. If you spotted it +first, help us smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* David Vidal + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/mrp_production_grouped_by_product/__init__.py b/mrp_production_grouped_by_product/__init__.py new file mode 100644 index 000000000..a9e337226 --- /dev/null +++ b/mrp_production_grouped_by_product/__init__.py @@ -0,0 +1,2 @@ + +from . import models diff --git a/mrp_production_grouped_by_product/__manifest__.py b/mrp_production_grouped_by_product/__manifest__.py new file mode 100644 index 000000000..3815ef003 --- /dev/null +++ b/mrp_production_grouped_by_product/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2018 Tecnativa - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Production Grouped By Product', + 'version': '11.0.1.0.0', + 'category': 'MRP', + 'author': 'Tecnativa, ' + 'Odoo Community Association (OCA)', + 'website': 'https://github.com/oca/manufacture', + 'license': 'AGPL-3', + 'depends': [ + 'mrp', + ], + 'data': [ + ], + 'installable': True, +} diff --git a/mrp_production_grouped_by_product/models/__init__.py b/mrp_production_grouped_by_product/models/__init__.py new file mode 100644 index 000000000..29b455422 --- /dev/null +++ b/mrp_production_grouped_by_product/models/__init__.py @@ -0,0 +1,3 @@ + +from . import mrp_production +from . import procurement diff --git a/mrp_production_grouped_by_product/models/mrp_production.py b/mrp_production_grouped_by_product/models/mrp_production.py new file mode 100644 index 000000000..d6e97b1a0 --- /dev/null +++ b/mrp_production_grouped_by_product/models/mrp_production.py @@ -0,0 +1,15 @@ +# Copyright 2018 Tecnativa - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class MrpProduction(models.Model): + _inherit = 'mrp.production' + + @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') diff --git a/mrp_production_grouped_by_product/models/procurement.py b/mrp_production_grouped_by_product/models/procurement.py new file mode 100644 index 000000000..de99e50d7 --- /dev/null +++ b/mrp_production_grouped_by_product/models/procurement.py @@ -0,0 +1,44 @@ +# Copyright 2018 Tecnativa - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ProcuermentRule(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) diff --git a/mrp_production_grouped_by_product/tests/__init__.py b/mrp_production_grouped_by_product/tests/__init__.py new file mode 100644 index 000000000..66f012923 --- /dev/null +++ b/mrp_production_grouped_by_product/tests/__init__.py @@ -0,0 +1,2 @@ + +from . import test_mrp_production_grouped_by_product 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 new file mode 100644 index 000000000..7ffa9fc64 --- /dev/null +++ b/mrp_production_grouped_by_product/tests/test_mrp_production_grouped_by_product.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Tecnativa - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests import common + + +class TestProductionGroupedByProduct(common.SavepointCase): + + @classmethod + def setUpClass(cls): + super(TestProductionGroupedByProduct, cls).setUpClass() + cls.product1 = cls.env['product.product'].create({ + 'name': 'TEST Muffin', + 'route_ids': [(6, 0, [ + cls.env.ref('mrp.route_warehouse0_manufacture').id])], + 'type': 'product', + }) + cls.product2 = cls.env['product.product'].create({ + 'name': 'TEST Paper muffin cup', + 'type': 'product', + }) + cls.product3 = cls.env['product.product'].create({ + 'name': 'TEST Muffin paset', + 'type': 'product', + }) + cls.bom = cls.env['mrp.bom'].create({ + 'product_id': cls.product1.id, + 'product_tmpl_id': cls.product1.product_tmpl_id.id, + 'type': 'normal', + 'bom_line_ids': [(0, 0, { + 'product_id': cls.product2.id, + 'product_qty': 1, + }), (0, 0, { + 'product_id': cls.product3.id, + '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', + 'product_id': cls.product1.id, + 'product_min_qty': 10, + 'product_max_qty': 100, + }) + + 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, + }) + move = self.env['stock.move'].create({ + 'name': self.product1.name, + 'product_id': self.product1.id, + 'product_uom_qty': 1000, + '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, + }) + 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), + ]) + self.assertEqual(len(mo), 1)