2
0
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:
Oliver 2021-02-23 14:47:24 +11:00 committed by GitHub
commit 94c8bb6805
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 92 additions and 25 deletions

View File

@ -125,6 +125,13 @@ class InvenTreeSetting(models.Model):
'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': {
'name': _('Template'),
'description': _('Parts are templates by default'),
@ -249,6 +256,13 @@ class InvenTreeSetting(models.Model):
'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': {
'name': _('Build Order Reference Prefix'),
'description': _('Prefix value for build order reference'),
@ -521,12 +535,18 @@ class InvenTreeSetting(models.Model):
validator = InvenTreeSetting.get_setting_validator(self.key)
if validator is not None:
self.run_validator(validator)
if self.is_bool():
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):
"""
Run a validator against the 'value' field for this InvenTreeSetting object.
@ -535,39 +555,39 @@ class InvenTreeSetting(models.Model):
if validator is None:
return
# 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)
return
if callable(validator):
# We can accept function validators with a single argument
print("Running validator function")
validator(self.value)
value = self.value
# Boolean validator
if validator == bool:
if self.is_bool():
# 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"
self.value = str(InvenTree.helpers.str2bool(self.value))
value = InvenTree.helpers.str2bool(value)
else:
raise ValidationError({
'value': _('Value must be a boolean value')
})
# Integer validator
if validator == int:
if self.is_int():
try:
# Coerce into an integer value
self.value = str(int(self.value))
value = int(value)
except (ValueError, TypeError):
raise ValidationError({
'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):
""" Ensure that the key:value pair is unique.
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)
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):
"""
@ -623,6 +649,8 @@ class InvenTreeSetting(models.Model):
if v == int:
return True
return False
def as_int(self):
"""
Return the value of this setting converted to a boolean value.

View File

@ -626,7 +626,7 @@ class PartList(generics.ListCreateAPIView):
queryset = queryset.filter(pk__in=parts_need_stock)
# Limit choices
# Limit number of results
limit = params.get('limit', None)
if limit is not None:

View File

@ -808,6 +808,19 @@ class StockList(generics.ListCreateAPIView):
print("After error:", str(updated_after))
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
queryset = queryset.prefetch_related(
'part',
@ -815,8 +828,6 @@ class StockList(generics.ListCreateAPIView):
'location'
)
queryset = queryset.order_by('part__name')
return queryset
filter_backends = [
@ -828,6 +839,15 @@ class StockList(generics.ListCreateAPIView):
filter_fields = [
]
ordering_fields = [
'part__name',
'updated',
'stocktake_date',
'expiry_date',
]
ordering = ['part__name']
search_fields = [
'serial',
'batch',

View File

@ -102,7 +102,7 @@ addHeaderAction('bom-validation', '{% trans "BOM Waiting Validation" %}', 'fa-ti
loadSimplePartTable("#table-latest-parts", "{% url 'api-part-list' %}", {
params: {
ordering: "-creation_date",
limit: 10,
limit: {% settings_value "PART_RECENT_COUNT" %},
},
name: 'latest_parts',
});
@ -125,8 +125,19 @@ loadSimplePartTable("#table-bom-validation", "{% url 'api-part-list' %}", {
{% if roles.stock.view %}
addHeaderTitle('{% trans "Stock" %}');
addHeaderAction('recently-updated-stock', '{% trans "Recently Updated" %}', 'fa-clock');
addHeaderAction('low-stock', '{% trans "Low Stock" %}', 'fa-shopping-cart');
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 %}
{% if expiry %}
addHeaderAction('expired-stock', '{% trans "Expired Stock" %}', 'fa-calendar-times');

View File

@ -19,6 +19,7 @@
{% 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_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>
{% include "InvenTree/settings/setting.html" with key="PART_TEMPLATE" icon="fa-clone" %}
{% include "InvenTree/settings/setting.html" with key="PART_ASSEMBLY" icon="fa-tools" %}

View File

@ -16,6 +16,7 @@
{% include "InvenTree/settings/header.html" %}
<tbody>
{% 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_STALE_DAYS" icon="fa-calendar" %}
{% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_EXPIRED_SALE" icon="fa-truck" %}

View File

@ -319,6 +319,12 @@ function loadStockTable(table, options) {
}
}
var grouping = true;
if ('grouping' in options) {
grouping = options.grouping;
}
table.inventreeTable({
method: 'get',
formatNoMatches: function() {
@ -333,7 +339,7 @@ function loadStockTable(table, options) {
{% settings_value 'STOCK_GROUP_BY_PART' as group_by_part %}
{% if group_by_part %}
groupByField: options.groupByField || 'part',
groupBy: true,
groupBy: grouping,
groupByFormatter: function(field, id, data) {
var row = data[0];