Repository: inventree/inventree
Issue: [BUG] Manufacturer part data missing from BOM

When exporting a BOM with manufacturer / supplier part information, manufacturer part data is excluded from the BOM if the manufacturer part is not linked from a particular supplier.

The current code iterates through the available supplier parts and references any manufacturer parts from there. However it misses any manufacturer parts which are not explicitly linked to a supplier part.


Changed files:
InvenTree/part/bom.py

diff --git a/InvenTree/part/bom.py b/InvenTree/part/bom.py
index f67e4ffe8..d09cc5130 100644
--- a/InvenTree/part/bom.py
+++ b/InvenTree/part/bom.py
@@ -160,171 +160,108 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=Fa
         # Add stock columns to dataset
         add_columns_to_dataset(stock_cols, len(bom_items))
 
-    if manufacturer_data and supplier_data:
+    if manufacturer_data or supplier_data:
         """
         If requested, add extra columns for each SupplierPart and ManufacturerPart associated with each line item
         """
 
-        # Expand dataset with manufacturer parts
-        manufacturer_headers = [
-            _('Manufacturer'),
-            _('MPN'),
-        ]
-
-        supplier_headers = [
-            _('Supplier'),
-            _('SKU'),
-        ]
+        # Keep track of the supplier parts we have already exported
+        supplier_parts_used = set()
 
         manufacturer_cols = {}
 
-        for b_idx, bom_item in enumerate(bom_items):
+        for bom_idx, bom_item in enumerate(bom_items):
             # Get part instance
             b_part = bom_item.sub_part
 
-            # Filter manufacturer parts
-            manufacturer_parts = ManufacturerPart.objects.filter(part__pk=b_part.pk)
-            manufacturer_parts = manufacturer_parts.prefetch_related('supplier_parts')
-
-            # Process manufacturer part
-            for manufacturer_idx, manufacturer_part in enumerate(manufacturer_parts):
-
-                if manufacturer_part and manufacturer_part.manufacturer:
-                    manufacturer_name = manufacturer_part.manufacturer.name
-                else:
-                    manufacturer_name = ''
-
-                if manufacturer_part:
-                    manufacturer_mpn = manufacturer_part.MPN
-                else:
-                    manufacturer_mpn = ''
+            # Include manufacturer data for each BOM item
+            if manufacturer_data:
 
-                # Generate column names for this manufacturer
-                k_man = manufacturer_headers[0] + "_" + str(manufacturer_idx)
-                k_mpn = manufacturer_headers[1] + "_" + str(manufacturer_idx)
+                # Filter manufacturer parts
+                manufacturer_parts = ManufacturerPart.objects.filter(part__pk=b_part.pk).prefetch_related('supplier_parts')
+                
+                for mp_idx, mp_part in enumerate(manufacturer_parts):
 
-                try:
-                    manufacturer_cols[k_man].update({b_idx: manufacturer_name})
-                    manufacturer_cols[k_mpn].update({b_idx: manufacturer_mpn})
-                except KeyError:
-                    manufacturer_cols[k_man] = {b_idx: manufacturer_name}
-                    manufacturer_cols[k_mpn] = {b_idx: manufacturer_mpn}
-
-                # Process supplier parts
-                for supplier_idx, supplier_part in enumerate(manufacturer_part.supplier_parts.all()):
-
-                    if supplier_part.supplier and supplier_part.supplier:
-                        supplier_name = supplier_part.supplier.name
+                    # Extract the "name" field of the Manufacturer (Company)
+                    if mp_part and mp_part.manufacturer:
+                        manufacturer_name = mp_part.manufacturer.name
                     else:
-                        supplier_name = ''
+                        manufacturer_name = ''
 
-                    if supplier_part:
-                        supplier_sku = supplier_part.SKU
+                    # Extract the "MPN" field from the Manufacturer Part
+                    if mp_part:
+                        manufacturer_mpn = mp_part.MPN
                     else:
-                        supplier_sku = ''
-
-                    # Generate column names for this supplier
-                    k_sup = str(supplier_headers[0]) + "_" + str(manufacturer_idx) + "_" + str(supplier_idx)
-                    k_sku = str(supplier_headers[1]) + "_" + str(manufacturer_idx) + "_" + str(supplier_idx)
+                        manufacturer_mpn = ''
 
+                    # Generate a column name for this manufacturer
+                    k_man = f'{_("Manufacturer")}_{mp_idx}'
+                    k_mpn = f'{_("MPN")}_{mp_idx}'
+                    
                     try:
-                        manufacturer_cols[k_sup].update({b_idx: supplier_name})
-                        manufacturer_cols[k_sku].update({b_idx: supplier_sku})
+                        manufacturer_cols[k_man].update({bom_idx: manufacturer_name})
+                        manufacturer_cols[k_mpn].update({bom_idx: manufacturer_mpn})
                     except KeyError:
-                        manufacturer_cols[k_sup] = {b_idx: supplier_name}
-                        manufacturer_cols[k_sku] = {b_idx: supplier_sku}
+                        manufacturer_cols[k_man] = {bom_idx: manufacturer_name}
+                        manufacturer_cols[k_mpn] = {bom_idx: manufacturer_mpn}
 
-        # Add manufacturer columns to dataset
-        add_columns_to_dataset(manufacturer_cols, len(bom_items))
+                    # We wish to include supplier data for this manufacturer part
+                    if supplier_data:
+                        
+                        for sp_idx, sp_part in enumerate(mp_part.supplier_parts.all()):
 
-    elif manufacturer_data:
-        """
-        If requested, add extra columns for each ManufacturerPart associated with each line item
-        """
+                            supplier_parts_used.add(sp_part)
 
-        # Expand dataset with manufacturer parts
-        manufacturer_headers = [
-            _('Manufacturer'),
-            _('MPN'),
-        ]
+                            if sp_part.supplier and sp_part.supplier:
+                                supplier_name = sp_part.supplier.name
+                            else:
+                                supplier_name = ''
 
-        manufacturer_cols = {}
+                            if sp_part:
+                                supplier_sku = sp_part.SKU
+                            else:
+                                supplier_sku = ''
 
-        for b_idx, bom_item in enumerate(bom_items):
-            # Get part instance
-            b_part = bom_item.sub_part
+                            # Generate column names for this supplier
+                            k_sup = str(_("Supplier")) + "_" + str(mp_idx) + "_" + str(sp_idx)
+                            k_sku = str(_("SKU")) + "_" + str(mp_idx) + "_" + str(sp_idx)
 
-            # Filter supplier parts
-            manufacturer_parts = ManufacturerPart.objects.filter(part__pk=b_part.pk)
+                            try:
+                                manufacturer_cols[k_sup].update({bom_idx: supplier_name})
+                                manufacturer_cols[k_sku].update({bom_idx: supplier_sku})
+                            except KeyError:
+                                manufacturer_cols[k_sup] = {bom_idx: supplier_name}
+                                manufacturer_cols[k_sku] = {bom_idx: supplier_sku}
 
-            for idx, manufacturer_part in enumerate(manufacturer_parts):
+            if supplier_data:
+                # Add in any extra supplier parts, which are not associated with a manufacturer part
 
-                if manufacturer_part:
-                    manufacturer_name = manufacturer_part.manufacturer.name
-                else:
-                    manufacturer_name = ''
-
-                manufacturer_mpn = manufacturer_part.MPN
+                for sp_idx, sp_part in enumerate(SupplierPart.objects.filter(part__pk=b_part.pk)):
 
-                # Add manufacturer data to the manufacturer columns
+                    if sp_part in supplier_parts_used:
+                        continue
 
-                # Generate column names for this manufacturer
-                k_man = manufacturer_headers[0] + "_" + str(idx)
-                k_mpn = manufacturer_headers[1] + "_" + str(idx)
+                    supplier_parts_used.add(sp_part)
 
-                try:
-                    manufacturer_cols[k_man].update({b_idx: manufacturer_name})
-                    manufacturer_cols[k_mpn].update({b_idx: manufacturer_mpn})
-                except KeyError:
-                    manufacturer_cols[k_man] = {b_idx: manufacturer_name}
-                    manufacturer_cols[k_mpn] = {b_idx: manufacturer_mpn}
-
-        # Add manufacturer columns to dataset
-        add_columns_to_dataset(manufacturer_cols, len(bom_items))
-
-    elif supplier_data:
-        """
-        If requested, add extra columns for each SupplierPart associated with each line item
-        """
-
-        # Expand dataset with manufacturer parts
-        manufacturer_headers = [
-            _('Supplier'),
-            _('SKU'),
-        ]
-
-        manufacturer_cols = {}
-
-        for b_idx, bom_item in enumerate(bom_items):
-            # Get part instance
-            b_part = bom_item.sub_part
-
-            # Filter supplier parts
-            supplier_parts = SupplierPart.objects.filter(part__pk=b_part.pk)
-
-            for idx, supplier_part in enumerate(supplier_parts):
-
-                if supplier_part.supplier:
-                    supplier_name = supplier_part.supplier.name
-                else:
-                    supplier_name = ''
-
-                supplier_sku = supplier_part.SKU
+                    if sp_part.supplier:
+                        supplier_name = sp_part.supplier.name
+                    else:
+                        supplier_name = ''
 
-                # Add manufacturer data to the manufacturer columns
+                    supplier_sku = sp_part.SKU
 
-                # Generate column names for this supplier
-                k_sup = manufacturer_headers[0] + "_" + str(idx)
-                k_sku = manufacturer_headers[1] + "_" + str(idx)
+                    # Generate column names for this supplier
+                    k_sup = str(_("Supplier")) + "_" + str(sp_idx)
+                    k_sku = str(_("SKU")) + "_" + str(sp_idx)
 
-                try:
-                    manufacturer_cols[k_sup].update({b_idx: supplier_name})
-                    manufacturer_cols[k_sku].update({b_idx: supplier_sku})
-                except KeyError:
-                    manufacturer_cols[k_sup] = {b_idx: supplier_name}
-                    manufacturer_cols[k_sku] = {b_idx: supplier_sku}
+                    try:
+                        manufacturer_cols[k_sup].update({bom_idx: supplier_name})
+                        manufacturer_cols[k_sku].update({bom_idx: supplier_sku})
+                    except KeyError:
+                        manufacturer_cols[k_sup] = {bom_idx: supplier_name}
+                        manufacturer_cols[k_sku] = {bom_idx: supplier_sku}
 
-        # Add manufacturer columns to dataset
+        # Add supplier columns to dataset
         add_columns_to_dataset(manufacturer_cols, len(bom_items))
 
     data = dataset.export(fmt)