From 15bf2a3d9b2ab425d94e8c485a921c295504ecf3 Mon Sep 17 00:00:00 2001 From: Jordi Ballester Alomar Date: Thu, 26 Mar 2020 10:52:38 +0100 Subject: [PATCH 1/7] Improving the management of planned orders: * Add menu entry for planned orders * Add button to navigate from planned orders to linked manufacturing orders * Add action to convert planned orders to fixed * When changing the due date in a planned order the release date is recomputed --- mrp_multi_level/__manifest__.py | 29 ++-- mrp_multi_level/models/mrp_planned_order.py | 38 +++++- .../views/mrp_planned_order_views.xml | 125 ++++++++++++++++++ 3 files changed, 177 insertions(+), 15 deletions(-) create mode 100644 mrp_multi_level/views/mrp_planned_order_views.xml diff --git a/mrp_multi_level/__manifest__.py b/mrp_multi_level/__manifest__.py index 105467870..72f0722ca 100644 --- a/mrp_multi_level/__manifest__.py +++ b/mrp_multi_level/__manifest__.py @@ -10,7 +10,7 @@ 'author': 'Ucamco, ' 'Eficent, ' 'Odoo Community Association (OCA)', - 'maintainers': ['jbeficent', 'lreficent'], + "maintainers": ["JordiBForgeFlow", "LoisRForgeFlow"], 'summary': 'Adds an MRP Scheduler', 'website': 'https://github.com/OCA/manufacture', 'category': 'Manufacturing', @@ -20,19 +20,20 @@ 'mrp_warehouse_calendar', ], 'data': [ - 'security/mrp_multi_level_security.xml', - 'security/ir.model.access.csv', - 'views/mrp_area_views.xml', - 'views/product_product_views.xml', - 'views/product_template_views.xml', - 'views/product_mrp_area_views.xml', - 'views/stock_location_views.xml', - 'wizards/mrp_inventory_procure_views.xml', - 'views/mrp_inventory_views.xml', - 'wizards/mrp_multi_level_views.xml', - 'views/mrp_menuitem.xml', - 'data/mrp_multi_level_cron.xml', - 'data/mrp_area_data.xml', + "security/mrp_multi_level_security.xml", + "security/ir.model.access.csv", + "views/mrp_area_views.xml", + "views/product_product_views.xml", + "views/product_template_views.xml", + "views/product_mrp_area_views.xml", + "views/stock_location_views.xml", + "wizards/mrp_inventory_procure_views.xml", + "views/mrp_inventory_views.xml", + "views/mrp_planned_order_views.xml", + "wizards/mrp_multi_level_views.xml", + "views/mrp_menuitem.xml", + "data/mrp_multi_level_cron.xml", + "data/mrp_area_data.xml", ], 'demo': [ 'demo/product_category_demo.xml', diff --git a/mrp_multi_level/models/mrp_planned_order.py b/mrp_multi_level/models/mrp_planned_order.py index 9959788c5..4fbc9389b 100644 --- a/mrp_multi_level/models/mrp_planned_order.py +++ b/mrp_multi_level/models/mrp_planned_order.py @@ -1,8 +1,9 @@ # Copyright 2019 Eficent Business and IT Consulting Services S.L. # - Lois Rilo Antelo # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from datetime import timedelta -from odoo import models, fields +from odoo import api, fields, models class MrpPlannedOrder(models.Model): @@ -68,3 +69,38 @@ class MrpPlannedOrder(models.Model): comodel_name="mrp.inventory", ondelete="set null", ) + mrp_production_ids = fields.One2many( + "mrp.production", "planned_order_id", string="Manufacturing Orders" + ) + mo_count = fields.Integer(compute="_compute_mrp_production_count") + + def _compute_mrp_production_count(self): + for rec in self: + rec.mo_count = len(rec.mrp_production_ids) + + @api.onchange("due_date") + def _onchange_due_date(self): + if self.due_date: + if self.product_mrp_area_id.mrp_lead_time: + calendar = self.mrp_area_id.calendar_id + if calendar: + dt = fields.Datetime.from_string(self.due_date) + res = calendar.plan_days( + -1 * (self.product_mrp_area_id.mrp_lead_time + 1), dt + ) + self.order_release_date = res.date() + else: + self.order_release_date = fields.Date.from_string( + self.due_date + ) - timedelta(days=self.product_mrp_area_id.mrp_lead_time) + + def action_toggle_fixed(self): + for rec in self: + rec.fixed = not rec.fixed + + def action_open_linked_mrp_production(self): + action = self.env.ref("mrp.mrp_production_action") + result = action.read()[0] + result["context"] = {} + result["domain"] = "[('id','in',%s)]" % self.mrp_production_ids.ids + return result diff --git a/mrp_multi_level/views/mrp_planned_order_views.xml b/mrp_multi_level/views/mrp_planned_order_views.xml new file mode 100644 index 000000000..62e265b11 --- /dev/null +++ b/mrp_multi_level/views/mrp_planned_order_views.xml @@ -0,0 +1,125 @@ + + + + + mrp.planned.order.tree + mrp.planned.order + + + + + + + + + + + + + + + mrp.planned.order.form + mrp.planned.order + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+ + mrp.planned.order.pivot + mrp.planned.order + + + + + + + + + + + mrp.planned.order.search + mrp.planned.order + + + + + + + + + + + + + + + Planned Orders + mrp.planned.order + ir.actions.act_window + tree,form,pivot + {'search_default_fixed': 1} + + + + Toggle Fixed + + + code + records.action_toggle_fixed() + + + Procure + mrp.inventory.procure + form + new + + +
From 9c30e585c84eb915e602d22d952650ca73cb6ae0 Mon Sep 17 00:00:00 2001 From: hveficent Date: Fri, 31 Jan 2020 13:11:20 +0100 Subject: [PATCH 2/7] [IMP] mrp_multi_level: Show supply method on mrp inventory --- mrp_multi_level/models/mrp_inventory.py | 6 ++++ mrp_multi_level/views/mrp_inventory_views.xml | 5 +++ .../wizards/mrp_inventory_procure.py | 35 +++++++++++++------ .../wizards/mrp_inventory_procure_views.xml | 1 + 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/mrp_multi_level/models/mrp_inventory.py b/mrp_multi_level/models/mrp_inventory.py index 75ae6b023..43c44c9fe 100644 --- a/mrp_multi_level/models/mrp_inventory.py +++ b/mrp_multi_level/models/mrp_inventory.py @@ -67,6 +67,12 @@ class MrpInventory(models.Model): inverse_name="mrp_inventory_id", readonly=True, ) + supply_method = fields.Selection( + string="Supply Method", + related="product_mrp_area_id.supply_method", + readonly=True, + store=True, + ) @api.multi def _compute_uom_id(self): diff --git a/mrp_multi_level/views/mrp_inventory_views.xml b/mrp_multi_level/views/mrp_inventory_views.xml index 9903d1ab3..6d51b932a 100644 --- a/mrp_multi_level/views/mrp_inventory_views.xml +++ b/mrp_multi_level/views/mrp_inventory_views.xml @@ -14,6 +14,7 @@ + @@ -51,6 +52,7 @@ name="%(mrp_multi_level.act_mrp_inventory_procure)d" icon="fa-cogs" type="action" attrs="{'invisible':[('to_procure','<=',0.0)]}"/> + @@ -103,6 +105,9 @@ + diff --git a/mrp_multi_level/wizards/mrp_inventory_procure.py b/mrp_multi_level/wizards/mrp_inventory_procure.py index e107a6ba6..54d677e30 100644 --- a/mrp_multi_level/wizards/mrp_inventory_procure.py +++ b/mrp_multi_level/wizards/mrp_inventory_procure.py @@ -19,16 +19,16 @@ class MrpInventoryProcure(models.TransientModel): @api.model def _prepare_item(self, planned_order): return { - 'planned_order_id': planned_order.id, - 'qty': planned_order.mrp_qty - planned_order.qty_released, - 'uom_id': planned_order.mrp_inventory_id.uom_id.id, - 'date_planned': planned_order.due_date, - 'mrp_inventory_id': planned_order.mrp_inventory_id.id, - 'product_id': planned_order.product_id.id, - 'warehouse_id': planned_order.mrp_area_id.warehouse_id.id, - 'location_id': - planned_order.product_mrp_area_id.location_proc_id.id or - planned_order.mrp_area_id.location_id.id, + "planned_order_id": planned_order.id, + "qty": planned_order.mrp_qty - planned_order.qty_released, + "uom_id": planned_order.mrp_inventory_id.uom_id.id, + "date_planned": planned_order.due_date, + "mrp_inventory_id": planned_order.mrp_inventory_id.id, + "product_id": planned_order.product_id.id, + "warehouse_id": planned_order.mrp_area_id.warehouse_id.id, + "location_id": planned_order.product_mrp_area_id.location_proc_id.id + or planned_order.mrp_area_id.location_id.id, + "supply_method": planned_order.product_mrp_area_id.supply_method, } @api.model @@ -126,6 +126,21 @@ class MrpInventoryProcureItem(models.TransientModel): string='Location', comodel_name='stock.location', ) + planned_order_id = fields.Many2one(comodel_name="mrp.planned.order") + product_id = fields.Many2one(string="Product", comodel_name="product.product") + warehouse_id = fields.Many2one(string="Warehouse", comodel_name="stock.warehouse") + location_id = fields.Many2one(string="Location", comodel_name="stock.location") + supply_method = fields.Selection( + string="Supply Method", + selection=[ + ("buy", "Buy"), + ("none", "Undefined"), + ("manufacture", "Produce"), + ("pull", "Pull From"), + ("push", "Push To"), + ("pull_push", "Pull & Push"), + ], + ) def _prepare_procurement_values(self, group=False): return { diff --git a/mrp_multi_level/wizards/mrp_inventory_procure_views.xml b/mrp_multi_level/wizards/mrp_inventory_procure_views.xml index 482bf5dc8..5002bdb10 100644 --- a/mrp_multi_level/wizards/mrp_inventory_procure_views.xml +++ b/mrp_multi_level/wizards/mrp_inventory_procure_views.xml @@ -23,6 +23,7 @@ + From 48f52582c89b9251bfddcdcbee68c178482abae1 Mon Sep 17 00:00:00 2001 From: hveficent Date: Wed, 5 Feb 2020 10:09:52 +0100 Subject: [PATCH 3/7] [FIX] Allow no-MRP users to look into Products --- mrp_multi_level/security/ir.model.access.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/mrp_multi_level/security/ir.model.access.csv b/mrp_multi_level/security/ir.model.access.csv index e709cbec1..ed7fba05f 100644 --- a/mrp_multi_level/security/ir.model.access.csv +++ b/mrp_multi_level/security/ir.model.access.csv @@ -7,5 +7,6 @@ access_mrp_area_user,mrp.area user,model_mrp_area,mrp.group_mrp_user,1,0,0,0 access_mrp_area_manager,mrp.area manager,model_mrp_area,mrp.group_mrp_manager,1,1,1,1 access_product_mrp_area_user,product.mrp.area user,model_product_mrp_area,mrp.group_mrp_user,1,1,1,0 access_product_mrp_area_manager,product.mrp.area manager,model_product_mrp_area,mrp.group_mrp_manager,1,1,1,1 +access_product_mrp_area_read,product.mrp.area read,model_product_mrp_area,base.group_user,1,0,0,0 access_mrp_planned_order_user,mrp.planned.order user,model_mrp_planned_order,mrp.group_mrp_user,1,0,0,0 access_mrp_planned_order_manager,mrp.planned.order manager,model_mrp_planned_order,mrp.group_mrp_manager,1,1,1,1 From f76aec8e9523b0fae91a74557eead10de2f6ae56 Mon Sep 17 00:00:00 2001 From: hveficent Date: Mon, 2 Mar 2020 17:13:45 +0100 Subject: [PATCH 4/7] [IMP] mrp_multi_level: * Planned Order release and due date become required. * Add button to Product MRP Area to update MOQ from Supplier Info. * Link Manufacturing Orders with Planned Orders. * Allow Mrp Inventory Procure Wizard to be used from other models. * Make MRP Inventory creation more extensible. * Main Supplier computation (v13 requires explicit False definitions). --- mrp_multi_level/models/__init__.py | 2 + mrp_multi_level/models/mrp_planned_order.py | 4 +- mrp_multi_level/models/mrp_production.py | 12 +++++ mrp_multi_level/models/product_mrp_area.py | 11 ++++ mrp_multi_level/models/stock_rule.py | 33 ++++++++++++ mrp_multi_level/tests/test_mrp_multi_level.py | 9 ++-- .../views/product_mrp_area_views.xml | 12 ++++- .../wizards/mrp_inventory_procure.py | 46 ++++++++-------- mrp_multi_level/wizards/mrp_multi_level.py | 54 +++++++++++++------ 9 files changed, 136 insertions(+), 47 deletions(-) create mode 100644 mrp_multi_level/models/mrp_production.py create mode 100644 mrp_multi_level/models/stock_rule.py diff --git a/mrp_multi_level/models/__init__.py b/mrp_multi_level/models/__init__.py index 486079907..0ebdf3353 100644 --- a/mrp_multi_level/models/__init__.py +++ b/mrp_multi_level/models/__init__.py @@ -6,3 +6,5 @@ from . import mrp_move from . import mrp_planned_order from . import mrp_inventory from . import product_mrp_area +from . import stock_rule +from . import mrp_production diff --git a/mrp_multi_level/models/mrp_planned_order.py b/mrp_multi_level/models/mrp_planned_order.py index 4fbc9389b..e3ea1f67b 100644 --- a/mrp_multi_level/models/mrp_planned_order.py +++ b/mrp_multi_level/models/mrp_planned_order.py @@ -38,12 +38,12 @@ class MrpPlannedOrder(models.Model): readonly=True, ) order_release_date = fields.Date( - string="Release Date", - help="Order release date planned by MRP.", + string="Release Date", help="Order release date planned by MRP.", required=True ) due_date = fields.Date( string="Due Date", help="Date in which the supply must have been completed.", + required=True, ) qty_released = fields.Float() fixed = fields.Boolean() diff --git a/mrp_multi_level/models/mrp_production.py b/mrp_multi_level/models/mrp_production.py new file mode 100644 index 000000000..53601bf21 --- /dev/null +++ b/mrp_multi_level/models/mrp_production.py @@ -0,0 +1,12 @@ +# Copyright 2020 ForgeFlow S.L. (https://www.forgeflow.com) +# - Héctor Villarreal +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo import fields, models + + +class MrpProduction(models.Model): + """ Manufacturing Orders """ + + _inherit = "mrp.production" + + planned_order_id = fields.Many2one(comodel_name="mrp.planned.order") diff --git a/mrp_multi_level/models/product_mrp_area.py b/mrp_multi_level/models/product_mrp_area.py index 9ad15df03..053612457 100644 --- a/mrp_multi_level/models/product_mrp_area.py +++ b/mrp_multi_level/models/product_mrp_area.py @@ -190,9 +190,14 @@ class ProductMRPArea(models.Model): and (not r.company_id or r.company_id == rec.company_id) ) if not suppliers: + rec.main_supplierinfo_id = False + rec.main_supplier_id = False continue rec.main_supplierinfo_id = suppliers[0] rec.main_supplier_id = suppliers[0].name + for rec in self.filtered(lambda r: r.supply_method != "buy"): + rec.main_supplierinfo_id = False + rec.main_supplier_id = False @api.multi def _adjust_qty_to_order(self, qty_to_order): @@ -209,3 +214,9 @@ class ProductMRPArea(models.Model): self.mrp_maximum_order_qty: return self.mrp_maximum_order_qty return qty_to_order + + def update_min_qty_from_main_supplier(self): + for rec in self.filtered( + lambda r: r.main_supplierinfo_id and r.supply_method == "buy" + ): + rec.mrp_minimum_order_qty = rec.main_supplierinfo_id.min_qty diff --git a/mrp_multi_level/models/stock_rule.py b/mrp_multi_level/models/stock_rule.py new file mode 100644 index 000000000..8d24460c1 --- /dev/null +++ b/mrp_multi_level/models/stock_rule.py @@ -0,0 +1,33 @@ +# Copyright 2020 ForgeFlow S.L. (https://www.forgeflow.com) +# - Héctor Villarreal +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo import models + + +class StockRule(models.Model): + _inherit = "stock.rule" + + def _prepare_mo_vals( + self, + product_id, + product_qty, + product_uom, + location_id, + name, + origin, + values, + bom, + ): + res = super()._prepare_mo_vals( + product_id, + product_qty, + product_uom, + location_id, + name, + origin, + values, + bom, + ) + if "planned_order_id" in values: + res["planned_order_id"] = values["planned_order_id"] + return res diff --git a/mrp_multi_level/tests/test_mrp_multi_level.py b/mrp_multi_level/tests/test_mrp_multi_level.py index 027d796a0..312232406 100644 --- a/mrp_multi_level/tests/test_mrp_multi_level.py +++ b/mrp_multi_level/tests/test_mrp_multi_level.py @@ -24,9 +24,12 @@ class TestMrpMultiLevel(TestMrpMultiLevelCommon): self.assertEqual(product_mrp_area.supply_method, 'buy') self.assertEqual(product_mrp_area.main_supplier_id, self.vendor) self.assertEqual(product_mrp_area.qty_available, 10.0) - product_mrp_area = self.product_mrp_area_obj.search([ - ('product_id', '=', self.sf_1.id)]) - self.assertEqual(product_mrp_area.supply_method, 'manufacture') + product_mrp_area = self.product_mrp_area_obj.search( + [("product_id", "=", self.sf_1.id)] + ) + self.assertEqual(product_mrp_area.supply_method, "manufacture") + self.assertFalse(product_mrp_area.main_supplier_id) + self.assertFalse(product_mrp_area.main_supplierinfo_id) def test_03_mrp_moves(self): """Tests for mrp moves generated.""" diff --git a/mrp_multi_level/views/product_mrp_area_views.xml b/mrp_multi_level/views/product_mrp_area_views.xml index 2c315c2b7..e4d1f7510 100644 --- a/mrp_multi_level/views/product_mrp_area_views.xml +++ b/mrp_multi_level/views/product_mrp_area_views.xml @@ -57,7 +57,17 @@ - +