diff --git a/addons/mrp/models/mrp_bom.py b/addons/mrp/models/mrp_bom.py index 2b8ee43d5af..601d281b924 100644 --- a/addons/mrp/models/mrp_bom.py +++ b/addons/mrp/models/mrp_bom.py @@ -297,8 +297,7 @@ class MrpBom(models.Model): warehouse = self.env['stock.warehouse'].search([('company_id', '=', company_id)], limit=1) for bom in self: bom_data = self.env['report.mrp.report_bom_structure'].with_context(minimized=True)._get_bom_data(bom, warehouse, bom.product_id, ignore_stock=True) - availability_delay = bom_data.get('resupply_avail_delay') - bom.days_to_prepare_mo = availability_delay - bom_data.get('lead_time', 0) if availability_delay else 0 + bom.days_to_prepare_mo = self.env['report.mrp.report_bom_structure']._get_max_component_delay(bom_data['components']) @api.constrains('product_tmpl_id', 'product_id', 'type') def check_kit_has_not_orderpoint(self): diff --git a/addons/mrp/models/res_company.py b/addons/mrp/models/res_company.py index 0a26c44c757..bbfdfd84d14 100644 --- a/addons/mrp/models/res_company.py +++ b/addons/mrp/models/res_company.py @@ -36,8 +36,3 @@ class Company(models.Model): def _create_per_company_sequences(self): super(Company, self)._create_per_company_sequences() self._create_unbuild_sequence() - - def _get_security_by_rule_action(self): - res = super()._get_security_by_rule_action() - res['manufacture'] = self.manufacturing_lead - return res diff --git a/addons/mrp/models/stock_rule.py b/addons/mrp/models/stock_rule.py index 7624d4b7804..82e7b96719b 100644 --- a/addons/mrp/models/stock_rule.py +++ b/addons/mrp/models/stock_rule.py @@ -190,25 +190,29 @@ class StockRule(models.Model): """Add the product and company manufacture delay to the cumulative delay and cumulative description. """ - delay, delay_description = super()._get_lead_days(product, **values) + delays, delay_description = super()._get_lead_days(product, **values) bypass_delay_description = self.env.context.get('bypass_delay_description') manufacture_rule = self.filtered(lambda r: r.action == 'manufacture') if not manufacture_rule: - return delay, delay_description + return delays, delay_description manufacture_rule.ensure_one() bom = values.get('bom') or self.env['mrp.bom']._bom_find(product, picking_type=manufacture_rule.picking_type_id, company_id=manufacture_rule.company_id.id)[product] manufacture_delay = bom.produce_delay - delay += manufacture_delay + delays['total_delay'] += manufacture_delay + delays['manufacture_delay'] += manufacture_delay if not bypass_delay_description: delay_description.append((_('Manufacturing Lead Time'), _('+ %d day(s)', manufacture_delay))) - security_delay = manufacture_rule.picking_type_id.company_id.manufacturing_lead - delay += security_delay - if not bypass_delay_description: - delay_description.append((_('Manufacture Security Lead Time'), _('+ %d day(s)', security_delay))) + if bom.type == 'normal': + security_delay = self.picking_type_id.company_id.manufacturing_lead + delays['total_delay'] += security_delay + delays['security_lead_days'] += security_delay + if not bypass_delay_description: + delay_description.append((_('Manufacture Security Lead Time'), _('+ %d day(s)', security_delay))) days_to_order = values.get('days_to_order', bom.days_to_prepare_mo) + delays['total_delay'] += days_to_order if not bypass_delay_description: delay_description.append((_('Days to Supply Components'), _('+ %d day(s)', days_to_order))) - return delay + days_to_order, delay_description + return delays, delay_description def _push_prepare_move_copy_values(self, move_to_copy, new_date): new_move_vals = super(StockRule, self)._push_prepare_move_copy_values(move_to_copy, new_date) diff --git a/addons/mrp/report/mrp_report_bom_structure.py b/addons/mrp/report/mrp_report_bom_structure.py index 615c8f50d94..ebe8b593c25 100644 --- a/addons/mrp/report/mrp_report_bom_structure.py +++ b/addons/mrp/report/mrp_report_bom_structure.py @@ -37,18 +37,19 @@ class ReportBomStructure(models.AbstractModel): @api.model def _compute_production_capacities(self, bom_qty, bom_data): date_today = self.env.context.get('from_date', fields.date.today()) - same_delay = bom_data['lead_time'] == bom_data['availability_delay'] + lead_time = bom_data['lead_time'] - bom_data['bom'].days_to_prepare_mo + same_delay = lead_time == bom_data['availability_delay'] res = {} if bom_data.get('producible_qty', 0): # Check if something is producible today, at the earliest time possible considering product's lead time. res['earliest_capacity'] = bom_data['producible_qty'] - res['earliest_date'] = format_date(self.env, date_today + timedelta(days=bom_data['lead_time'])) + res['earliest_date'] = format_date(self.env, date_today + timedelta(days=lead_time)) if bom_data['availability_state'] != 'unavailable': if same_delay: # Means that stock will be resupplied at date_today, so the whole manufacture can start at date_today. res['earliest_capacity'] = bom_qty - res['earliest_date'] = format_date(self.env, date_today + timedelta(days=bom_data['lead_time'])) + res['earliest_date'] = format_date(self.env, date_today + timedelta(days=bom_data['availability_delay'])) else: res['leftover_capacity'] = bom_qty - bom_data.get('producible_qty', 0) res['leftover_date'] = format_date(self.env, date_today + timedelta(days=bom_data['availability_delay'])) @@ -247,7 +248,6 @@ class ReportBomStructure(models.AbstractModel): 'route_type': route_info.get('route_type', ''), 'route_name': route_info.get('route_name', ''), 'route_detail': route_info.get('route_detail', ''), - 'lead_time': route_info.get('lead_time', False), 'currency': company.currency_id, 'currency_id': company.currency_id.id, 'product': product, @@ -314,6 +314,8 @@ class ReportBomStructure(models.AbstractModel): bom_report_line['bom_cost'] *= bom_report_line['cost_share'] availabilities = self._get_availabilities(product, current_quantity, product_info, bom_key, quantities_info, level, ignore_stock, components, report_line=bom_report_line) + # in case of subcontracting, lead_time will be calculated with components availability delay + bom_report_line['lead_time'] = route_info.get('lead_time', False) bom_report_line.update(availabilities) if level == 0: @@ -588,8 +590,9 @@ class ReportBomStructure(models.AbstractModel): 'route_type': 'manufacture', 'route_name': manufacture_rules[0].route_id.display_name, 'route_detail': bom.display_name, - 'lead_time': bom.produce_delay + rules_delay + manufacturing_lead, + 'lead_time': bom.produce_delay + rules_delay + manufacturing_lead + bom.days_to_prepare_mo, 'manufacture_delay': bom.produce_delay + rules_delay + manufacturing_lead, + 'bom': bom, } return {} diff --git a/addons/mrp_subcontracting/report/mrp_report_bom_structure.py b/addons/mrp_subcontracting/report/mrp_report_bom_structure.py index 9a167ea4370..e048d2c562e 100644 --- a/addons/mrp_subcontracting/report/mrp_report_bom_structure.py +++ b/addons/mrp_subcontracting/report/mrp_report_bom_structure.py @@ -72,15 +72,16 @@ class ReportBomStructure(models.AbstractModel): subcontract_rules = [rule for rule in rules if rule.action == 'buy' and bom and bom.type == 'subcontract'] if subcontract_rules: supplier = product._select_seller(quantity=quantity, uom_id=product.uom_id, params={'subcontractor_ids': bom.subcontractor_ids}) + # for subcontracting, we can't decide the lead time without component's resupply availability + # we only return necessary info and calculate the lead time late when we have component's data if supplier: return { 'route_type': 'subcontract', 'route_name': subcontract_rules[0].route_id.display_name, 'route_detail': supplier.display_name, - 'lead_time': supplier.delay + rules_delay, - 'supplier_delay': supplier.delay + rules_delay, - 'manufacture_delay': bom.produce_delay, + 'lead_time': rules_delay, 'supplier': supplier, + 'bom': bom, } return res @@ -106,11 +107,16 @@ class ReportBomStructure(models.AbstractModel): @api.model def _get_resupply_availability(self, route_info, components): + resupply_state, resupply_delay = super()._get_resupply_availability(route_info, components) if route_info.get('route_type') == 'subcontract': max_component_delay = self._get_max_component_delay(components) if max_component_delay is False: return ('unavailable', False) - produce_delay = route_info.get('manufacture_delay', 0) + max_component_delay - supplier_delay = route_info.get('supplier_delay', 0) - return ('estimated', max(produce_delay, supplier_delay)) - return super()._get_resupply_availability(route_info, components) + # Calculate the lead time for subcontracting, keep same as `_get_lead_days` + vendor_lead_time = route_info['supplier'].delay + manufacture_lead_time = route_info['bom'].produce_delay + subcontract_delay = resupply_delay if resupply_delay else 0 + subcontract_delay += max(vendor_lead_time, manufacture_lead_time + max_component_delay) + route_info['lead_time'] += max(vendor_lead_time, manufacture_lead_time + route_info['bom'].days_to_prepare_mo) + return ('estimated', subcontract_delay) + return (resupply_state, resupply_delay) diff --git a/addons/mrp_subcontracting_purchase/models/__init__.py b/addons/mrp_subcontracting_purchase/models/__init__.py index d72772bf68b..6576bfb375b 100644 --- a/addons/mrp_subcontracting_purchase/models/__init__.py +++ b/addons/mrp_subcontracting_purchase/models/__init__.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import account_move_line -from . import stock_picking -from . import stock_valuation_layer from . import purchase_order +from . import stock_picking +from . import stock_rule +from . import stock_valuation_layer diff --git a/addons/mrp_subcontracting_purchase/models/stock_rule.py b/addons/mrp_subcontracting_purchase/models/stock_rule.py new file mode 100644 index 00000000000..b0c3d40d6b4 --- /dev/null +++ b/addons/mrp_subcontracting_purchase/models/stock_rule.py @@ -0,0 +1,50 @@ +from odoo import models, _ + + +class StockRule(models.Model): + _inherit = 'stock.rule' + + def _get_lead_days(self, product, **values): + """For subcontracting, we need to consider both vendor lead time and + manufacturing lead time, and DTPMO (Days To Prepare MO). + Subcontracting delay = + max(Vendor lead time, Manufacturing lead time + DTPMO) + Days to Purchase + Purchase security lead time + """ + bypass_delay_description = self.env.context.get('bypass_delay_description') + buy_rule = self.filtered(lambda r: r.action == 'buy') + seller = 'supplierinfo' in values and values['supplierinfo'] or product.with_company(buy_rule.company_id)._select_seller(quantity=None) + if not buy_rule or not seller: + return super()._get_lead_days(product, **values) + seller = seller[0] + bom = self.env['mrp.bom'].sudo()._bom_subcontract_find( + product, + company_id=buy_rule.picking_type_id.company_id.id, + bom_type='subcontract', + subcontractor=seller.partner_id) + if not bom: + return super()._get_lead_days(product, **values) + + delays, delay_description = super(StockRule, self - buy_rule)._get_lead_days(product, **values) + extra_delays, extra_delay_description = super(StockRule, buy_rule.with_context(ignore_vendor_lead_time=True))._get_lead_days(product, **values) + if seller.delay >= bom.produce_delay + bom.days_to_prepare_mo: + delays['total_delay'] += seller.delay + delays['purchase_delay'] += seller.delay + if not bypass_delay_description: + delay_description.append((_('Vendor Lead Time'), _('+ %d day(s)', seller.delay))) + else: + manufacture_delay = bom.produce_delay + delays['total_delay'] += manufacture_delay + # set manufacture_delay to purchase_delay so that PO can be created with correct date + delays['purchase_delay'] += manufacture_delay + if not bypass_delay_description: + delay_description.append((_('Manufacturing Lead Time'), _('+ %d day(s)', manufacture_delay))) + days_to_order = bom.days_to_prepare_mo + delays['total_delay'] += days_to_order + # add dtpmo to purchase_delay so that PO can be created with correct date + delays['purchase_delay'] += days_to_order + if not bypass_delay_description: + extra_delay_description.append((_('Days to Supply Components'), _('+ %d day(s)', days_to_order))) + + for key, value in extra_delays.items(): + delays[key] += value + return delays, delay_description + extra_delay_description diff --git a/addons/mrp_subcontracting_purchase/report/mrp_report_bom_structure.py b/addons/mrp_subcontracting_purchase/report/mrp_report_bom_structure.py index 41b6b766c99..b37d60826be 100644 --- a/addons/mrp_subcontracting_purchase/report/mrp_report_bom_structure.py +++ b/addons/mrp_subcontracting_purchase/report/mrp_report_bom_structure.py @@ -10,3 +10,14 @@ class ReportBomStructure(models.AbstractModel): @api.model def _is_buy_route(self, rules, product, bom): return super()._is_buy_route(rules, product, bom) and (not bom or bom.type != 'subcontract') + + @api.model + def _get_resupply_availability(self, route_info, components): + resupply_state, resupply_delay = super()._get_resupply_availability(route_info, components) + if route_info.get('route_type') == 'subcontract': + # always add `Purchase security lead days` and `Days to Purchase` + extra_delay = route_info['bom'].company_id.po_lead + route_info['bom'].company_id.days_to_purchase + route_info['lead_time'] += extra_delay + subcontract_delay = (resupply_delay if resupply_delay else 0) + extra_delay + return ('estimated', subcontract_delay) + return (resupply_state, resupply_delay) diff --git a/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py b/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py index 84b4fd9990d..63a299d20c5 100644 --- a/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py +++ b/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py @@ -382,3 +382,103 @@ class MrpSubcontractingPurchaseTest(TestMrpSubcontractingCommon): {'location_dest_id': stock_location_id.id, 'quantity': 10.0, 'state': 'done'}, {'location_dest_id': subco_location_id.id, 'quantity': 1.0, 'state': 'done'}, ]) + + def test_subcontracting_lead_days(self): + """ Test the lead days computation for subcontracting. Subcontracting delay = + max(Vendor lead time, Manufacturing lead time + DTPMO) + Days to Purchase + Purchase security lead time + """ + rule = self.env['stock.rule'].search([('action', '=', 'buy')], limit=1) + self.env.company.manufacturing_lead = 114514 # should never be used + + self.env.company.po_lead = 1 + self.env.company.days_to_purchase = 2 + # Case 1 Vendor lead time >= Manufacturing lead time + DTPMO + seller = self.env['product.supplierinfo'].create({ + 'product_tmpl_id': self.finished.product_tmpl_id.id, + 'partner_id': self.subcontractor_partner1.id, + 'price': 12.0, + 'delay': 10 + }) + self.bom.produce_delay = 3 + self.bom.days_to_prepare_mo = 4 + delays, _ = rule._get_lead_days(self.finished, supplierinfo=seller) + self.assertEqual(delays['total_delay'], seller.delay + self.env.company.po_lead + self.env.company.days_to_purchase) + # Case 2 Vendor lead time < Manufacturing lead time + DTPMO + self.bom.produce_delay = 5 + self.bom.days_to_prepare_mo = 6 + delays, _ = rule._get_lead_days(self.finished, supplierinfo=seller) + self.assertEqual(delays['total_delay'], self.bom.produce_delay + self.bom.days_to_prepare_mo + self.env.company.po_lead + self.env.company.days_to_purchase) + + def test_subcontracting_lead_days_on_overview(self): + """Test on the BOM overview, the lead days and resupply availability are + correctly computed. The dtpmo on the bom should be used for the lead days, + while the resupply availability should be based on the calculated dtpmo. + """ + # should never be used + self.env.company.manufacturing_lead = 114514 + # should be added in all cases + self.env.company.po_lead = 5 + self.env.company.days_to_purchase = 5 + + rule = self.env['stock.rule'].search([('action', '=', 'buy')], limit=1) + (self.finished | self.comp1 | self.comp2).route_ids = [(6, None, [rule.route_id.id])] + self.comp2_bom.active = False + self.env['product.supplierinfo'].create({ + 'product_tmpl_id': self.finished.product_tmpl_id.id, + 'partner_id': self.subcontractor_partner1.id, + 'price': 648.0, + 'delay': 15 + }) + self.env['product.supplierinfo'].create({ + 'product_tmpl_id': self.comp1.product_tmpl_id.id, + 'partner_id': self.subcontractor_partner1.id, + 'price': 648.0, + 'delay': 10 + }) + self.env['product.supplierinfo'].create({ + 'product_tmpl_id': self.comp2.product_tmpl_id.id, + 'partner_id': self.subcontractor_partner1.id, + 'price': 648.0, + 'delay': 6 + }) + self.bom.produce_delay = 10 + self.bom.days_to_prepare_mo = 0 + + # Case 1: Vendor lead time >= Manufacturing lead time + DTPMO on BOM + bom_data = self.env['report.mrp.report_bom_structure']._get_bom_data(self.bom, self.warehouse, self.finished) + self.assertEqual(bom_data['lead_time'], 15 + 5 + 5 + 0, + "Lead time = Purchase lead time(finished) + Days to Purchase + Purchase security lead time + DTPMO on BOM") + self.assertEqual(bom_data['resupply_avail_delay'], 10 + 5 + 5 + 20, + "Component avail delay = Purchase lead time(comp1) + Days to Purchase + Purchase security lead time + Calculated DTPMO") + + # Case 2: Vendor lead time < Manufacturing lead time + DTPMO on BOM + self.bom.action_compute_bom_days() + self.assertEqual(self.bom.days_to_prepare_mo, 10 + 5 + 5, + "DTPMO = Purchase lead time(comp1) + Days to Purchase + Purchase security lead time") + self.bom.days_to_prepare_mo = 10 + bom_data = self.env['report.mrp.report_bom_structure']._get_bom_data(self.bom, self.warehouse, self.finished) + self.assertEqual(bom_data['lead_time'], 10 + 5 + 5 + 10, + "Lead time = Manufacturing lead time + Days to Purchase + Purchase security lead time + DTPMO on BOM") + self.assertEqual(bom_data['resupply_avail_delay'], 10 + 5 + 5 + 20, + "Component avail delay = Manufacturing lead time + Days to Purchase + Purchase security lead time + Calculated DTPMO") + + # Update stock for components, calculate DTPMO should be 0 + self.env['stock.quant']._update_available_quantity(self.comp1, self.warehouse.lot_stock_id, 100) + self.env['stock.quant']._update_available_quantity(self.comp2, self.warehouse.lot_stock_id, 100) + self.env.invalidate_all() # invalidate cache to get updated qty_available + # Case 1: Vendor lead time >= Manufacturing lead time + DTPMO on BOM + self.bom.days_to_prepare_mo = 2 + bom_data = self.env['report.mrp.report_bom_structure']._get_bom_data(self.bom, self.warehouse, self.finished) + self.assertEqual(bom_data['lead_time'], 15 + 5 + 5, + "Lead time = Purchase lead time(finished) + Days to Purchase + Purchase security lead time") + for component in bom_data['components']: + self.assertEqual(component['availability_state'], 'available') + # Case 2: Vendor lead time < Manufacturing lead time + DTPMO on BOM + self.bom.action_compute_bom_days() + self.assertEqual(self.bom.days_to_prepare_mo, 10 + 5 + 5, + "DTPMO = Purchase lead time(comp1) + Days to Purchase + Purchase security lead time") + bom_data = self.env['report.mrp.report_bom_structure']._get_bom_data(self.bom, self.warehouse, self.finished) + self.assertEqual(bom_data['lead_time'], 10 + 5 + 5 + 20, + "Lead time = Manufacturing lead time + Days to Purchase + Purchase security lead time + DTPMO on BOM") + for component in bom_data['components']: + self.assertEqual(component['availability_state'], 'available') diff --git a/addons/purchase_stock/models/res_company.py b/addons/purchase_stock/models/res_company.py index 40b810ab185..b49d8cf9259 100644 --- a/addons/purchase_stock/models/res_company.py +++ b/addons/purchase_stock/models/res_company.py @@ -10,9 +10,3 @@ class ResCompany(models.Model): days_to_purchase = fields.Float( string='Days to Purchase', help="Days needed to confirm a PO, define when a PO should be validated") - - - def _get_security_by_rule_action(self): - res = super()._get_security_by_rule_action() - res['buy'] = self.po_lead - return res diff --git a/addons/purchase_stock/models/stock_rule.py b/addons/purchase_stock/models/stock_rule.py index e2d4bba9238..06426255691 100644 --- a/addons/purchase_stock/models/stock_rule.py +++ b/addons/purchase_stock/models/stock_rule.py @@ -161,23 +161,29 @@ class StockRule(models.Model): and cumulative description. The company lead time is always displayed for onboarding purpose in order to indicate that those options are available. """ - delay, delay_description = super()._get_lead_days(product, **values) + delays, delay_description = super()._get_lead_days(product, **values) bypass_delay_description = self.env.context.get('bypass_delay_description') buy_rule = self.filtered(lambda r: r.action == 'buy') seller = 'supplierinfo' in values and values['supplierinfo'] or product.with_company(buy_rule.company_id)._select_seller(quantity=None) if not buy_rule or not seller: - return delay, delay_description + return delays, delay_description buy_rule.ensure_one() - supplier_delay = seller[0].delay - if supplier_delay and not bypass_delay_description: - delay_description.append((_('Vendor Lead Time'), _('+ %d day(s)', supplier_delay))) + if not self.env.context.get('ignore_vendor_lead_time'): + supplier_delay = seller[0].delay + delays['total_delay'] += supplier_delay + delays['purchase_delay'] += supplier_delay + if not bypass_delay_description: + delay_description.append((_('Vendor Lead Time'), _('+ %d day(s)', supplier_delay))) security_delay = buy_rule.picking_type_id.company_id.po_lead + delays['total_delay'] += security_delay + delays['security_lead_days'] += security_delay if not bypass_delay_description: delay_description.append((_('Purchase Security Lead Time'), _('+ %d day(s)', security_delay))) - days_to_order = values.get('days_to_order', buy_rule.company_id.days_to_purchase) + days_to_order = buy_rule.company_id.days_to_purchase + delays['total_delay'] += days_to_order if not bypass_delay_description: delay_description.append((_('Days to Purchase'), _('+ %d day(s)', days_to_order))) - return delay + supplier_delay + security_delay + days_to_order, delay_description + return delays, delay_description @api.model def _get_procurements_to_merge_groupby(self, procurement): @@ -266,7 +272,7 @@ class StockRule(models.Model): params values: values of procurements params origins: procuremets origins to write on the PO """ - purchase_date = min([fields.Datetime.from_string(value['date_planned']) - relativedelta(days=int(value['supplier'].delay)) for value in values]) + purchase_date = min([value.get('date_order') or fields.Datetime.from_string(value['date_planned']) - relativedelta(days=int(value['supplier'].delay)) for value in values]) # Since the procurements are grouped if they share the same domain for # PO but the PO does not exist. In this case it will create the PO from diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index 53078cddbd0..0bfb825e5aa 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -595,12 +595,13 @@ class Product(models.Model): else: return self._get_rules_from_location(rule.location_src_id, seen_rules=seen_rules | rule) - def _get_date_with_security_lead_days(self, date, location, route_ids=False): + def _get_dates_info(self, date, location, route_ids=False): rules = self._get_rules_from_location(location, route_ids=route_ids) - for action, days in location.company_id._get_security_by_rule_action().items(): - if action in rules.mapped('action'): - date -= relativedelta(days=days) - return date + delays, _ = rules.with_context(bypass_delay_description=True)._get_lead_days(self) + return { + 'date_planned': date - relativedelta(days=delays['security_lead_days']), + 'date_order': date - relativedelta(days=delays['security_lead_days'] + delays['purchase_delay']), + } def _get_only_qty_available(self): """ Get only quantities available, it is equivalent to read qty_available diff --git a/addons/stock/models/res_company.py b/addons/stock/models/res_company.py index 0d1e9ab27e1..cadace55d4c 100644 --- a/addons/stock/models/res_company.py +++ b/addons/stock/models/res_company.py @@ -206,6 +206,3 @@ class Company(models.Model): 'partner_id': company.partner_id.id } for company in companies]) return companies - - def _get_security_by_rule_action(self): - return {} diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 6fe8123459e..557eeb37a95 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -1407,12 +1407,13 @@ Please change the quantity done or the rounding precision of your unit of measur elif self.rule_id.group_propagation_option == 'none': group_id = False product_id = self.product_id.with_context(lang=self._get_lang()) - date = self._get_mto_procurement_date() + dates_info = {'date_planned': self._get_mto_procurement_date()} if self.location_id.warehouse_id and self.location_id.warehouse_id.lot_stock_id.parent_path in self.location_id.parent_path: - date = self.product_id._get_date_with_security_lead_days(self.date, self.location_id, route_ids=self.route_ids) + dates_info = self.product_id._get_dates_info(self.date, self.location_id, route_ids=self.route_ids) return { 'product_description_variants': self.description_picking and self.description_picking.replace(product_id._get_description(self.picking_type_id), ''), - 'date_planned': date, + 'date_planned': dates_info.get('date_planned'), + 'date_order': dates_info.get('date_order'), 'date_deadline': self.date_deadline, 'move_dest_ids': self, 'group_id': group_id, diff --git a/addons/stock/models/stock_orderpoint.py b/addons/stock/models/stock_orderpoint.py index a3757648ed4..ac8c1e53770 100644 --- a/addons/stock/models/stock_orderpoint.py +++ b/addons/stock/models/stock_orderpoint.py @@ -79,6 +79,7 @@ class StockWarehouseOrderpoint(models.Model): qty_forecast = fields.Float('Forecast', readonly=True, compute='_compute_qty', digits='Product Unit of Measure') qty_to_order = fields.Float('To Order', compute='_compute_qty_to_order', store=True, readonly=False, digits='Product Unit of Measure') + #TODO: remove this field in master days_to_order = fields.Float(compute='_compute_days_to_order', help="Numbers of days in advance that replenishments demands are created.") visibility_days = fields.Float( compute='_compute_visibility_days', inverse='_set_visibility_days', readonly=False, @@ -113,7 +114,7 @@ class StockWarehouseOrderpoint(models.Model): continue values = orderpoint._get_lead_days_values() lead_days, dummy = orderpoint.rule_ids._get_lead_days(orderpoint.product_id, **values) - lead_days_date = fields.Date.today() + relativedelta.relativedelta(days=lead_days) + lead_days_date = fields.Date.today() + relativedelta.relativedelta(days=lead_days['total_delay']) orderpoint.lead_days_date = lead_days_date @api.depends('route_id', 'product_id', 'location_id', 'company_id', 'warehouse_id', 'product_id.route_ids') @@ -352,7 +353,7 @@ class StockWarehouseOrderpoint(models.Model): # group product by lead_days and location in order to read virtual_available # in batch rules = product._get_rules_from_location(loc) - lead_days = rules.with_context(bypass_delay_description=True)._get_lead_days(product)[0] + lead_days = rules.with_context(bypass_delay_description=True)._get_lead_days(product)[0]['total_delay'] ploc_per_day[(lead_days, loc)].add(product.id) # recompute virtual_available with lead days @@ -481,11 +482,12 @@ class StockWarehouseOrderpoint(models.Model): comming from an orderpoint. This method could be override in order to add other custom key that could be used in move/po creation. """ - date_planned = date or fields.Date.today() - date_planned = self.product_id._get_date_with_security_lead_days(date_planned, self.location_id, route_ids=self.route_id) + date_deadline = date or fields.Date.today() + dates_info = self.product_id._get_dates_info(date_deadline, self.location_id, route_ids=self.route_id) return { 'route_ids': self.route_id, - 'date_planned': date_planned, + 'date_planned': dates_info['date_planned'], + 'date_order': dates_info['date_order'], 'date_deadline': date or False, 'warehouse_id': self.warehouse_id, 'orderpoint_id': self, diff --git a/addons/stock/models/stock_rule.py b/addons/stock/models/stock_rule.py index 9b9e7a593ef..82c6954aa42 100644 --- a/addons/stock/models/stock_rule.py +++ b/addons/stock/models/stock_rule.py @@ -359,12 +359,14 @@ class StockRule(models.Model): :param product: the product of the procurement :type product: :class:`~odoo.addons.product.models.product.ProductProduct` :return: the cumulative delay and cumulative delay's description - :rtype: tuple[int, list[str, str]] + :rtype: tuple[defaultdict(float), list[str, str]] """ + delays = defaultdict(float) delay = sum(self.filtered(lambda r: r.action in ['pull', 'pull_push']).mapped('delay')) + delays['total_delay'] += delay global_visibility_days = self.env['ir.config_parameter'].sudo().get_param('stock.visibility_days') if global_visibility_days: - delay += int(global_visibility_days) + delays['total_delay'] += int(global_visibility_days) if self.env.context.get('bypass_delay_description'): delay_description = [] else: @@ -375,7 +377,7 @@ class StockRule(models.Model): ] if global_visibility_days: delay_description.append((_('Global Visibility Days'), _('+ %d day(s)', int(global_visibility_days)))) - return delay, delay_description + return delays, delay_description class ProcurementGroup(models.Model): diff --git a/addons/stock/wizard/stock_replenishment_info.py b/addons/stock/wizard/stock_replenishment_info.py index f711c606fdf..2d5f4e9374e 100644 --- a/addons/stock/wizard/stock_replenishment_info.py +++ b/addons/stock/wizard/stock_replenishment_info.py @@ -118,7 +118,7 @@ class StockReplenishmentOption(models.TransientModel): @api.depends('replenishment_info_id') def _compute_lead_time(self): for record in self: - lead_time = record.route_id.rule_ids._get_lead_days(record.product_id)[0] + lead_time = record.route_id.rule_ids._get_lead_days(record.product_id)[0]['total_delay'] #TO FIX: use _get_rule to avoid singleton issue record.lead_time = str(lead_time) + " days" @api.depends('warehouse_id', 'free_qty', 'uom', 'qty_to_order')