From 87d33596d11cf2e287b4c10736d49d782bf6d6f0 Mon Sep 17 00:00:00 2001 From: Nicolas Pierre Date: Fri, 18 Mar 2022 09:09:13 +0000 Subject: [PATCH] [IMP] mrp, purchase_stock: visibility days and days to prepare MO - Add the concept of 'Days to Prepare MO' in mrp, similar to 'Days to Purchase' but defined at the product level. Both concepts are merged into Days to Order at the orderpoint level, taking the value of Days to Purchase for orderpoints with a buy route and Days to Prepare MO for orderpoint with a manufacturing route. - Add the concept of 'Visibility Days' on orderpoints. The idea is to avoid to create through reordering rules multiple small orders for the same product on a small time frame but rather to order directly a bigger quantity. When Visibility Days are defined, the quantity of the orders created by a RR is not the quantity forecasted at Today + Lead times (quantity that triggered the order according to the RR), but the quantity forecasted at Today + Lead Times + Visibility days. The Visibility Days are different for purchase and manufacturing. Changing the route of the orderpoint will change its value. They can be defined at the company level and further fine-tuned on the orderpoint. In case of change of the parameters at the company level, only orderpoints without a specific value of Visibility Days will be changed. - Alignement between Manufacturing and Purchase Security Lead Time (previously the Manufacturing Security Lead time didn't impact the procurement date, instead the manufacturing time was made longer). closes odoo/odoo#86682 Task-id: 2738838 Pr: https://github.com/odoo/odoo/pull/86682 Related: odoo/upgrade#3415 Signed-off-by: William Henrotin (whe) --- addons/mrp/models/mrp_production.py | 3 +- addons/mrp/models/product.py | 4 +++ addons/mrp/models/stock_move.py | 7 ++++ addons/mrp/models/stock_orderpoint.py | 29 ++++++++++++++++ addons/mrp/models/stock_rule.py | 3 +- addons/mrp/views/product_views.xml | 4 +++ addons/purchase_mrp/models/stock_move.py | 7 ++++ addons/purchase_stock/models/__init__.py | 2 +- .../models/res_config_settings.py | 1 - addons/purchase_stock/models/stock.py | 21 ++++++++++++ addons/purchase_stock/models/stock_rule.py | 10 ++---- .../tests/test_purchase_lead_time.py | 5 +-- addons/stock/models/stock_move.py | 5 ++- addons/stock/models/stock_orderpoint.py | 33 +++++++++++++++---- addons/stock/models/stock_rule.py | 4 ++- addons/stock/views/stock_orderpoint_views.xml | 27 ++------------- 16 files changed, 118 insertions(+), 47 deletions(-) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index fb1a1131def..b0cdaa89b2d 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -658,7 +658,7 @@ class MrpProduction(models.Model): for production in self: if not production.date_planned_start or production.is_planned: continue - days_delay = production.product_id.produce_delay + production.company_id.manufacturing_lead + days_delay = production.product_id.produce_delay date_planned_finished = production.date_planned_start + relativedelta(days=days_delay) if date_planned_finished == production.date_planned_start: date_planned_finished = date_planned_finished + relativedelta(hours=1) @@ -904,7 +904,6 @@ class MrpProduction(models.Model): def _get_date_planned_finished(self): date_planned_finished = self.date_planned_start + relativedelta(days=self.product_id.produce_delay) - date_planned_finished = date_planned_finished + relativedelta(days=self.company_id.manufacturing_lead) if date_planned_finished == self.date_planned_start: date_planned_finished = date_planned_finished + relativedelta(hours=1) return date_planned_finished diff --git a/addons/mrp/models/product.py b/addons/mrp/models/product.py index 787c5c5c8bd..eeaf8deba20 100644 --- a/addons/mrp/models/product.py +++ b/addons/mrp/models/product.py @@ -33,6 +33,10 @@ class ProductTemplate(models.Model): 'Manufacturing Lead Time', default=0.0, help="Average lead time in days to manufacture this product. In the case of multi-level BOM, the manufacturing lead times of the components will be added. In case the product is subcontracted, this can be used to determine the date at which components should be sent to the subcontractor.") is_kits = fields.Boolean(compute='_compute_is_kits', compute_sudo=False) + days_to_prepare_mo = fields.Float( + string="Days to prepare Manufacturing Order", default=0.0, + help="Create and confirm Manufacturing Orders these many days in advance, to have enough time to replenish components or manufacture semi-finished products.\n" + "Note that this does not affect the MO scheduled date, which still respects the just-in-time mechanism.") def _compute_bom_count(self): for product in self: diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 77660241770..3ae798e8048 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from collections import defaultdict +from dateutil.relativedelta import relativedelta from odoo import api, Command, fields, models from odoo.osv import expression from odoo.tools import float_compare, float_round, float_is_zero, OrderedSet @@ -547,3 +548,9 @@ class StockMove(models.Model): res = super()._prepare_procurement_values() res['bom_line_id'] = self.bom_line_id.id return res + + def _get_mto_procurement_date(self): + date = super()._get_mto_procurement_date() + if 'manufacture' in self.product_id._get_rules_from_location(self.location_id).mapped('action'): + date -= relativedelta(days=self.company_id.manufacturing_lead) + return date diff --git a/addons/mrp/models/stock_orderpoint.py b/addons/mrp/models/stock_orderpoint.py index 2e5022c7d27..eb4ac948bc3 100644 --- a/addons/mrp/models/stock_orderpoint.py +++ b/addons/mrp/models/stock_orderpoint.py @@ -4,6 +4,7 @@ from odoo import _, api, fields, models from odoo.tools.float_utils import float_is_zero from odoo.osv.expression import AND +from dateutil.relativedelta import relativedelta class StockWarehouseOrderpoint(models.Model): @@ -13,6 +14,7 @@ class StockWarehouseOrderpoint(models.Model): bom_id = fields.Many2one( 'mrp.bom', string='Bill of Materials', check_company=True, domain="[('type', '=', 'normal'), '&', '|', ('company_id', '=', company_id), ('company_id', '=', False), '|', ('product_id', '=', product_id), '&', ('product_id', '=', False), ('product_tmpl_id', '=', product_tmpl_id)]") + manufacturing_visibility_days = fields.Float(default=0.0, help="Visibility Days applied on the manufacturing routes.") def _get_replenishment_order_notification(self): self.ensure_one() @@ -45,6 +47,27 @@ class StockWarehouseOrderpoint(models.Model): for orderpoint in self: orderpoint.show_bom = orderpoint.route_id.id in manufacture_route + def _compute_visibility_days(self): + res = super()._compute_visibility_days() + for orderpoint in self: + if 'manufacture' in orderpoint.rule_ids.mapped('action'): + orderpoint.visibility_days = orderpoint.manufacturing_visibility_days + return res + + def _set_visibility_days(self): + res = super()._set_visibility_days() + for orderpoint in self: + if 'manufacture' in orderpoint.rule_ids.mapped('action'): + orderpoint.manufacturing_visibility_days = orderpoint.visibility_days + return res + + def _compute_days_to_order(self): + res = super()._compute_days_to_order() + for orderpoint in self: + if 'manufacture' in orderpoint.rule_ids.mapped('action'): + orderpoint.days_to_order = orderpoint.product_id.days_to_prepare_mo + return res + def _quantity_in_progress(self): bom_kits = self.env['mrp.bom']._bom_find(self.product_id, bom_type='phantom') bom_kit_orderpoints = { @@ -109,6 +132,12 @@ class StockWarehouseOrderpoint(models.Model): orderpoint_wh_bom.route_id = route_id[0].id return super()._set_default_route_id() + def _get_orderpoint_procurement_date(self): + date = super()._get_orderpoint_procurement_date() + if any(rule.action == 'manufacture' for rule in self.rule_ids): + date -= relativedelta(days=self.company_id.manufacturing_lead) + return date + def _prepare_procurement_values(self, date=False, group=False): values = super()._prepare_procurement_values(date=date, group=group) values['bom_id'] = self.bom_id diff --git a/addons/mrp/models/stock_rule.py b/addons/mrp/models/stock_rule.py index 8af591ea519..82da0108cd5 100644 --- a/addons/mrp/models/stock_rule.py +++ b/addons/mrp/models/stock_rule.py @@ -109,7 +109,7 @@ class StockRule(models.Model): def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_dest_id, name, origin, company_id, values, bom): date_planned = self._get_date_planned(product_id, company_id, values) - date_deadline = values.get('date_deadline') or date_planned + relativedelta(days=company_id.manufacturing_lead) + relativedelta(days=product_id.produce_delay) + date_deadline = values.get('date_deadline') or date_planned + relativedelta(days=product_id.produce_delay) mo_values = { 'origin': origin, 'product_id': product_id.id, @@ -144,7 +144,6 @@ class StockRule(models.Model): def _get_date_planned(self, product_id, company_id, values): format_date_planned = fields.Datetime.from_string(values['date_planned']) date_planned = format_date_planned - relativedelta(days=product_id.produce_delay) - date_planned = date_planned - relativedelta(days=company_id.manufacturing_lead) if date_planned == format_date_planned: date_planned = date_planned - relativedelta(hours=1) return date_planned diff --git a/addons/mrp/views/product_views.xml b/addons/mrp/views/product_views.xml index 644ed616540..5ea072a561e 100644 --- a/addons/mrp/views/product_views.xml +++ b/addons/mrp/views/product_views.xml @@ -12,6 +12,10 @@
days
+