diff --git a/InvenTree/part/bom.py b/InvenTree/part/bom.py index 8f1241de01..86b33bc02f 100644 --- a/InvenTree/part/bom.py +++ b/InvenTree/part/bom.py @@ -14,6 +14,7 @@ from InvenTree.helpers import DownloadFile, GetExportFormats from .admin import BomItemResource from .models import BomItem +from company.models import SupplierPart def IsValidBOMFormat(fmt): @@ -46,7 +47,7 @@ def MakeBomTemplate(fmt): return DownloadFile(data, filename) -def ExportBom(part, fmt='csv', cascade=False, max_levels=None): +def ExportBom(part, fmt='csv', cascade=False, max_levels=None, supplier_data=False): """ Export a BOM (Bill of Materials) for a given part. Args: @@ -90,6 +91,71 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None): bom_items = [item for item in part.bom_items.all().order_by('id')] dataset = BomItemResource().export(queryset=bom_items, cascade=cascade) + + if supplier_data: + """ + If requested, add extra columns for each SupplierPart associated with the each line item + """ + + # Expand dataset with manufacturer parts + manufacturer_headers = [ + _('Supplier'), + _('SKU'), + _('Manufacturer'), + _('MPN'), + ] + + 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 supplier_part.manufacturer: + manufacturer_name = supplier_part.manufacturer.name + else: + manufacturer_name = '' + + manufacturer_mpn = supplier_part.MPN + + # Add manufacturer data to the manufacturer columns + + # Generate column names for this supplier + k_sup = manufacturer_headers[0] + "_" + str(idx) + k_sku = manufacturer_headers[1] + "_" + str(idx) + k_man = manufacturer_headers[2] + "_" + str(idx) + k_mpn = manufacturer_headers[3] + "_" + str(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({b_idx: manufacturer_name}) + manufacturer_cols[k_mpn].update({b_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] = {b_idx: manufacturer_name} + manufacturer_cols[k_mpn] = {b_idx: manufacturer_mpn} + + # Add manufacturer columns to dataset + for header, col_dict in manufacturer_cols.items(): + # Construct column tuple + col = tuple(col_dict.get(c_idx, '') for c_idx in range(len(bom_items))) + # Add column to dataset + dataset.append_col(col, header=header) + data = dataset.export(fmt) filename = '{n}_BOM.{fmt}'.format(n=part.full_name, fmt=fmt) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index dbbced352b..7b252067e8 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -54,10 +54,12 @@ class BomExportForm(forms.Form): file_format = forms.ChoiceField(label=_("File Format"), help_text=_("Select output file format")) - cascading = forms.BooleanField(label=_("Cascading"), required=False, initial=False, help_text=_("Download cascading / multi-level BOM")) + cascading = forms.BooleanField(label=_("Cascading"), required=False, initial=True, help_text=_("Download cascading / multi-level BOM")) levels = forms.IntegerField(label=_("Levels"), required=True, initial=0, help_text=_("Select maximum number of BOM levels to export (0 = all levels)")) + supplier_data = forms.BooleanField(label=_("Include Supplier Data"), required=False, initial=True, help_text=_("Include supplier part data in exported BOM")) + def get_choices(self): """ BOM export format choices """ diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index a01b6b4ff2..3afb863791 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1499,6 +1499,8 @@ class BomDownload(AjaxView): cascade = str2bool(request.GET.get('cascade', False)) + supplier_data = str2bool(request.GET.get('supplier_data', False)) + levels = request.GET.get('levels', None) if levels is not None: @@ -1514,7 +1516,7 @@ class BomDownload(AjaxView): if not IsValidBOMFormat(export_format): export_format = 'csv' - return ExportBom(part, fmt=export_format, cascade=cascade, max_levels=levels) + return ExportBom(part, fmt=export_format, cascade=cascade, max_levels=levels, supplier_data=supplier_data) def get_data(self): return { @@ -1539,6 +1541,7 @@ class BomExport(AjaxView): fmt = request.POST.get('file_format', 'csv').lower() cascade = str2bool(request.POST.get('cascading', False)) levels = request.POST.get('levels', None) + supplier_data = str2bool(request.POST.get('supplier_data', False)) try: part = Part.objects.get(pk=self.kwargs['pk']) @@ -1553,6 +1556,7 @@ class BomExport(AjaxView): url += '?file_format=' + fmt url += '&cascade=' + str(cascade) + url += '&supplier_data=' + str(supplier_data) if levels: url += '&levels=' + str(levels)