mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #836 from SchrodingersGat/serial-number-fixes
Serial number fixes
This commit is contained in:
commit
3678c940eb
@ -272,8 +272,9 @@ function setupFilterList(tableKey, table, target) {
|
|||||||
for (var key in filters) {
|
for (var key in filters) {
|
||||||
var value = getFilterOptionValue(tableKey, key, filters[key]);
|
var value = getFilterOptionValue(tableKey, key, filters[key]);
|
||||||
var title = getFilterTitle(tableKey, key);
|
var title = getFilterTitle(tableKey, key);
|
||||||
|
var description = getFilterDescription(tableKey, key);
|
||||||
|
|
||||||
element.append(`<div class='filter-tag'>${title} = ${value}<span ${tag}='${key}' class='close'>x</span></div>`);
|
element.append(`<div title='${description}' class='filter-tag'>${title} = ${value}<span ${tag}='${key}' class='close'>x</span></div>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a callback for adding a new filter
|
// 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.
|
* Return a description for the given table and filter selection.
|
||||||
*/
|
*/
|
||||||
|
@ -33,6 +33,13 @@
|
|||||||
<td>{{ part.revision }}</td>
|
<td>{{ part.revision }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if part.trackable %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-hashtag'></span></td>
|
||||||
|
<td><b>{% trans "Next Serial Number" %}</b></td>
|
||||||
|
<td>{{ part.getNextSerialNumber }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-info-circle'></span></td>
|
<td><span class='fas fa-info-circle'></span></td>
|
||||||
<td><b>{% trans "Description" %}</b></td>
|
<td><b>{% trans "Description" %}</b></td>
|
||||||
|
@ -6,11 +6,14 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
{% if part.virtual %}
|
||||||
|
<div class='alert alert-info alert-block'>
|
||||||
|
{% trans "This part is a virtual part" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if part.is_template %}
|
{% if part.is_template %}
|
||||||
<div class='alert alert-info alert-block'>
|
<div class='alert alert-info alert-block'>
|
||||||
{% trans "This part is a template part." %}
|
{% trans "This part is a template part." %}
|
||||||
<br>
|
|
||||||
{% trans "It is not a real part, but real parts can be based on this template." %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if part.variant_of %}
|
{% if part.variant_of %}
|
||||||
|
@ -13,9 +13,11 @@
|
|||||||
<a href="{% url 'part-variants' part.id %}">{% trans "Variants" %} <span class='badge'>{{ part.variants.count }}</span></span></a>
|
<a href="{% url 'part-variants' part.id %}">{% trans "Variants" %} <span class='badge'>{{ part.variants.count }}</span></span></a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if not part.virtual %}
|
||||||
<li{% ifequal tab 'stock' %} class="active"{% endifequal %}>
|
<li{% ifequal tab 'stock' %} class="active"{% endifequal %}>
|
||||||
<a href="{% url 'part-stock' part.id %}">{% trans "Stock" %} <span class="badge">{% decimal part.total_stock %}</span></a>
|
<a href="{% url 'part-stock' part.id %}">{% trans "Stock" %} <span class="badge">{% decimal part.total_stock %}</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% if part.component or part.used_in_count > 0 %}
|
{% if part.component or part.used_in_count > 0 %}
|
||||||
<li{% ifequal tab 'allocation' %} class="active"{% endifequal %}>
|
<li{% ifequal tab 'allocation' %} class="active"{% endifequal %}>
|
||||||
<a href="{% url 'part-allocation' part.id %}">{% trans "Allocated" %} <span class="badge">{% decimal part.allocation_count %}</span></a>
|
<a href="{% url 'part-allocation' part.id %}">{% trans "Allocated" %} <span class="badge">{% decimal part.allocation_count %}</span></a>
|
||||||
|
@ -487,7 +487,20 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
if serial_number is not None:
|
if serial_number is not None:
|
||||||
queryset = queryset.filter(serial=serial_number)
|
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)
|
in_stock = params.get('in_stock', None)
|
||||||
|
|
||||||
if in_stock is not None:
|
if in_stock is not None:
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
<input class='numberinput'
|
<input class='numberinput'
|
||||||
min='0'
|
min='0'
|
||||||
{% if stock_action == 'take' or stock_action == 'move' %} max='{{ item.quantity }}' {% endif %}
|
{% if stock_action == 'take' or stock_action == 'move' %} max='{{ item.quantity }}' {% endif %}
|
||||||
value='{{ item.new_quantity }}' type='number' name='stock-id-{{ item.id }}' id='stock-id-{{ item.id }}'/>
|
value='{% decimal item.new_quantity %}' type='number' name='stock-id-{{ item.id }}' id='stock-id-{{ item.id }}'/>
|
||||||
{% if item.error %}
|
{% if item.error %}
|
||||||
<br><span class='help-inline'>{{ item.error }}</span>
|
<br><span class='help-inline'>{{ item.error }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1036,6 +1036,36 @@ class StockItemCreate(AjaxCreateView):
|
|||||||
ajax_template_name = 'modal_form.html'
|
ajax_template_name = 'modal_form.html'
|
||||||
ajax_form_title = _('Create new Stock Item')
|
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=<id>)
|
||||||
|
- 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):
|
def get_form(self):
|
||||||
""" Get form for StockItem creation.
|
""" Get form for StockItem creation.
|
||||||
Overrides the default get_form() method to intelligently limit
|
Overrides the default get_form() method to intelligently limit
|
||||||
@ -1044,53 +1074,44 @@ class StockItemCreate(AjaxCreateView):
|
|||||||
|
|
||||||
form = super().get_form()
|
form = super().get_form()
|
||||||
|
|
||||||
part = None
|
part = self.get_part(form=form)
|
||||||
|
|
||||||
# If the user has selected a Part, limit choices for SupplierPart
|
if part is not None:
|
||||||
if form['part'].value():
|
sn = part.getNextSerialNumber()
|
||||||
part_id = form['part'].value()
|
form.field_placeholder['serial_numbers'] = _('Next available serial number is') + ' ' + str(sn)
|
||||||
|
|
||||||
try:
|
form.rebuild_layout()
|
||||||
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()
|
# 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)
|
# trackable parts get special consideration
|
||||||
form.fields['part'].widget = HiddenInput()
|
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 the part is NOT purchaseable, hide the supplier_part field
|
||||||
if part.trackable:
|
if not part.purchaseable:
|
||||||
form.fields['delete_on_deplete'].widget = HiddenInput()
|
form.fields['supplier_part'].widget = HiddenInput()
|
||||||
form.fields['delete_on_deplete'].initial = False
|
else:
|
||||||
else:
|
# Pre-select the allowable SupplierPart options
|
||||||
form.fields.pop('serial_numbers')
|
parts = form.fields['supplier_part'].queryset
|
||||||
|
parts = parts.filter(part=part.id)
|
||||||
|
|
||||||
# If the part is NOT purchaseable, hide the supplier_part field
|
form.fields['supplier_part'].queryset = parts
|
||||||
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
|
# 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
|
if len(all_parts) == 1:
|
||||||
all_parts = parts.all()
|
|
||||||
|
|
||||||
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
|
||||||
# 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
|
|
||||||
|
|
||||||
# Otherwise if the user has selected a SupplierPart, we know what Part they meant!
|
# 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
|
pass
|
||||||
|
|
||||||
return form
|
return form
|
||||||
@ -1113,27 +1134,20 @@ class StockItemCreate(AjaxCreateView):
|
|||||||
else:
|
else:
|
||||||
initials = super(StockItemCreate, self).get_initial().copy()
|
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)
|
loc_id = self.request.GET.get('location', None)
|
||||||
sup_part_id = self.request.GET.get('supplier_part', None)
|
sup_part_id = self.request.GET.get('supplier_part', None)
|
||||||
|
|
||||||
part = None
|
|
||||||
location = None
|
location = None
|
||||||
supplier_part = None
|
supplier_part = None
|
||||||
|
|
||||||
# Part field has been specified
|
if part is not None:
|
||||||
if part_id:
|
# Check that the supplied part is 'valid'
|
||||||
try:
|
if not part.is_template and part.active and not part.virtual:
|
||||||
part = Part.objects.get(pk=part_id)
|
initials['part'] = part
|
||||||
|
initials['location'] = part.get_default_location()
|
||||||
# Check that the supplied part is 'valid'
|
initials['supplier_part'] = part.default_supplier
|
||||||
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
|
|
||||||
|
|
||||||
# SupplierPart field has been specified
|
# SupplierPart field has been specified
|
||||||
# It must match the Part, if that has been supplied
|
# It must match the Part, if that has been supplied
|
||||||
|
@ -337,12 +337,21 @@ function loadStockTable(table, options) {
|
|||||||
} else {
|
} else {
|
||||||
return '-';
|
return '-';
|
||||||
}
|
}
|
||||||
} else if (field == 'location__path') {
|
} else if (field == 'location_detail.pathstring') {
|
||||||
/* Determine how many locations */
|
/* Determine how many locations */
|
||||||
var locations = [];
|
var locations = [];
|
||||||
|
|
||||||
data.forEach(function(item) {
|
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)) {
|
if (!locations.includes(loc)) {
|
||||||
locations.push(loc);
|
locations.push(loc);
|
||||||
@ -353,7 +362,11 @@ function loadStockTable(table, options) {
|
|||||||
return "In " + locations.length + " locations";
|
return "In " + locations.length + " locations";
|
||||||
} else {
|
} else {
|
||||||
// A single location!
|
// 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 "<i>{% trans "Undefined location" %}</i>";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (field == 'notes') {
|
} else if (field == 'notes') {
|
||||||
var notes = [];
|
var notes = [];
|
||||||
|
@ -34,6 +34,14 @@ function getAvailableTableFilters(tableKey) {
|
|||||||
title: '{% trans "Is allocated" %}',
|
title: '{% trans "Is allocated" %}',
|
||||||
description: '{% trans "Item has been alloacted" %}',
|
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" %}",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user