From 63b5b956105df731889b51ba1c6d3e00afb865c6 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Mar 2022 13:43:19 +1100 Subject: [PATCH 01/71] Ensure that kwargs are correctly passed through the settings chain Problem: validate_unique fails - Otherwise, the object is not correctly passed - Then, validate_unique fails --- InvenTree/common/models.py | 46 ++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 288e3451ad..7a938735fc 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -79,7 +79,7 @@ class BaseInvenTreeSetting(models.Model): self.key = str(self.key).upper() self.clean(**kwargs) - self.validate_unique() + self.validate_unique(**kwargs) super().save() @@ -253,13 +253,6 @@ class BaseInvenTreeSetting(models.Model): if user is not None: settings = settings.filter(user=user) - try: - setting = settings.filter(**cls.get_filters(key, **kwargs)).first() - except (ValueError, cls.DoesNotExist): - setting = None - except (IntegrityError, OperationalError): - setting = None - plugin = kwargs.pop('plugin', None) if plugin: @@ -270,6 +263,15 @@ class BaseInvenTreeSetting(models.Model): kwargs['plugin'] = plugin + settings = settings.filter(plugin=plugin) + + try: + setting = settings.filter(**cls.get_filters(key, **kwargs)).first() + except (ValueError, cls.DoesNotExist): + setting = None + except (IntegrityError, OperationalError): + setting = None + # Setting does not exist! (Try to create it) if not setting: @@ -287,7 +289,7 @@ class BaseInvenTreeSetting(models.Model): try: # Wrap this statement in "atomic", so it can be rolled back if it fails with transaction.atomic(): - setting.save() + setting.save(**kwargs) except (IntegrityError, OperationalError): # It might be the case that the database isn't created yet pass @@ -438,17 +440,37 @@ class BaseInvenTreeSetting(models.Model): validator(self.value) def validate_unique(self, exclude=None, **kwargs): - """ 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' is unique, using a case-insensitive comparison. + + Note that sub-classes (UserSetting, PluginSetting) use other filters + to determine if the setting is 'unique' or not """ super().validate_unique(exclude) + filters = { + 'key': self.key, + } + + user = getattr(self, 'user', None) + plugin = getattr(self, 'plugin', None) + + if user is not None: + filters['user'] = user + + if plugin is not None: + filters['plugin'] = plugin + try: - setting = self.__class__.objects.exclude(id=self.id).filter(**self.get_filters(self.key, **kwargs)) + # Check if a duplicate setting already exists + setting = self.__class__.objects.filter(**filters).exclude(id=self.id) + if setting.exists(): raise ValidationError({'key': _('Key string must be unique')}) + except self.DoesNotExist: pass @@ -1312,7 +1334,7 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): def get_setting_object(cls, key, user): return super().get_setting_object(key, user=user) - def validate_unique(self, exclude=None): + def validate_unique(self, exclude=None, **kwargs): return super().validate_unique(exclude=exclude, user=self.user) @classmethod From 5ee9af7f0ebed1cb56ea488299824daf0db0afb8 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Mar 2022 14:10:05 +1100 Subject: [PATCH 02/71] Remove old "get_filters" function - It was confusing! - Manually filter where required --- InvenTree/common/models.py | 49 +++++++++++++++++++++++--------------- InvenTree/plugin/models.py | 17 ------------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 7a938735fc..c72e9f5adb 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -230,10 +230,6 @@ class BaseInvenTreeSetting(models.Model): return choices - @classmethod - def get_filters(cls, key, **kwargs): - return {'key__iexact': key} - @classmethod def get_setting_object(cls, key, **kwargs): """ @@ -247,26 +243,30 @@ class BaseInvenTreeSetting(models.Model): settings = cls.objects.all() + filters = { + 'key__iexact': key, + } + # Filter by user user = kwargs.get('user', None) if user is not None: - settings = settings.filter(user=user) + filters['user'] = user - plugin = kwargs.pop('plugin', None) + # Filter by plugin + plugin = kwargs.get('plugin', None) - if plugin: + if plugin is not None: from plugin import InvenTreePluginBase if issubclass(plugin.__class__, InvenTreePluginBase): plugin = plugin.plugin_config() + filters['plugin'] = plugin kwargs['plugin'] = plugin - settings = settings.filter(plugin=plugin) - try: - setting = settings.filter(**cls.get_filters(key, **kwargs)).first() + setting = settings.filter(**filters).first() except (ValueError, cls.DoesNotExist): setting = None except (IntegrityError, OperationalError): @@ -344,8 +344,26 @@ class BaseInvenTreeSetting(models.Model): if change_user is not None and not change_user.is_staff: return + filters = { + 'key__iexact': key, + } + + user = kwargs.get('user', None) + plugin = kwargs.get('plugin', None) + + if user is not None: + filters['user'] = user + + if plugin is not None: + from plugin import InvenTreePluginBase + + if issubclass(plugin.__class__, InvenTreePluginBase): + filters['plugin'] = plugin.plugin_config() + else: + filters['plugin'] = plugin + try: - setting = cls.objects.get(**cls.get_filters(key, **kwargs)) + setting = cls.objects.get(**filters) except cls.DoesNotExist: if create: @@ -452,7 +470,7 @@ class BaseInvenTreeSetting(models.Model): super().validate_unique(exclude) filters = { - 'key': self.key, + 'key__iexact': self.key, } user = getattr(self, 'user', None) @@ -1337,13 +1355,6 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): def validate_unique(self, exclude=None, **kwargs): return super().validate_unique(exclude=exclude, user=self.user) - @classmethod - def get_filters(cls, key, **kwargs): - return { - 'key__iexact': key, - 'user__id': kwargs['user'].id - } - def to_native_value(self): """ Return the "pythonic" value, diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index e33d452e0a..44eeafd012 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -175,23 +175,6 @@ class PluginSetting(common.models.BaseInvenTreeSetting): return super().get_setting_definition(key, **kwargs) - @classmethod - def get_filters(cls, key, **kwargs): - """ - Override filters method to ensure settings are filtered by plugin id - """ - - filters = super().get_filters(key, **kwargs) - - plugin = kwargs.get('plugin', None) - - if plugin: - if issubclass(plugin.__class__, InvenTreePluginBase): - plugin = plugin.plugin_config() - filters['plugin'] = plugin - - return filters - plugin = models.ForeignKey( PluginConfig, related_name='settings', From bc48a9584407a238886cddc8e23013cf84191fa0 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Mar 2022 14:13:02 +1100 Subject: [PATCH 03/71] PEP fixes --- InvenTree/common/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index c72e9f5adb..ad144cd8db 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -353,7 +353,7 @@ class BaseInvenTreeSetting(models.Model): if user is not None: filters['user'] = user - + if plugin is not None: from plugin import InvenTreePluginBase @@ -462,7 +462,7 @@ class BaseInvenTreeSetting(models.Model): Ensure that the key:value pair is unique. In addition to the base validators, this ensures that the 'key' is unique, using a case-insensitive comparison. - + Note that sub-classes (UserSetting, PluginSetting) use other filters to determine if the setting is 'unique' or not """ From 011d97db3da22d1ba222749683c419613214df2a Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Mar 2022 14:20:25 +1100 Subject: [PATCH 04/71] Tweak display of label printing plugins --- InvenTree/templates/js/translated/label.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js index 5215ce9d28..c0f4c2f735 100644 --- a/InvenTree/templates/js/translated/label.js +++ b/InvenTree/templates/js/translated/label.js @@ -263,7 +263,7 @@ function selectLabel(labels, items, options={}) { `; plugins.forEach(function(plugin) { - plugin_selection += ``; + plugin_selection += ``; }); plugin_selection += ` From c1b408f8a326bdc8ca649039cb8f202c052671f8 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Mar 2022 15:57:32 +1100 Subject: [PATCH 05/71] Send notification if printing fails If label printing fails (due to a plugin error) then the user is notified. --- InvenTree/label/api.py | 7 ++++++- InvenTree/part/tasks.py | 1 - InvenTree/plugin/events.py | 26 ++++++++++++++++++++++++-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/InvenTree/label/api.py b/InvenTree/label/api.py index 1a679d5ac5..5103d99676 100644 --- a/InvenTree/label/api.py +++ b/InvenTree/label/api.py @@ -138,6 +138,9 @@ class LabelPrintMixin: """ + # Label instance + label_instance = self.get_object() + for output in outputs: """ For each output, we generate a temporary image file, @@ -156,7 +159,9 @@ class LabelPrintMixin: offload_task( 'plugin.events.print_label', plugin.plugin_slug(), - image + image, + label_instance=label_instance, + user=request.user, ) return JsonResponse({ diff --git a/InvenTree/part/tasks.py b/InvenTree/part/tasks.py index 9bc34f83df..b5e02e1128 100644 --- a/InvenTree/part/tasks.py +++ b/InvenTree/part/tasks.py @@ -5,7 +5,6 @@ import logging from django.utils.translation import ugettext_lazy as _ - import InvenTree.helpers import InvenTree.tasks import common.notifications diff --git a/InvenTree/plugin/events.py b/InvenTree/plugin/events.py index 829aeaa935..7a2e0d2da3 100644 --- a/InvenTree/plugin/events.py +++ b/InvenTree/plugin/events.py @@ -7,12 +7,15 @@ from __future__ import unicode_literals import logging +from django.utils.translation import ugettext_lazy as _ + from django.conf import settings from django.db import transaction from django.db.models.signals import post_save, post_delete from django.dispatch.dispatcher import receiver from common.models import InvenTreeSetting +import common.notifications from InvenTree.ready import canAppAccessDatabase from InvenTree.tasks import offload_task @@ -192,12 +195,14 @@ def after_delete(sender, instance, **kwargs): ) -def print_label(plugin_slug, label_image, **kwargs): +def print_label(plugin_slug, label_image, label_instance=None, user=None): """ Print label with the provided plugin. This task is nominally handled by the background worker. + If the printing fails (throws an exception) then the user is notified. + Arguments: plugin_slug: The unique slug (key) of the plugin label_image: A PIL.Image image object to be printed @@ -211,4 +216,21 @@ def print_label(plugin_slug, label_image, **kwargs): logger.error(f"Could not find matching plugin for '{plugin_slug}'") return - plugin.print_label(label_image) + try: + plugin.print_label(label_image) + except Exception as e: + # Plugin threw an error - notify the user who attempted to print + + ctx = { + 'name': _('Label printing failed'), + 'message': str(e), + } + + logger.error(f"Label printing failed: Sending notification to user '{user}'") + + common.notifications.trigger_notifaction( + label_instance, + 'label.printing_failed', + targets=[user], + context=ctx, + ) From 9046c746282b9186be530e50776e0146116d1fc9 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Mar 2022 16:05:35 +1100 Subject: [PATCH 06/71] Only send printing failed as a UI notification --- InvenTree/common/notifications.py | 12 ++++++++++-- InvenTree/plugin/events.py | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index 9151f9b879..fe737fc919 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -154,11 +154,18 @@ class UIMessageNotification(SingleNotificationMethod): return True -def trigger_notifaction(obj, category=None, obj_ref='pk', targets=None, target_fnc=None, target_args=[], target_kwargs={}, context={}): +def trigger_notifaction(obj, category=None, obj_ref='pk', **kwargs): """ Send out a notification """ + targets = kwargs.get('targets', None) + target_fnc = kwargs.get('target_fnc', None) + target_args = kwargs.get('target_args', []) + target_kwargs = kwargs.get('target_kwargs', {}) + context = kwargs.get('context', {}) + delivery_methods = kwargs.get('delivery_methods', None) + # Check if data is importing currently if isImportingData(): return @@ -190,7 +197,8 @@ def trigger_notifaction(obj, category=None, obj_ref='pk', targets=None, target_f logger.info(f"Sending notification '{category}' for '{str(obj)}'") # Collect possible methods - delivery_methods = inheritors(NotificationMethod) + if delivery_methods is None: + delivery_methods = inheritors(NotificationMethod) for method in [a for a in delivery_methods if a not in [SingleNotificationMethod, BulkNotificationMethod]]: logger.info(f"Triggering method '{method.METHOD_NAME}'") diff --git a/InvenTree/plugin/events.py b/InvenTree/plugin/events.py index 7a2e0d2da3..ac0eaf3914 100644 --- a/InvenTree/plugin/events.py +++ b/InvenTree/plugin/events.py @@ -233,4 +233,5 @@ def print_label(plugin_slug, label_image, label_instance=None, user=None): 'label.printing_failed', targets=[user], context=ctx, + delivery_methods=[common.notifications.UIMessageNotification] ) From 052d9770ceb1a31f98f45876b1701584b693932c Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Mar 2022 16:39:43 +1100 Subject: [PATCH 07/71] Override view permissions for plugin list API - It is necessary for *any* logged in user to view this endpoint - This is how the user determines which plugins are available (e.g. for label printing!) --- InvenTree/plugin/api.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/InvenTree/plugin/api.py b/InvenTree/plugin/api.py index 9ab3b96724..15eefff286 100644 --- a/InvenTree/plugin/api.py +++ b/InvenTree/plugin/api.py @@ -9,6 +9,7 @@ from django.conf.urls import url, include from rest_framework import generics from rest_framework import status +from rest_framework import permissions from rest_framework.response import Response from common.api import GlobalSettingsPermissions @@ -22,6 +23,11 @@ class PluginList(generics.ListAPIView): - GET: Return a list of all PluginConfig objects """ + # Allow any logged in user to read this endpoint + # This is necessary to allow certain functionality, + # e.g. determining which label printing plugins are available + permission_classes = [permissions.IsAuthenticated] + serializer_class = PluginSerializers.PluginConfigSerializer queryset = PluginConfig.objects.all() From 15a221509b21b92866313f8eb18b83c16050d14d Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Mar 2022 16:40:30 +1100 Subject: [PATCH 08/71] Bump API version --- InvenTree/InvenTree/version.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index bb36b28ff8..f97f134e4a 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -12,11 +12,14 @@ import common.models INVENTREE_SW_VERSION = "0.7.0 dev" # InvenTree API version -INVENTREE_API_VERSION = 33 +INVENTREE_API_VERSION = 34 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v34 -> 2022-03-25 + - Change permissions for "plugin list" API endpoint (now allows any authenticated user) + v33 -> 2022-03-24 - Adds "plugins_enabled" information to root API endpoint From 6e30ec560334504c8afb5ca55198013bc152465a Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Mar 2022 16:42:20 +1100 Subject: [PATCH 09/71] PEP fix --- InvenTree/plugin/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/plugin/api.py b/InvenTree/plugin/api.py index 15eefff286..5a4ea7bae3 100644 --- a/InvenTree/plugin/api.py +++ b/InvenTree/plugin/api.py @@ -25,7 +25,7 @@ class PluginList(generics.ListAPIView): # Allow any logged in user to read this endpoint # This is necessary to allow certain functionality, - # e.g. determining which label printing plugins are available + # e.g. determining which label printing plugins are available permission_classes = [permissions.IsAuthenticated] serializer_class = PluginSerializers.PluginConfigSerializer From 03eb5441c72be8af1eb6e290af88201506ff7018 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 25 Mar 2022 21:43:10 +1100 Subject: [PATCH 10/71] Pin setuptools version - Recent update seems to have borked it --- docker/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/requirements.txt b/docker/requirements.txt index cd27bdb5ce..4370522dbb 100644 --- a/docker/requirements.txt +++ b/docker/requirements.txt @@ -1,7 +1,7 @@ # Base python requirements for docker containers # Basic package requirements -setuptools>=57.4.0 +setuptools>=57.4.0<=60.1.0 wheel>=0.37.0 invoke>=1.4.0 # Invoke build tool From fcfdec0ad63870794bca990d93142418f3baf6c1 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 25 Mar 2022 21:47:38 +1100 Subject: [PATCH 11/71] Fix requirements.txt formatting --- docker/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/requirements.txt b/docker/requirements.txt index 4370522dbb..192d6dac6f 100644 --- a/docker/requirements.txt +++ b/docker/requirements.txt @@ -1,7 +1,7 @@ # Base python requirements for docker containers # Basic package requirements -setuptools>=57.4.0<=60.1.0 +setuptools>=57.4.0,<=60.1.0 wheel>=0.37.0 invoke>=1.4.0 # Invoke build tool From 3e484317d2d781dcf15a300fa34ce8843c9d10a3 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 25 Mar 2022 23:49:15 +1100 Subject: [PATCH 12/71] Register notification against the plugin class, not the particular label --- InvenTree/plugin/events.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/InvenTree/plugin/events.py b/InvenTree/plugin/events.py index ac0eaf3914..cc0e7539b9 100644 --- a/InvenTree/plugin/events.py +++ b/InvenTree/plugin/events.py @@ -228,8 +228,9 @@ def print_label(plugin_slug, label_image, label_instance=None, user=None): logger.error(f"Label printing failed: Sending notification to user '{user}'") + # Throw an error against the plugin instance common.notifications.trigger_notifaction( - label_instance, + plugin.plugin_config(), 'label.printing_failed', targets=[user], context=ctx, From e18493a3974b8fd23766963bbd3b0cea22783569 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 26 Mar 2022 13:43:48 +1100 Subject: [PATCH 13/71] Supply label width and height to the label printing plugin --- InvenTree/plugin/builtin/integration/mixins.py | 4 ++++ InvenTree/plugin/events.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 451ddaf40f..118f0b775b 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -419,6 +419,10 @@ class LabelPrintingMixin: Arguments: label: A black-and-white pillow Image object + kwargs: + length: The length of the label (in mm) + width: The width of the label (in mm) + """ # Unimplemented (to be implemented by the particular plugin class) diff --git a/InvenTree/plugin/events.py b/InvenTree/plugin/events.py index cc0e7539b9..b510b42683 100644 --- a/InvenTree/plugin/events.py +++ b/InvenTree/plugin/events.py @@ -217,7 +217,7 @@ def print_label(plugin_slug, label_image, label_instance=None, user=None): return try: - plugin.print_label(label_image) + plugin.print_label(label_image, width=label_instance.width, height=label_instance.height) except Exception as e: # Plugin threw an error - notify the user who attempted to print From 5b949b6f60bb296884063cb48b84516e0882a1a7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 27 Mar 2022 07:45:45 +1100 Subject: [PATCH 14/71] Fix sorting for location column in part table --- InvenTree/templates/js/translated/stock.js | 166 +-------------------- 1 file changed, 1 insertion(+), 165 deletions(-) diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index b7f4162621..ade8bc5a0a 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -1770,6 +1770,7 @@ function loadStockTable(table, options) { col = { field: 'location_detail.pathstring', title: '{% trans "Location" %}', + sortName: 'location', formatter: function(value, row) { return locationDetail(row); } @@ -1912,172 +1913,8 @@ function loadStockTable(table, options) { original: original, showColumns: true, columns: columns, - {% if False %} - groupByField: options.groupByField || 'part', - groupBy: grouping, - groupByFormatter: function(field, id, data) { - - var row = data[0]; - - if (field == 'part_detail.full_name') { - - var html = imageHoverIcon(row.part_detail.thumbnail); - - html += row.part_detail.full_name; - html += ` (${data.length} {% trans "items" %})`; - - html += makePartIcons(row.part_detail); - - return html; - } else if (field == 'part_detail.IPN') { - var ipn = row.part_detail.IPN; - - if (ipn) { - return ipn; - } else { - return '-'; - } - } else if (field == 'part_detail.description') { - return row.part_detail.description; - } else if (field == 'packaging') { - var packaging = []; - - data.forEach(function(item) { - var pkg = item.packaging; - - if (!pkg) { - pkg = '-'; - } - - if (!packaging.includes(pkg)) { - packaging.push(pkg); - } - }); - - if (packaging.length > 1) { - return "..."; - } else if (packaging.length == 1) { - return packaging[0]; - } else { - return "-"; - } - } else if (field == 'quantity') { - var stock = 0; - var items = 0; - - data.forEach(function(item) { - stock += parseFloat(item.quantity); - items += 1; - }); - - stock = +stock.toFixed(5); - - return `${stock} (${items} {% trans "items" %})`; - } else if (field == 'status') { - var statii = []; - - data.forEach(function(item) { - var status = String(item.status); - - if (!status || status == '') { - status = '-'; - } - - if (!statii.includes(status)) { - statii.push(status); - } - }); - - // Multiple status codes - if (statii.length > 1) { - return "..."; - } else if (statii.length == 1) { - return stockStatusDisplay(statii[0]); - } else { - return "-"; - } - } else if (field == 'batch') { - var batches = []; - - data.forEach(function(item) { - var batch = item.batch; - - if (!batch || batch == '') { - batch = '-'; - } - - if (!batches.includes(batch)) { - batches.push(batch); - } - }); - - if (batches.length > 1) { - return "" + batches.length + " {% trans 'batches' %}"; - } else if (batches.length == 1) { - if (batches[0]) { - return batches[0]; - } else { - return '-'; - } - } else { - return '-'; - } - } else if (field == 'location_detail.pathstring') { - /* Determine how many locations */ - var locations = []; - - data.forEach(function(item) { - - var detail = locationDetail(item); - - if (!locations.includes(detail)) { - locations.push(detail); - } - }); - - if (locations.length == 1) { - // Single location, easy! - return locations[0]; - } else if (locations.length > 1) { - return "In " + locations.length + " {% trans 'locations' %}"; - } else { - return "{% trans 'Undefined location' %}"; - } - } else if (field == 'notes') { - var notes = []; - - data.forEach(function(item) { - var note = item.notes; - - if (!note || note == '') { - note = '-'; - } - - if (!notes.includes(note)) { - notes.push(note); - } - }); - - if (notes.length > 1) { - return '...'; - } else if (notes.length == 1) { - return notes[0] || '-'; - } else { - return '-'; - } - } else { - return ''; - } - }, - {% endif %} }); - /* - if (options.buttons) { - linkButtonsToSelection(table, options.buttons); - } - */ - var buttons = [ '#stock-print-options', '#stock-options', @@ -2092,7 +1929,6 @@ function loadStockTable(table, options) { buttons, ); - function stockAdjustment(action) { var items = $(table).bootstrapTable('getSelections'); From ce323d80ea4aadd1bbf56712450eccaae3e5e812 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 27 Mar 2022 20:29:59 +1100 Subject: [PATCH 15/71] Fix floating point issues --- InvenTree/templates/js/translated/build.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index 46f7f32e42..fd88144f8b 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -1025,9 +1025,10 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { } // Store the required quantity in the row data - row.required = quantity; + // Prevent weird rounding issues + row.required = parseFloat(quantity.toFixed(15)); - return quantity; + return row.required; } function sumAllocations(row) { @@ -1043,9 +1044,9 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { quantity += item.quantity; }); - row.allocated = quantity; + row.allocated = parseFloat(quantity.toFixed(15)); - return quantity; + return row.allocated; } function setupCallbacks() { From ab7eda9d2c03cb96ac44d434c0d0bd640d40150c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 27 Mar 2022 21:04:14 +1100 Subject: [PATCH 16/71] Add new button to navbar --- InvenTree/templates/navbar.html | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/InvenTree/templates/navbar.html b/InvenTree/templates/navbar.html index 126376a7dc..898171a552 100644 --- a/InvenTree/templates/navbar.html +++ b/InvenTree/templates/navbar.html @@ -89,9 +89,16 @@ {% endif %} {% include "search_form.html" %}