diff --git a/InvenTree/InvenTree/static/css/inventree.css b/InvenTree/InvenTree/static/css/inventree.css index ac3d402c3b..a0053e5f53 100644 --- a/InvenTree/InvenTree/static/css/inventree.css +++ b/InvenTree/InvenTree/static/css/inventree.css @@ -50,6 +50,16 @@ border-radius: 5px; } +.index-bg { + width: 100%; + object-fit: fill; + opacity: 5%; +} + +.index-action-selected { + background-color: #EEEEF5; +} + .markdownx .row { margin: 5px; padding: 5px; @@ -487,6 +497,14 @@ background-color: #f33; } +.badge-orange { + background-color: #fcba03; +} + +.badge-green { + background-color: #1A1; +} + .part-thumb { width: 200px; height: 200px; diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 970b2ba92e..c51398e182 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -7,7 +7,7 @@ import django import common.models -INVENTREE_SW_VERSION = "0.1.6 pre" +INVENTREE_SW_VERSION = "0.1.7 pre" def inventreeInstanceName(): diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index d893206126..52f38bea18 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -242,6 +242,13 @@ class InvenTreeSetting(models.Model): 'validator': bool, }, + 'STOCK_GROUP_BY_PART': { + 'name': _('Group by Part'), + 'description': _('Group stock items by part reference in table views'), + 'default': True, + 'validator': bool, + }, + 'BUILDORDER_REFERENCE_PREFIX': { 'name': _('Build Order Reference Prefix'), 'description': _('Prefix value for build order reference'), diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index 731fd193fa..3398760d45 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -184,6 +184,8 @@ class SupplierPartList(generics.ListCreateAPIView): 'manufacturer__name', 'description', 'MPN', + 'part__name', + 'part__description', ] diff --git a/InvenTree/company/templates/company/supplier_part_base.html b/InvenTree/company/templates/company/supplier_part_base.html index 7476a7c606..fde4e745d5 100644 --- a/InvenTree/company/templates/company/supplier_part_base.html +++ b/InvenTree/company/templates/company/supplier_part_base.html @@ -83,12 +83,21 @@ src="{% static 'img/blank_image.png' %}" {% trans "Manufacturer" %} {{ part.manufacturer.name }} + {% endif %} + {% if part.MPN %} {% trans "MPN" %} {{ part.MPN }} {% endif %} + {% if part.packaging %} + + + {% trans "Packaging" %} + {{ part.packaging }} + + {% endif %} {% if part.note %} diff --git a/InvenTree/report/migrations/0013_testreport_include_installed.py b/InvenTree/report/migrations/0013_testreport_include_installed.py new file mode 100644 index 0000000000..3a535bf172 --- /dev/null +++ b/InvenTree/report/migrations/0013_testreport_include_installed.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2021-02-19 04:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('report', '0012_buildreport'), + ] + + operations = [ + migrations.AddField( + model_name='testreport', + name='include_installed', + field=models.BooleanField(default=False, help_text='Include test results for stock items installed inside assembled item', verbose_name='Include Installed Tests'), + ), + ] diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index 4ab6a25bf4..00777449fc 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -281,6 +281,12 @@ class TestReport(ReportTemplateBase): ] ) + include_installed = models.BooleanField( + default=False, + verbose_name=_('Include Installed Tests'), + help_text=_('Include test results for stock items installed inside assembled item') + ) + def matches_stock_item(self, item): """ Test if this report template matches a given StockItem objects @@ -304,8 +310,8 @@ class TestReport(ReportTemplateBase): return { 'stock_item': stock_item, 'part': stock_item.part, - 'results': stock_item.testResultMap(), - 'result_list': stock_item.testResultList() + 'results': stock_item.testResultMap(include_installed=self.include_installed), + 'result_list': stock_item.testResultList(include_installed=self.include_installed) } diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 36ea8d453d..4e532b90b7 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -684,10 +684,17 @@ class StockList(generics.ListCreateAPIView): try: part = Part.objects.get(pk=part_id) - # Filter by any parts "under" the given part - parts = part.get_descendants(include_self=True) + # Do we wish to filter *just* for this part, or also for parts *under* this one? + include_variants = str2bool(params.get('include_variants', True)) - queryset = queryset.filter(part__in=parts) + if include_variants: + # Filter by any parts "under" the given part + parts = part.get_descendants(include_self=True) + + queryset = queryset.filter(part__in=parts) + + else: + queryset = queryset.filter(part=part) except (ValueError, Part.DoesNotExist): raise ValidationError({"part": "Invalid Part ID specified"}) diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 7659981ecd..0bec20a3e8 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -134,6 +134,7 @@ class CreateStockItemForm(HelperForm): 'quantity', 'batch', 'serial_numbers', + 'packaging', 'purchase_price', 'expiry_date', 'link', @@ -414,6 +415,7 @@ class EditStockItemForm(HelperForm): 'status', 'expiry_date', 'purchase_price', + 'packaging', 'link', 'delete_on_deplete', 'owner', diff --git a/InvenTree/stock/migrations/0058_stockitem_packaging.py b/InvenTree/stock/migrations/0058_stockitem_packaging.py new file mode 100644 index 0000000000..ee33724588 --- /dev/null +++ b/InvenTree/stock/migrations/0058_stockitem_packaging.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2021-02-19 00:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0057_stock_location_item_owner'), + ] + + operations = [ + migrations.AddField( + model_name='stockitem', + name='packaging', + field=models.CharField(blank=True, help_text='Packaging this stock item is stored in', max_length=50, null=True, verbose_name='Packaging'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 84cc696593..1be988fed0 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -155,6 +155,7 @@ class StockItem(MPTTModel): infinite: If True this StockItem can never be exhausted sales_order: Link to a SalesOrder object (if the StockItem has been assigned to a SalesOrder) purchase_price: The unit purchase price for this StockItem - this is the unit price at time of purchase (if this item was purchased from an external supplier) + packaging: Description of how the StockItem is packaged (e.g. "reel", "loose", "tape" etc) """ # A Query filter which will be re-used in multiple places to determine if a StockItem is actually "in stock" @@ -387,6 +388,13 @@ class StockItem(MPTTModel): help_text=_('Where is this stock item located?') ) + packaging = models.CharField( + max_length=50, + blank=True, null=True, + verbose_name=_('Packaging'), + help_text=_('Packaging this stock item is stored in') + ) + belongs_to = models.ForeignKey( 'self', verbose_name=_('Installed In'), @@ -699,6 +707,41 @@ class StockItem(MPTTModel): return True + def get_installed_items(self, cascade=False): + """ + Return all stock items which are *installed* in this one! + + Args: + cascade - Include items which are installed in items which are installed in items + + Note: This function is recursive, and may result in a number of database hits! + """ + + installed = set() + + items = StockItem.objects.filter(belongs_to=self) + + for item in items: + + # Prevent duplication or recursion + if item == self or item in installed: + continue + + installed.add(item) + + if cascade: + sub_items = item.get_installed_items(cascade=True) + + for sub_item in sub_items: + + # Prevent recursion + if sub_item == self or sub_item in installed: + continue + + installed.add(sub_item) + + return installed + def installedItemCount(self): """ Return the number of stock items installed inside this one. @@ -824,6 +867,27 @@ class StockItem(MPTTModel): return query.exists() + @property + def can_adjust_location(self): + """ + Returns True if the stock location can be "adjusted" for this part + + Cannot be adjusted if: + - Has been delivered to a customer + - Has been installed inside another StockItem + """ + + if self.customer is not None: + return False + + if self.belongs_to is not None: + return False + + if self.sales_order is not None: + return False + + return True + @property def tracking_info_count(self): return self.tracking_info.count() @@ -1276,6 +1340,9 @@ class StockItem(MPTTModel): as all named tests are accessible. """ + # Do we wish to include test results from installed items? + include_installed = kwargs.pop('include_installed', False) + # Filter results by "date", so that newer results # will override older ones. results = self.getTestResults(**kwargs).order_by('date') @@ -1286,6 +1353,20 @@ class StockItem(MPTTModel): key = helpers.generateTestKey(result.test) result_map[key] = result + # Do we wish to "cascade" and include test results from installed stock items? + cascade = kwargs.get('cascade', False) + + if include_installed: + installed_items = self.get_installed_items(cascade=cascade) + + for item in installed_items: + item_results = item.testResultMap() + + for key in item_results.keys(): + # Results from sub items should not override master ones + if key not in result_map.keys(): + result_map[key] = item_results[key] + return result_map def testResultList(self, **kwargs): diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index b8e71ff58c..9cb2538ba7 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -198,6 +198,7 @@ class StockItemSerializer(InvenTreeModelSerializer): 'location', 'location_detail', 'notes', + 'packaging', 'part', 'part_detail', 'pk', diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 93d60fb3d1..a9c8337b57 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -155,7 +155,7 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}