mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 20:16:44 +00:00
Merge pull request #1350 from SchrodingersGat/recently-updated
Adds "Recently Updated Stock" to index page
This commit is contained in:
commit
94c8bb6805
@ -125,6 +125,13 @@ class InvenTreeSetting(models.Model):
|
|||||||
'validator': bool
|
'validator': bool
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'PART_RECENT_COUNT': {
|
||||||
|
'name': _('Recent Part Count'),
|
||||||
|
'description': _('Number of recent parts to display on index page'),
|
||||||
|
'default': 10,
|
||||||
|
'validator': [int, MinValueValidator(1)]
|
||||||
|
},
|
||||||
|
|
||||||
'PART_TEMPLATE': {
|
'PART_TEMPLATE': {
|
||||||
'name': _('Template'),
|
'name': _('Template'),
|
||||||
'description': _('Parts are templates by default'),
|
'description': _('Parts are templates by default'),
|
||||||
@ -249,6 +256,13 @@ class InvenTreeSetting(models.Model):
|
|||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'STOCK_RECENT_COUNT': {
|
||||||
|
'name': _('Recent Stock Count'),
|
||||||
|
'description': _('Number of recent stock items to display on index page'),
|
||||||
|
'default': 10,
|
||||||
|
'validator': [int, MinValueValidator(1)]
|
||||||
|
},
|
||||||
|
|
||||||
'BUILDORDER_REFERENCE_PREFIX': {
|
'BUILDORDER_REFERENCE_PREFIX': {
|
||||||
'name': _('Build Order Reference Prefix'),
|
'name': _('Build Order Reference Prefix'),
|
||||||
'description': _('Prefix value for build order reference'),
|
'description': _('Prefix value for build order reference'),
|
||||||
@ -521,12 +535,18 @@ class InvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
validator = InvenTreeSetting.get_setting_validator(self.key)
|
validator = InvenTreeSetting.get_setting_validator(self.key)
|
||||||
|
|
||||||
if validator is not None:
|
|
||||||
self.run_validator(validator)
|
|
||||||
|
|
||||||
if self.is_bool():
|
if self.is_bool():
|
||||||
self.value = InvenTree.helpers.str2bool(self.value)
|
self.value = InvenTree.helpers.str2bool(self.value)
|
||||||
|
|
||||||
|
if self.is_int():
|
||||||
|
try:
|
||||||
|
self.value = int(self.value)
|
||||||
|
except (ValueError):
|
||||||
|
raise ValidationError(_('Must be an integer value'))
|
||||||
|
|
||||||
|
if validator is not None:
|
||||||
|
self.run_validator(validator)
|
||||||
|
|
||||||
def run_validator(self, validator):
|
def run_validator(self, validator):
|
||||||
"""
|
"""
|
||||||
Run a validator against the 'value' field for this InvenTreeSetting object.
|
Run a validator against the 'value' field for this InvenTreeSetting object.
|
||||||
@ -535,39 +555,39 @@ class InvenTreeSetting(models.Model):
|
|||||||
if validator is None:
|
if validator is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# If a list of validators is supplied, iterate through each one
|
value = self.value
|
||||||
if type(validator) in [list, tuple]:
|
|
||||||
for v in validator:
|
|
||||||
self.run_validator(v)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
if callable(validator):
|
|
||||||
# We can accept function validators with a single argument
|
|
||||||
print("Running validator function")
|
|
||||||
validator(self.value)
|
|
||||||
|
|
||||||
# Boolean validator
|
# Boolean validator
|
||||||
if validator == bool:
|
if self.is_bool():
|
||||||
# Value must "look like" a boolean value
|
# Value must "look like" a boolean value
|
||||||
if InvenTree.helpers.is_bool(self.value):
|
if InvenTree.helpers.is_bool(value):
|
||||||
# Coerce into either "True" or "False"
|
# Coerce into either "True" or "False"
|
||||||
self.value = str(InvenTree.helpers.str2bool(self.value))
|
value = InvenTree.helpers.str2bool(value)
|
||||||
else:
|
else:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'value': _('Value must be a boolean value')
|
'value': _('Value must be a boolean value')
|
||||||
})
|
})
|
||||||
|
|
||||||
# Integer validator
|
# Integer validator
|
||||||
if validator == int:
|
if self.is_int():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Coerce into an integer value
|
# Coerce into an integer value
|
||||||
self.value = str(int(self.value))
|
value = int(value)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'value': _('Value must be an integer value'),
|
'value': _('Value must be an integer value'),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# If a list of validators is supplied, iterate through each one
|
||||||
|
if type(validator) in [list, tuple]:
|
||||||
|
for v in validator:
|
||||||
|
self.run_validator(v)
|
||||||
|
|
||||||
|
if callable(validator):
|
||||||
|
# We can accept function validators with a single argument
|
||||||
|
validator(self.value)
|
||||||
|
|
||||||
def validate_unique(self, exclude=None):
|
def validate_unique(self, exclude=None):
|
||||||
""" Ensure that the key:value pair is unique.
|
""" Ensure that the key:value pair is unique.
|
||||||
In addition to the base validators, this ensures that the 'key'
|
In addition to the base validators, this ensures that the 'key'
|
||||||
@ -597,7 +617,13 @@ class InvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
validator = InvenTreeSetting.get_setting_validator(self.key)
|
validator = InvenTreeSetting.get_setting_validator(self.key)
|
||||||
|
|
||||||
return validator == bool
|
if validator == bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if type(validator) in [list, tuple]:
|
||||||
|
for v in validator:
|
||||||
|
if v == bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def as_bool(self):
|
def as_bool(self):
|
||||||
"""
|
"""
|
||||||
@ -623,6 +649,8 @@ class InvenTreeSetting(models.Model):
|
|||||||
if v == int:
|
if v == int:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def as_int(self):
|
def as_int(self):
|
||||||
"""
|
"""
|
||||||
Return the value of this setting converted to a boolean value.
|
Return the value of this setting converted to a boolean value.
|
||||||
|
@ -626,7 +626,7 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
queryset = queryset.filter(pk__in=parts_need_stock)
|
queryset = queryset.filter(pk__in=parts_need_stock)
|
||||||
|
|
||||||
# Limit choices
|
# Limit number of results
|
||||||
limit = params.get('limit', None)
|
limit = params.get('limit', None)
|
||||||
|
|
||||||
if limit is not None:
|
if limit is not None:
|
||||||
|
@ -808,6 +808,19 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
print("After error:", str(updated_after))
|
print("After error:", str(updated_after))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Limit number of results
|
||||||
|
limit = params.get('limit', None)
|
||||||
|
|
||||||
|
if limit is not None:
|
||||||
|
try:
|
||||||
|
limit = int(limit)
|
||||||
|
|
||||||
|
if limit > 0:
|
||||||
|
queryset = queryset[:limit]
|
||||||
|
|
||||||
|
except (ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
# Also ensure that we pre-fecth all the related items
|
# Also ensure that we pre-fecth all the related items
|
||||||
queryset = queryset.prefetch_related(
|
queryset = queryset.prefetch_related(
|
||||||
'part',
|
'part',
|
||||||
@ -815,8 +828,6 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
'location'
|
'location'
|
||||||
)
|
)
|
||||||
|
|
||||||
queryset = queryset.order_by('part__name')
|
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
@ -828,6 +839,15 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
filter_fields = [
|
filter_fields = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ordering_fields = [
|
||||||
|
'part__name',
|
||||||
|
'updated',
|
||||||
|
'stocktake_date',
|
||||||
|
'expiry_date',
|
||||||
|
]
|
||||||
|
|
||||||
|
ordering = ['part__name']
|
||||||
|
|
||||||
search_fields = [
|
search_fields = [
|
||||||
'serial',
|
'serial',
|
||||||
'batch',
|
'batch',
|
||||||
|
@ -102,7 +102,7 @@ addHeaderAction('bom-validation', '{% trans "BOM Waiting Validation" %}', 'fa-ti
|
|||||||
loadSimplePartTable("#table-latest-parts", "{% url 'api-part-list' %}", {
|
loadSimplePartTable("#table-latest-parts", "{% url 'api-part-list' %}", {
|
||||||
params: {
|
params: {
|
||||||
ordering: "-creation_date",
|
ordering: "-creation_date",
|
||||||
limit: 10,
|
limit: {% settings_value "PART_RECENT_COUNT" %},
|
||||||
},
|
},
|
||||||
name: 'latest_parts',
|
name: 'latest_parts',
|
||||||
});
|
});
|
||||||
@ -125,8 +125,19 @@ loadSimplePartTable("#table-bom-validation", "{% url 'api-part-list' %}", {
|
|||||||
|
|
||||||
{% if roles.stock.view %}
|
{% if roles.stock.view %}
|
||||||
addHeaderTitle('{% trans "Stock" %}');
|
addHeaderTitle('{% trans "Stock" %}');
|
||||||
|
addHeaderAction('recently-updated-stock', '{% trans "Recently Updated" %}', 'fa-clock');
|
||||||
addHeaderAction('low-stock', '{% trans "Low Stock" %}', 'fa-shopping-cart');
|
addHeaderAction('low-stock', '{% trans "Low Stock" %}', 'fa-shopping-cart');
|
||||||
addHeaderAction('stock-to-build', '{% trans "Required for Build Orders" %}', 'fa-bullhorn');
|
addHeaderAction('stock-to-build', '{% trans "Required for Build Orders" %}', 'fa-bullhorn');
|
||||||
|
|
||||||
|
loadStockTable($('#table-recently-updated-stock'), {
|
||||||
|
params: {
|
||||||
|
ordering: "-updated",
|
||||||
|
limit: {% settings_value "STOCK_RECENT_COUNT" %},
|
||||||
|
},
|
||||||
|
name: 'recently-updated-stock',
|
||||||
|
grouping: false,
|
||||||
|
});
|
||||||
|
|
||||||
{% settings_value "STOCK_ENABLE_EXPIRY" as expiry %}
|
{% settings_value "STOCK_ENABLE_EXPIRY" as expiry %}
|
||||||
{% if expiry %}
|
{% if expiry %}
|
||||||
addHeaderAction('expired-stock', '{% trans "Expired Stock" %}', 'fa-calendar-times');
|
addHeaderAction('expired-stock', '{% trans "Expired Stock" %}', 'fa-calendar-times');
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
{% include "InvenTree/settings/setting.html" with key="PART_IPN_REGEX" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_IPN_REGEX" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="PART_ALLOW_DUPLICATE_IPN" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_ALLOW_DUPLICATE_IPN" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="PART_SHOW_QUANTITY_IN_FORMS" icon="fa-hashtag" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_SHOW_QUANTITY_IN_FORMS" icon="fa-hashtag" %}
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="PART_RECENT_COUNT" icon="fa-clock" %}
|
||||||
<tr><td colspan='5 '></td></tr>
|
<tr><td colspan='5 '></td></tr>
|
||||||
{% include "InvenTree/settings/setting.html" with key="PART_TEMPLATE" icon="fa-clone" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_TEMPLATE" icon="fa-clone" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="PART_ASSEMBLY" icon="fa-tools" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_ASSEMBLY" icon="fa-tools" %}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
{% include "InvenTree/settings/header.html" %}
|
{% include "InvenTree/settings/header.html" %}
|
||||||
<tbody>
|
<tbody>
|
||||||
{% include "InvenTree/settings/setting.html" with key="STOCK_GROUP_BY_PART" icon="fa-layer-group" %}
|
{% include "InvenTree/settings/setting.html" with key="STOCK_GROUP_BY_PART" icon="fa-layer-group" %}
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="STOCK_RECENT_COUNT" icon="fa-clock" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="STOCK_ENABLE_EXPIRY" icon="fa-stopwatch" %}
|
{% 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_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_SALE" icon="fa-truck" %}
|
||||||
|
@ -319,6 +319,12 @@ function loadStockTable(table, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var grouping = true;
|
||||||
|
|
||||||
|
if ('grouping' in options) {
|
||||||
|
grouping = options.grouping;
|
||||||
|
}
|
||||||
|
|
||||||
table.inventreeTable({
|
table.inventreeTable({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
formatNoMatches: function() {
|
formatNoMatches: function() {
|
||||||
@ -333,7 +339,7 @@ function loadStockTable(table, options) {
|
|||||||
{% settings_value 'STOCK_GROUP_BY_PART' as group_by_part %}
|
{% settings_value 'STOCK_GROUP_BY_PART' as group_by_part %}
|
||||||
{% if group_by_part %}
|
{% if group_by_part %}
|
||||||
groupByField: options.groupByField || 'part',
|
groupByField: options.groupByField || 'part',
|
||||||
groupBy: true,
|
groupBy: grouping,
|
||||||
groupByFormatter: function(field, id, data) {
|
groupByFormatter: function(field, id, data) {
|
||||||
|
|
||||||
var row = data[0];
|
var row = data[0];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user