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/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..5088a314b5 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'), 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..aa33412ab3 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -283,6 +283,13 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {{ item.batch }} {% endif %} + {% if item.packaging %} + + + {% trans "Packaging" %} + {{ item.packaging }} + + {% endif %} {% if item.build %} diff --git a/InvenTree/templates/InvenTree/settings/stock.html b/InvenTree/templates/InvenTree/settings/stock.html index 588f01e0e9..9c82202a02 100644 --- a/InvenTree/templates/InvenTree/settings/stock.html +++ b/InvenTree/templates/InvenTree/settings/stock.html @@ -15,11 +15,12 @@ {% include "InvenTree/settings/header.html" %} - {% include "InvenTree/settings/setting.html" with key="STOCK_ENABLE_EXPIRY" %} - {% include "InvenTree/settings/setting.html" with key="STOCK_STALE_DAYS" %} - {% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_EXPIRED_SALE" %} - {% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_EXPIRED_BUILD" %} - {% include "InvenTree/settings/setting.html" with key="STOCK_OWNERSHIP_CONTROL" %} + {% include "InvenTree/settings/setting.html" with key="STOCK_GROUP_BY_PART" icon="fa-layer-group" %} + {% include "InvenTree/settings/setting.html" with key="STOCK_ENABLE_EXPIRY" icon="fa-stopwatch" %} + {% include "InvenTree/settings/setting.html" with key="STOCK_STALE_DAYS" icon="fa-calendar" %} + {% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_EXPIRED_SALE" icon="fa-truck" %} + {% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_EXPIRED_BUILD" icon="fa-tools" %} + {% include "InvenTree/settings/setting.html" with key="STOCK_OWNERSHIP_CONTROL" icon="fa-users" %}
{% endblock %} diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js index 3e6fee43e4..f1794f3664 100644 --- a/InvenTree/templates/js/stock.js +++ b/InvenTree/templates/js/stock.js @@ -327,11 +327,13 @@ function loadStockTable(table, options) { url: options.url || "{% url 'api-stock-list' %}", queryParams: filters, customSort: customGroupSorter, - groupBy: true, name: 'stock', original: original, showColumns: true, + {% settings_value 'STOCK_GROUP_BY_PART' as group_by_part %} + {% if group_by_part %} groupByField: options.groupByField || 'part', + groupBy: true, groupByFormatter: function(field, id, data) { var row = data[0]; @@ -359,6 +361,29 @@ function loadStockTable(table, options) { else if (field == 'part_detail.description') { return row.part_detail.description; } + else if (field == 'packaging') { + var packaging = []; + + data.forEach(function(item) { + var pkg = item.packaging; + + if (!pkg) { + pkg = '-'; + } + + if (!packaging.includes(pkg)) { + packaging.push(pkg); + } + }); + + if (packaging.length > 1) { + return "..."; + } else if (packaging.length == 1) { + return packaging[0]; + } else { + return "-"; + } + } else if (field == 'quantity') { var stock = 0; var items = 0; @@ -388,7 +413,7 @@ function loadStockTable(table, options) { // Multiple status codes if (statii.length > 1) { - return "-"; + return "..."; } else if (statii.length == 1) { return stockStatusDisplay(statii[0]); } else { @@ -468,6 +493,7 @@ function loadStockTable(table, options) { return ''; } }, + {% endif %} columns: [ { checkbox: true, @@ -619,6 +645,12 @@ function loadStockTable(table, options) { title: '{% trans "Last Updated" %}', sortable: true, }, + { + field: 'packaging', + title: '{% trans "Packaging" %}', + sortable: true, + searchable: true, + }, { field: 'notes', title: '{% trans "Notes" %}',