diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 823fc8e512..bdf7dcb5ae 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -662,6 +662,18 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'validator': bool, }, + # 2021-10-08 + # This setting exists as an interim solution for https://github.com/inventree/InvenTree/issues/2042 + # The BOM API can be extremely slow when calculating pricing information "on the fly" + # A future solution will solve this properly, + # but as an interim step we provide a global to enable / disable BOM pricing + 'PART_SHOW_PRICE_IN_BOM': { + 'name': _('Show Price in BOM'), + 'description': _('Include pricing information in BOM tables'), + 'default': True, + 'validator': bool, + }, + 'PART_SHOW_RELATED': { 'name': _('Show related parts'), 'description': _('Display related parts for a part'), diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 34441286ff..de37d4ea52 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -1100,6 +1100,12 @@ class BomList(generics.ListCreateAPIView): except AttributeError: pass + try: + # Include or exclude pricing information in the serialized data + kwargs['include_pricing'] = str2bool(self.request.GET.get('include_pricing', True)) + except AttributeError: + pass + # Ensure the request context is passed through! kwargs['context'] = self.get_serializer_context() @@ -1141,6 +1147,18 @@ class BomList(generics.ListCreateAPIView): except (ValueError, Part.DoesNotExist): pass + include_pricing = str2bool(params.get('include_pricing', True)) + + if include_pricing: + queryset = self.annotate_pricing(queryset) + + return queryset + + def annotate_pricing(self, queryset): + """ + Add part pricing information to the queryset + """ + # Annotate with purchase prices queryset = queryset.annotate( purchase_price_min=Min('sub_part__stock_items__purchase_price'), diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 4f1ba8cc8b..509de43b68 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -419,6 +419,7 @@ class BomItemSerializer(InvenTreeModelSerializer): part_detail = kwargs.pop('part_detail', False) sub_part_detail = kwargs.pop('sub_part_detail', False) + include_pricing = kwargs.pop('include_pricing', False) super(BomItemSerializer, self).__init__(*args, **kwargs) @@ -428,6 +429,14 @@ class BomItemSerializer(InvenTreeModelSerializer): if sub_part_detail is not True: self.fields.pop('sub_part_detail') + if not include_pricing: + # Remove all pricing related fields + self.fields.pop('price_range') + self.fields.pop('purchase_price_min') + self.fields.pop('purchase_price_max') + self.fields.pop('purchase_price_avg') + self.fields.pop('purchase_price_range') + @staticmethod def setup_eager_loading(queryset): queryset = queryset.prefetch_related('part') diff --git a/InvenTree/templates/InvenTree/settings/part.html b/InvenTree/templates/InvenTree/settings/part.html index 837f59e997..0ec5f56db6 100644 --- a/InvenTree/templates/InvenTree/settings/part.html +++ b/InvenTree/templates/InvenTree/settings/part.html @@ -18,6 +18,7 @@ {% include "InvenTree/settings/setting.html" with key="PART_ALLOW_DUPLICATE_IPN" %} {% include "InvenTree/settings/setting.html" with key="PART_ALLOW_EDIT_IPN" %} {% include "InvenTree/settings/setting.html" with key="PART_SHOW_PRICE_IN_FORMS" icon="fa-dollar-sign" %} + {% include "InvenTree/settings/setting.html" with key="PART_SHOW_PRICE_IN_BOM" icon="fa-dollar-sign" %} {% include "InvenTree/settings/setting.html" with key="PART_SHOW_RELATED" icon="fa-random" %} {% include "InvenTree/settings/setting.html" with key="PART_CREATE_INITIAL" icon="fa-boxes" %} diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 8f571df02e..ec37f71d15 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -148,6 +148,13 @@ function loadBomTable(table, options) { ordering: 'name', }; + // Do we show part pricing in the BOM table? + var show_pricing = global_settings.PART_SHOW_PRICE_IN_BOM; + + if (!show_pricing) { + params.include_pricing = false; + } + if (options.part_detail) { params.part_detail = true; } @@ -282,32 +289,34 @@ function loadBomTable(table, options) { } }); - cols.push({ - field: 'purchase_price_range', - title: '{% trans "Purchase Price Range" %}', - searchable: false, - sortable: true, - }); + if (show_pricing) { + cols.push({ + field: 'purchase_price_range', + title: '{% trans "Purchase Price Range" %}', + searchable: false, + sortable: true, + }); - cols.push({ - field: 'purchase_price_avg', - title: '{% trans "Purchase Price Average" %}', - searchable: false, - sortable: true, - }); + cols.push({ + field: 'purchase_price_avg', + title: '{% trans "Purchase Price Average" %}', + searchable: false, + sortable: true, + }); - cols.push({ - field: 'price_range', - title: '{% trans "Supplier Cost" %}', - sortable: true, - formatter: function(value) { - if (value) { - return value; - } else { - return `{% trans 'No supplier pricing available' %}`; + cols.push({ + field: 'price_range', + title: '{% trans "Supplier Cost" %}', + sortable: true, + formatter: function(value) { + if (value) { + return value; + } else { + return `{% trans 'No supplier pricing available' %}`; + } } - } - }); + }); + } cols.push({ field: 'optional',