diff --git a/addons/mrp/report/mrp_report_bom_structure.py b/addons/mrp/report/mrp_report_bom_structure.py
index 0e447b1e480..d7f4cd29256 100644
--- a/addons/mrp/report/mrp_report_bom_structure.py
+++ b/addons/mrp/report/mrp_report_bom_structure.py
@@ -37,7 +37,7 @@ 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())
- lead_time = bom_data['lead_time'] - bom_data['bom'].days_to_prepare_mo
+ lead_time = bom_data['manufacture_delay']
same_delay = lead_time == bom_data['availability_delay']
res = {}
if bom_data.get('producible_qty', 0):
@@ -317,6 +317,7 @@ class ReportBomStructure(models.AbstractModel):
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['manufacture_delay'] = route_info.get('manufacture_delay', False)
bom_report_line.update(availabilities)
if level == 0:
@@ -371,6 +372,7 @@ class ReportBomStructure(models.AbstractModel):
'route_name': route_info.get('route_name', ''),
'route_detail': route_info.get('route_detail', ''),
'lead_time': route_info.get('lead_time', False),
+ 'manufacture_delay': route_info.get('manufacture_delay', False),
'stock_avail_state': availabilities['stock_avail_state'],
'resupply_avail_delay': availabilities['resupply_avail_delay'],
'availability_display': availabilities['availability_display'],
@@ -511,6 +513,7 @@ class ReportBomStructure(models.AbstractModel):
'route_name': bom_line['route_name'],
'route_detail': bom_line['route_detail'],
'lead_time': bom_line['lead_time'],
+ 'manufacture_delay': bom_line['manufacture_delay'],
'level': bom_line['level'],
'code': bom_line['code'],
'availability_state': bom_line['availability_state'],
diff --git a/addons/mrp/static/src/components/bom_overview_table/mrp_bom_overview_table.xml b/addons/mrp/static/src/components/bom_overview_table/mrp_bom_overview_table.xml
index 7a299a595a3..2385630a3f2 100644
--- a/addons/mrp/static/src/components/bom_overview_table/mrp_bom_overview_table.xml
+++ b/addons/mrp/static/src/components/bom_overview_table/mrp_bom_overview_table.xml
@@ -11,7 +11,7 @@
Reference:
-
+
Free to Use
diff --git a/addons/mrp_subcontracting/report/mrp_report_bom_structure.py b/addons/mrp_subcontracting/report/mrp_report_bom_structure.py
index 9aa4a550593..fcddd506fe2 100644
--- a/addons/mrp_subcontracting/report/mrp_report_bom_structure.py
+++ b/addons/mrp_subcontracting/report/mrp_report_bom_structure.py
@@ -119,7 +119,8 @@ class ReportBomStructure(models.AbstractModel):
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)
+ subcontract_delay += max(vendor_lead_time, manufacture_lead_time) + max_component_delay
+ route_info['manufacture_delay'] = route_info['lead_time'] + max(vendor_lead_time, manufacture_lead_time)
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/tests/test_subcontracting.py b/addons/mrp_subcontracting/tests/test_subcontracting.py
index 878288b54d6..1048d902197 100644
--- a/addons/mrp_subcontracting/tests/test_subcontracting.py
+++ b/addons/mrp_subcontracting/tests/test_subcontracting.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from freezegun import freeze_time
+
from odoo import Command
from odoo.exceptions import AccessError, UserError
from odoo.tests import Form
@@ -944,6 +946,60 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
{'qty_producing': 10.0, 'product_qty': 10.0, 'state': 'to_close'},
])
+ @freeze_time('2024-01-01')
+ def test_bom_overview_availability(self):
+ # Create routes for components and the main product
+ self.env['product.supplierinfo'].create({
+ 'product_tmpl_id': self.finished.product_tmpl_id.id,
+ 'partner_id': self.subcontractor_partner1.id,
+ 'price': 1.0,
+ 'delay': 10
+ })
+ self.env['product.supplierinfo'].create({
+ 'product_tmpl_id': self.comp1.product_tmpl_id.id,
+ 'partner_id': self.subcontractor_partner1.id,
+ 'price': 648.0,
+ 'delay': 5
+ })
+ self.env['product.supplierinfo'].create({
+ 'product_tmpl_id': self.comp2.product_tmpl_id.id,
+ 'partner_id': self.subcontractor_partner1.id,
+ 'price': 648.0,
+ 'delay': 5
+ })
+
+ self.bom.produce_delay = 1
+ self.bom.days_to_prepare_mo = 3
+
+ # Add 4 units of each component to subcontractor's location
+ subcontractor_location = self.env.company.subcontracting_location_id
+ self.env['stock.quant']._update_available_quantity(self.comp1, subcontractor_location, 4)
+ self.env['stock.quant']._update_available_quantity(self.comp2, subcontractor_location, 4)
+
+ # Generate a report for 3 products: all products should be ready for production
+ bom_data = self.env['report.mrp.report_bom_structure']._get_report_data(self.bom.id, 3)
+
+ self.assertTrue(bom_data['lines']['components_available'])
+ for component in bom_data['lines']['components']:
+ self.assertEqual(component['quantity_on_hand'], 4)
+ self.assertEqual(component['availability_state'], 'available')
+ self.assertEqual(bom_data['lines']['earliest_capacity'], 3)
+ self.assertEqual(bom_data['lines']['earliest_date'], '01/11/2024')
+ self.assertTrue('leftover_capacity' not in bom_data['lines']['earliest_date'])
+ self.assertTrue('leftover_date' not in bom_data['lines']['earliest_date'])
+
+ # Generate a report for 5 products: only 4 products should be ready for production
+ bom_data = self.env['report.mrp.report_bom_structure']._get_report_data(self.bom.id, 5)
+
+ self.assertFalse(bom_data['lines']['components_available'])
+ for component in bom_data['lines']['components']:
+ self.assertEqual(component['quantity_on_hand'], 4)
+ self.assertEqual(component['availability_state'], 'estimated')
+ self.assertEqual(bom_data['lines']['earliest_capacity'], 4)
+ self.assertEqual(bom_data['lines']['earliest_date'], '01/11/2024')
+ self.assertEqual(bom_data['lines']['leftover_capacity'], 1)
+ self.assertEqual(bom_data['lines']['leftover_date'], '01/16/2024')
+
@tagged('post_install', '-at_install')
class TestSubcontractingTracking(TransactionCase):
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 b37d60826be..d2b79cc9235 100644
--- a/addons/mrp_subcontracting_purchase/report/mrp_report_bom_structure.py
+++ b/addons/mrp_subcontracting_purchase/report/mrp_report_bom_structure.py
@@ -14,10 +14,11 @@ 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':
+ if route_info.get('route_type') == 'subcontract' and resupply_delay:
# 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
+ route_info['manufacture_delay'] += extra_delay
+ subcontract_delay = resupply_delay + 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 b0480a2bc6c..df3950a0d7c 100644
--- a/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py
+++ b/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py
@@ -3,6 +3,8 @@
import logging
+from freezegun import freeze_time
+
from odoo import Command
from odoo.exceptions import UserError
from odoo.fields import Date
@@ -535,23 +537,41 @@ class MrpSubcontractingPurchaseTest(TestMrpSubcontractingCommon):
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")
+ # Resupply delay = 0 (received from MRP, where route type != "manufacture")
+ # Vendor lead time = 15 (finished product supplier delay)
+ # Manufacture lead time = 10 (BoM.produce_delay)
+ # Max purchase component delay = max delay(comp1, comp2) + po_lead + days_to_purchase = 20
+ self.assertEqual(bom_data['resupply_avail_delay'], 0 + 15 + 20 + 5 + 5,
+ 'Resupply avail delay = Resupply delay + Max(Vendor lead time, Manufacture lead time)'
+ ' + Max purchase component delay + Purchase security lead time + Days to Purchase'
+ )
# 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
+ # Temp increase BoM.produce_delay, to check if it is now used in the final calculation
+ self.bom.produce_delay = 30
+
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,
+ self.assertEqual(bom_data['lead_time'], 30 + 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")
+ # Resupply delay = 0 (received from MRP, where route type != "manufacture")
+ # Vendor lead time = 15 (finished product supplier delay)
+ # Manufacture lead time = 30 (BoM.produce_delay)
+ # Max purchase component delay = max delay(comp1, comp2) + po_lead + days_to_purchase = 20
+ self.assertEqual(bom_data['resupply_avail_delay'], 0 + 30 + 20 + 5 + 5,
+ 'Resupply avail delay = Resupply delay + Max(Vendor lead time, Manufacture lead time)'
+ ' + Max purchase component delay + Purchase security lead time + Days to Purchase'
+ )
+ # Continue the test with the original produce_delay
+ self.bom.produce_delay = 10
# 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['stock.quant']._update_available_quantity(self.comp1, self.env.company.subcontracting_location_id, 100)
+ self.env['stock.quant']._update_available_quantity(self.comp2, self.env.company.subcontracting_location_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
@@ -734,3 +754,59 @@ class MrpSubcontractingPurchaseTest(TestMrpSubcontractingCommon):
report_values = self.env['report.mrp.report_bom_structure']._get_report_data(bom.id, searchQty=search_qty_more_than_total, searchVariant=False)
self.assertEqual(report_values['lines']['components'][0]['stock_avail_state'], 'unavailable')
+
+ @freeze_time('2024-01-01')
+ def test_bom_overview_availability_po_lead(self):
+ # Create routes for components and the main product
+ self.env['product.supplierinfo'].create({
+ 'product_tmpl_id': self.finished.product_tmpl_id.id,
+ 'partner_id': self.subcontractor_partner1.id,
+ 'delay': 10
+ })
+ self.env['product.supplierinfo'].create({
+ 'product_tmpl_id': self.comp1.product_tmpl_id.id,
+ 'partner_id': self.subcontractor_partner1.id,
+ 'delay': 5
+ })
+ self.env['product.supplierinfo'].create({
+ 'product_tmpl_id': self.comp2.product_tmpl_id.id,
+ 'partner_id': self.subcontractor_partner1.id,
+ 'delay': 5
+ })
+
+ self.bom.produce_delay = 1
+ self.bom.days_to_prepare_mo = 3
+ # Security Lead Time for Purchase should always be added
+ self.env.company.po_lead = 2
+
+ # Add 4 units of each component to subcontractor's location
+ subcontractor_location = self.env.company.subcontracting_location_id
+ self.env['stock.quant']._update_available_quantity(self.comp1, subcontractor_location, 4)
+ self.env['stock.quant']._update_available_quantity(self.comp2, subcontractor_location, 4)
+
+ # Generate a report for 3 products: all products should be ready for production
+ bom_data = self.env['report.mrp.report_bom_structure']._get_report_data(self.bom.id, 3)
+
+ self.assertTrue(bom_data['lines']['components_available'])
+ for component in bom_data['lines']['components']:
+ self.assertEqual(component['quantity_on_hand'], 4)
+ self.assertEqual(component['availability_state'], 'available')
+ self.assertEqual(bom_data['lines']['earliest_capacity'], 3)
+ # 01/11 + 2 days of Security Lead Time = 01/13
+ self.assertEqual(bom_data['lines']['earliest_date'], '01/13/2024')
+ self.assertTrue('leftover_capacity' not in bom_data['lines']['earliest_date'])
+ self.assertTrue('leftover_date' not in bom_data['lines']['earliest_date'])
+
+ # Generate a report for 5 products: only 4 products should be ready for production
+ bom_data = self.env['report.mrp.report_bom_structure']._get_report_data(self.bom.id, 5)
+
+ self.assertFalse(bom_data['lines']['components_available'])
+ for component in bom_data['lines']['components']:
+ self.assertEqual(component['quantity_on_hand'], 4)
+ self.assertEqual(component['availability_state'], 'estimated')
+ self.assertEqual(bom_data['lines']['earliest_capacity'], 4)
+ # 01/11 + 2 days of Security Lead Time = 01/13
+ self.assertEqual(bom_data['lines']['earliest_date'], '01/13/2024')
+ self.assertEqual(bom_data['lines']['leftover_capacity'], 1)
+ # 01/16 + 2 x 2 days (for components and for final product) = 01/20
+ self.assertEqual(bom_data['lines']['leftover_date'], '01/20/2024')