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 @@ {{ part.revision }} {% endif %} + {% if part.trackable %} + + + {% trans "Next Serial Number" %} + {{ part.getNextSerialNumber }} + + {% endif %} {% trans "Description" %} diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index 0bb3687fc8..d9f80edf44 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -6,11 +6,14 @@ {% block content %} +{% if part.virtual %} +
+ {% trans "This part is a virtual part" %} +
+{% endif %} {% if part.is_template %}
{% trans "This part is a template part." %} -
- {% trans "It is not a real part, but real parts can be based on this template." %}
{% endif %} {% if part.variant_of %} diff --git a/InvenTree/part/templates/part/tabs.html b/InvenTree/part/templates/part/tabs.html index 28aa2cbb4d..ecec2796d6 100644 --- a/InvenTree/part/templates/part/tabs.html +++ b/InvenTree/part/templates/part/tabs.html @@ -13,9 +13,11 @@ {% trans "Variants" %} {{ part.variants.count }} {% endif %} + {% if not part.virtual %} {% trans "Stock" %} {% decimal part.total_stock %} + {% 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" %}", + }, }; }