From 6fc275ca300613116bea1631bef3872fddd6397b Mon Sep 17 00:00:00 2001 From: eeintech Date: Thu, 20 Aug 2020 13:53:27 -0500 Subject: [PATCH 1/3] BoM export: added option to export part paremeters (#126) and stocks (#793) --- InvenTree/part/bom.py | 78 +++++++++++++++++++++++++++++++++++++---- InvenTree/part/forms.py | 6 +++- InvenTree/part/views.py | 16 ++++++++- 3 files changed, 91 insertions(+), 9 deletions(-) diff --git a/InvenTree/part/bom.py b/InvenTree/part/bom.py index 86b33bc02f..9b3e6997d6 100644 --- a/InvenTree/part/bom.py +++ b/InvenTree/part/bom.py @@ -7,6 +7,8 @@ from rapidfuzz import fuzz import tablib import os +from collections import OrderedDict + from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError @@ -47,7 +49,7 @@ def MakeBomTemplate(fmt): return DownloadFile(data, filename) -def ExportBom(part, fmt='csv', cascade=False, max_levels=None, supplier_data=False): +def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=False, stock_data=False, supplier_data=False): """ Export a BOM (Bill of Materials) for a given part. Args: @@ -92,9 +94,75 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, supplier_data=Fal dataset = BomItemResource().export(queryset=bom_items, cascade=cascade) + def add_columns_to_dataset(columns, column_size): + try: + for header, column_dict in columns.items(): + # Construct column tuple + col = tuple(column_dict.get(c_idx, '') for c_idx in range(column_size)) + # Add column to dataset + dataset.append_col(col, header=header) + except AttributeError: + pass + + if parameter_data: + """ + If requested, add extra columns for each PartParameter associated with each line item + """ + + parameter_cols = {} + + for b_idx, bom_item in enumerate(bom_items): + # Get part parameters + parameters = bom_item.sub_part.get_parameters() + # Add parameters to columns + if parameters: + for parameter in parameters: + name = parameter.template.name + value = parameter.data + + try: + parameter_cols[name].update({b_idx: value}) + except KeyError: + parameter_cols[name] = {b_idx: value} + + # Add parameter columns to dataset + parameter_cols_ordered = OrderedDict(sorted(parameter_cols.items(), key=lambda x: x[0])) + add_columns_to_dataset(parameter_cols_ordered, len(bom_items)) + + if stock_data: + """ + If requested, add extra columns for stock data associated with each line item + """ + + stock_headers = [ + _('Location'), + _('Available Stock'), + ] + + stock_cols = {} + + for b_idx, bom_item in enumerate(bom_items): + stock_data = [] + # Get part default location + try: + stock_data.append(bom_item.sub_part.get_default_location().name) + except AttributeError: + stock_data.append('') + # Get part current stock + stock_data.append(bom_item.sub_part.available_stock) + + for s_idx, header in enumerate(stock_headers): + try: + stock_cols[header].update({b_idx: stock_data[s_idx]}) + except KeyError: + stock_cols[header] = {b_idx: stock_data[s_idx]} + + # Add stock columns to dataset + add_columns_to_dataset(stock_cols, len(bom_items)) + if supplier_data: """ - If requested, add extra columns for each SupplierPart associated with the each line item + If requested, add extra columns for each SupplierPart associated with each line item """ # Expand dataset with manufacturer parts @@ -150,11 +218,7 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, supplier_data=Fal 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) + add_columns_to_dataset(manufacturer_cols, len(bom_items)) data = dataset.export(fmt) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 7b252067e8..8dccf7f28b 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -58,7 +58,11 @@ class BomExportForm(forms.Form): 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")) + parameter_data = forms.BooleanField(label=_("Include Parameter Data"), required=False, initial=False, help_text=_("Include part parameters data in exported BOM")) + + stock_data = forms.BooleanField(label=_("Include Stock Data"), required=False, initial=False, help_text=_("Include part stock data in exported BOM")) + + supplier_data = forms.BooleanField(label=_("Include Supplier Data"), required=False, initial=True, help_text=_("Include part supplier 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 3afb863791..b8e157bfe3 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1499,6 +1499,10 @@ class BomDownload(AjaxView): cascade = str2bool(request.GET.get('cascade', False)) + parameter_data = str2bool(request.GET.get('parameter_data', False)) + + stock_data = str2bool(request.GET.get('stock_data', False)) + supplier_data = str2bool(request.GET.get('supplier_data', False)) levels = request.GET.get('levels', None) @@ -1516,7 +1520,13 @@ class BomDownload(AjaxView): if not IsValidBOMFormat(export_format): export_format = 'csv' - return ExportBom(part, fmt=export_format, cascade=cascade, max_levels=levels, supplier_data=supplier_data) + return ExportBom( part, + fmt=export_format, + cascade=cascade, + max_levels=levels, + parameter_data=parameter_data, + stock_data=stock_data, + supplier_data=supplier_data) def get_data(self): return { @@ -1541,6 +1551,8 @@ class BomExport(AjaxView): fmt = request.POST.get('file_format', 'csv').lower() cascade = str2bool(request.POST.get('cascading', False)) levels = request.POST.get('levels', None) + parameter_data = str2bool(request.POST.get('parameter_data', False)) + stock_data = str2bool(request.POST.get('stock_data', False)) supplier_data = str2bool(request.POST.get('supplier_data', False)) try: @@ -1556,6 +1568,8 @@ class BomExport(AjaxView): url += '?file_format=' + fmt url += '&cascade=' + str(cascade) + url += '¶meter_data=' + str(parameter_data) + url += '&stock_data=' + str(stock_data) url += '&supplier_data=' + str(supplier_data) if levels: From 89e63df1fb2a3c1306b66d50925333aefafd1593 Mon Sep 17 00:00:00 2001 From: eeintech Date: Thu, 20 Aug 2020 14:53:03 -0500 Subject: [PATCH 2/3] Corrected style --- InvenTree/part/views.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index b8e157bfe3..35d6d5cf51 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1520,13 +1520,13 @@ class BomDownload(AjaxView): if not IsValidBOMFormat(export_format): export_format = 'csv' - return ExportBom( part, - fmt=export_format, - cascade=cascade, - max_levels=levels, - parameter_data=parameter_data, - stock_data=stock_data, - supplier_data=supplier_data) + return ExportBom(part, + fmt=export_format, + cascade=cascade, + max_levels=levels, + parameter_data=parameter_data, + stock_data=stock_data, + supplier_data=supplier_data) def get_data(self): return { From 5d6def75cccadc1543c2633005753869250e627b Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 25 Aug 2020 16:02:46 -0500 Subject: [PATCH 3/3] BoM export, Part stock: changed 'Location' header to 'Default Location' --- InvenTree/part/bom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/part/bom.py b/InvenTree/part/bom.py index 9b3e6997d6..092b3e3183 100644 --- a/InvenTree/part/bom.py +++ b/InvenTree/part/bom.py @@ -135,7 +135,7 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=Fa """ stock_headers = [ - _('Location'), + _('Default Location'), _('Available Stock'), ]