diff --git a/InvenTree/InvenTree/static/script/inventree/filters.js b/InvenTree/InvenTree/static/script/inventree/filters.js
index 8c9ebbec6d..3209ba3beb 100644
--- a/InvenTree/InvenTree/static/script/inventree/filters.js
+++ b/InvenTree/InvenTree/static/script/inventree/filters.js
@@ -272,8 +272,9 @@ function setupFilterList(tableKey, table, target) {
for (var key in filters) {
var value = getFilterOptionValue(tableKey, key, filters[key]);
var title = getFilterTitle(tableKey, key);
+ var description = getFilterDescription(tableKey, key);
- element.append(`
${title} = ${value}x
`);
+ element.append(`
${title} = ${value}x
`);
}
// Add a callback for adding a new filter
@@ -362,6 +363,15 @@ function getFilterTitle(tableKey, filterKey) {
}
+/**
+ * Return the pretty description for the given table and filter selection
+ */
+function getFilterDescription(tableKey, filterKey) {
+ var settings = getFilterSettings(tableKey, filterKey);
+
+ return settings.title;
+}
+
/*
* Return a description for the given table and filter selection.
*/
diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html
index e9e09959fb..1e7d36e127 100644
--- a/InvenTree/part/templates/part/detail.html
+++ b/InvenTree/part/templates/part/detail.html
@@ -33,6 +33,13 @@
+ {% endif %}
{% if part.component or part.used_in_count > 0 %}
{% trans "Allocated" %} {% decimal part.allocation_count %}
diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py
index 516edc5040..23d836b1e1 100644
--- a/InvenTree/stock/api.py
+++ b/InvenTree/stock/api.py
@@ -487,7 +487,20 @@ class StockList(generics.ListCreateAPIView):
if serial_number is not None:
queryset = queryset.filter(serial=serial_number)
+
+ # Filter by range of serial numbers?
+ serial_number_gte = params.get('serial_gte', None)
+ serial_number_lte = params.get('serial_lte', None)
+
+ if serial_number_gte is not None or serial_number_lte is not None:
+ queryset = queryset.exclude(serial=None)
+
+ if serial_number_gte is not None:
+ queryset = queryset.filter(serial__gte=serial_number_gte)
+ if serial_number_lte is not None:
+ queryset = queryset.filter(serial__lte=serial_number_lte)
+
in_stock = params.get('in_stock', None)
if in_stock is not None:
diff --git a/InvenTree/stock/templates/stock/stock_adjust.html b/InvenTree/stock/templates/stock/stock_adjust.html
index 566640544c..a72407f735 100644
--- a/InvenTree/stock/templates/stock/stock_adjust.html
+++ b/InvenTree/stock/templates/stock/stock_adjust.html
@@ -32,7 +32,7 @@
+ value='{% decimal item.new_quantity %}' type='number' name='stock-id-{{ item.id }}' id='stock-id-{{ item.id }}'/>
{% if item.error %}
{{ item.error }}
{% endif %}
diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py
index ae6f593717..8d3e5ebbf1 100644
--- a/InvenTree/stock/views.py
+++ b/InvenTree/stock/views.py
@@ -1036,6 +1036,36 @@ class StockItemCreate(AjaxCreateView):
ajax_template_name = 'modal_form.html'
ajax_form_title = _('Create new Stock Item')
+ def get_part(self, form=None):
+ """
+ Attempt to get the "part" associted with this new stockitem.
+
+ - May be passed to the form as a query parameter (e.g. ?part=)
+ - May be passed via the form field itself.
+ """
+
+ # Try to extract from the URL query
+ part_id = self.request.GET.get('part', None)
+
+ if part_id:
+ try:
+ part = Part.objects.get(pk=part_id)
+ return part
+ except (Part.DoesNotExist, ValueError):
+ pass
+
+ # Try to get from the form
+ if form:
+ try:
+ part_id = form['part'].value()
+ part = Part.objects.get(pk=part_id)
+ return part
+ except (Part.DoesNotExist, ValueError):
+ pass
+
+ # Could not extract a part object
+ return None
+
def get_form(self):
""" Get form for StockItem creation.
Overrides the default get_form() method to intelligently limit
@@ -1044,53 +1074,44 @@ class StockItemCreate(AjaxCreateView):
form = super().get_form()
- part = None
+ part = self.get_part(form=form)
- # If the user has selected a Part, limit choices for SupplierPart
- if form['part'].value():
- part_id = form['part'].value()
+ if part is not None:
+ sn = part.getNextSerialNumber()
+ form.field_placeholder['serial_numbers'] = _('Next available serial number is') + ' ' + str(sn)
- try:
- part = Part.objects.get(id=part_id)
-
- sn = part.getNextSerialNumber()
- form.field_placeholder['serial_numbers'] = _('Next available serial number is') + ' ' + str(sn)
+ form.rebuild_layout()
- form.rebuild_layout()
+ # Hide the 'part' field (as a valid part is selected)
+ form.fields['part'].widget = HiddenInput()
- # Hide the 'part' field (as a valid part is selected)
- form.fields['part'].widget = HiddenInput()
+ # trackable parts get special consideration
+ if part.trackable:
+ form.fields['delete_on_deplete'].widget = HiddenInput()
+ form.fields['delete_on_deplete'].initial = False
+ else:
+ form.fields.pop('serial_numbers')
- # trackable parts get special consideration
- if part.trackable:
- form.fields['delete_on_deplete'].widget = HiddenInput()
- form.fields['delete_on_deplete'].initial = False
- else:
- form.fields.pop('serial_numbers')
+ # If the part is NOT purchaseable, hide the supplier_part field
+ if not part.purchaseable:
+ form.fields['supplier_part'].widget = HiddenInput()
+ else:
+ # Pre-select the allowable SupplierPart options
+ parts = form.fields['supplier_part'].queryset
+ parts = parts.filter(part=part.id)
- # If the part is NOT purchaseable, hide the supplier_part field
- if not part.purchaseable:
- form.fields['supplier_part'].widget = HiddenInput()
- else:
- # Pre-select the allowable SupplierPart options
- parts = form.fields['supplier_part'].queryset
- parts = parts.filter(part=part.id)
+ form.fields['supplier_part'].queryset = parts
- form.fields['supplier_part'].queryset = parts
+ # If there is one (and only one) supplier part available, pre-select it
+ all_parts = parts.all()
- # If there is one (and only one) supplier part available, pre-select it
- all_parts = parts.all()
+ if len(all_parts) == 1:
- if len(all_parts) == 1:
-
- # TODO - This does NOT work for some reason? Ref build.views.BuildItemCreate
- form.fields['supplier_part'].initial = all_parts[0].id
-
- except Part.DoesNotExist:
- pass
+ # TODO - This does NOT work for some reason? Ref build.views.BuildItemCreate
+ form.fields['supplier_part'].initial = all_parts[0].id
# Otherwise if the user has selected a SupplierPart, we know what Part they meant!
- elif form['supplier_part'].value() is not None:
+ if form['supplier_part'].value() is not None:
pass
return form
@@ -1113,27 +1134,20 @@ class StockItemCreate(AjaxCreateView):
else:
initials = super(StockItemCreate, self).get_initial().copy()
- part_id = self.request.GET.get('part', None)
+ part = self.get_part()
+
loc_id = self.request.GET.get('location', None)
sup_part_id = self.request.GET.get('supplier_part', None)
- part = None
location = None
supplier_part = None
- # Part field has been specified
- if part_id:
- try:
- part = Part.objects.get(pk=part_id)
-
- # Check that the supplied part is 'valid'
- if not part.is_template and part.active and not part.virtual:
- initials['part'] = part
- initials['location'] = part.get_default_location()
- initials['supplier_part'] = part.default_supplier
-
- except (ValueError, Part.DoesNotExist):
- pass
+ if part is not None:
+ # Check that the supplied part is 'valid'
+ if not part.is_template and part.active and not part.virtual:
+ initials['part'] = part
+ initials['location'] = part.get_default_location()
+ initials['supplier_part'] = part.default_supplier
# SupplierPart field has been specified
# It must match the Part, if that has been supplied
diff --git a/InvenTree/templates/js/stock.html b/InvenTree/templates/js/stock.html
index fd2dd61746..c0737dc250 100644
--- a/InvenTree/templates/js/stock.html
+++ b/InvenTree/templates/js/stock.html
@@ -337,12 +337,21 @@ function loadStockTable(table, options) {
} else {
return '-';
}
- } else if (field == 'location__path') {
+ } else if (field == 'location_detail.pathstring') {
/* Determine how many locations */
var locations = [];
data.forEach(function(item) {
- var loc = item.location;
+
+ var loc = null;
+
+ if (item.location_detail) {
+ loc = item.location_detail.pathstring;
+ } else {
+ loc = "{% trans "Undefined location" %}";
+ }
+
+ console.log("Location: " + loc);
if (!locations.includes(loc)) {
locations.push(loc);
@@ -353,7 +362,11 @@ function loadStockTable(table, options) {
return "In " + locations.length + " locations";
} else {
// A single location!
- return renderLink(row.location__path, '/stock/location/' + row.location + '/')
+ if (row.location_detail) {
+ return renderLink(row.location_detail.pathstring, `/stock/location/${row.location}/`);
+ } else {
+ return "{% trans "Undefined location" %}";
+ }
}
} else if (field == 'notes') {
var notes = [];
diff --git a/InvenTree/templates/js/table_filters.html b/InvenTree/templates/js/table_filters.html
index 84b517e8e7..298a034517 100644
--- a/InvenTree/templates/js/table_filters.html
+++ b/InvenTree/templates/js/table_filters.html
@@ -34,6 +34,14 @@ function getAvailableTableFilters(tableKey) {
title: '{% trans "Is allocated" %}',
description: '{% trans "Item has been alloacted" %}',
},
+ serial_gte: {
+ title: "{% trans "Serial number GTE" %}",
+ description: "{% trans "Serial number greater than or equal to" %}"
+ },
+ serial_lte: {
+ title: "{% trans "Serial number LTE" %}",
+ description: "{% trans "Serial number less than or equal to" %}",
+ },
};
}