From 24bd5d0962e347747200e0e95cb103c2907f0587 Mon Sep 17 00:00:00 2001 From: wolflu05 <76838159+wolflu05@users.noreply.github.com> Date: Sun, 14 Jul 2024 13:22:04 +0200 Subject: [PATCH] initial implementation of barcode generation using plugins --- src/backend/InvenTree/InvenTree/helpers.py | 32 ------------ .../InvenTree/InvenTree/helpers_model.py | 7 +-- src/backend/InvenTree/InvenTree/models.py | 26 +++++++--- src/backend/InvenTree/build/models.py | 5 ++ src/backend/InvenTree/common/models.py | 19 ++++++- src/backend/InvenTree/company/models.py | 10 ++++ src/backend/InvenTree/order/models.py | 15 ++++++ src/backend/InvenTree/part/models.py | 9 +++- .../InvenTree/plugin/base/barcodes/api.py | 50 +++++++++++++++++-- .../InvenTree/plugin/base/barcodes/helper.py | 48 ++++++++++++++++++ .../InvenTree/plugin/base/barcodes/mixins.py | 25 ++++++++++ .../plugin/base/barcodes/serializers.py | 30 +++++++++-- .../builtin/barcodes/inventree_barcode.py | 49 ++++++++++++++---- src/backend/InvenTree/stock/models.py | 14 +++++- .../templates/InvenTree/settings/barcode.html | 1 + .../pages/Index/Settings/SystemSettings.tsx | 3 +- 16 files changed, 276 insertions(+), 67 deletions(-) create mode 100644 src/backend/InvenTree/plugin/base/barcodes/helper.py diff --git a/src/backend/InvenTree/InvenTree/helpers.py b/src/backend/InvenTree/InvenTree/helpers.py index ae72f59578..4558594436 100644 --- a/src/backend/InvenTree/InvenTree/helpers.py +++ b/src/backend/InvenTree/InvenTree/helpers.py @@ -396,38 +396,6 @@ def WrapWithQuotes(text, quote='"'): return text -def MakeBarcode(cls_name, object_pk: int, object_data=None, **kwargs): - """Generate a string for a barcode. Adds some global InvenTree parameters. - - Args: - cls_name: string describing the object type e.g. 'StockItem' - object_pk (int): ID (Primary Key) of the object in the database - object_data: Python dict object containing extra data which will be rendered to string (must only contain stringable values) - - Returns: - json string of the supplied data plus some other data - """ - if object_data is None: - object_data = {} - - brief = kwargs.get('brief', True) - - data = {} - - if brief: - data[cls_name] = object_pk - else: - data['tool'] = 'InvenTree' - data['version'] = InvenTree.version.inventreeVersion() - data['instance'] = InvenTree.version.inventreeInstanceName() - - # Ensure PK is included - object_data['id'] = object_pk - data[cls_name] = object_data - - return str(json.dumps(data, sort_keys=True)) - - def GetExportFormats(): """Return a list of allowable file formats for importing or exporting tabular data.""" return ['csv', 'xlsx', 'tsv', 'json'] diff --git a/src/backend/InvenTree/InvenTree/helpers_model.py b/src/backend/InvenTree/InvenTree/helpers_model.py index e9bbdc0b06..bf814dc12a 100644 --- a/src/backend/InvenTree/InvenTree/helpers_model.py +++ b/src/backend/InvenTree/InvenTree/helpers_model.py @@ -15,9 +15,6 @@ from djmoney.contrib.exchange.models import convert_money from djmoney.money import Money from PIL import Image -import InvenTree -import InvenTree.helpers_model -import InvenTree.version from common.notifications import ( InvenTreeNotificationBodies, NotificationBody, @@ -331,9 +328,7 @@ def notify_users( 'instance': instance, 'name': content.name.format(**content_context), 'message': content.message.format(**content_context), - 'link': InvenTree.helpers_model.construct_absolute_url( - instance.get_absolute_url() - ), + 'link': construct_absolute_url(instance.get_absolute_url()), 'template': {'subject': content.name.format(**content_context)}, } diff --git a/src/backend/InvenTree/InvenTree/models.py b/src/backend/InvenTree/InvenTree/models.py index 33cd1e9097..81a4fd002a 100644 --- a/src/backend/InvenTree/InvenTree/models.py +++ b/src/backend/InvenTree/InvenTree/models.py @@ -3,9 +3,7 @@ import logging from datetime import datetime -from django.conf import settings from django.contrib.auth import get_user_model -from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import models @@ -934,6 +932,8 @@ class InvenTreeBarcodeMixin(models.Model): - barcode_data : Raw data associated with an assigned barcode - barcode_hash : A 'hash' of the assigned barcode data used to improve matching + + The barcode_model_type_code() classmethod must be implemented in the model class. """ class Meta: @@ -964,11 +964,25 @@ class InvenTreeBarcodeMixin(models.Model): # By default, use the name of the class return cls.__name__.lower() + @classmethod + def barcode_model_type_code(cls): + r"""Return a 'short' code for the model type. + + This is used to generate a efficient QR code for the model type. + It is expected to match this pattern: [0-9A-Z $%*+-.\/:]{2} + + Note: Due to the shape constrains (45**2=2025 different allowed codes) + this needs to be explicitly implemented in the model class to avoid collisions. + """ + raise NotImplementedError( + 'barcode_model_type_code() must be implemented in the model class' + ) + def format_barcode(self, **kwargs): """Return a JSON string for formatting a QR code for this model instance.""" - return InvenTree.helpers.MakeBarcode( - self.__class__.barcode_model_type(), self.pk, **kwargs - ) + from plugin.base.barcodes.helper import generate_barcode + + return generate_barcode(self) def format_matched_response(self): """Format a standard response for a matched barcode.""" @@ -986,7 +1000,7 @@ class InvenTreeBarcodeMixin(models.Model): @property def barcode(self): """Format a minimal barcode string (e.g. for label printing).""" - return self.format_barcode(brief=True) + return self.format_barcode() @classmethod def lookup_barcode(cls, barcode_hash): diff --git a/src/backend/InvenTree/build/models.py b/src/backend/InvenTree/build/models.py index 326e484137..bcf62710b9 100644 --- a/src/backend/InvenTree/build/models.py +++ b/src/backend/InvenTree/build/models.py @@ -115,6 +115,11 @@ class Build( return defaults + @classmethod + def barcode_model_type_code(cls): + """Return the associated barcode model type code for this model.""" + return "BO" + def save(self, *args, **kwargs): """Custom save method for the BuildOrder model""" self.validate_reference_field(self.reference) diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py index a55e829cc8..8945517322 100644 --- a/src/backend/InvenTree/common/models.py +++ b/src/backend/InvenTree/common/models.py @@ -9,12 +9,13 @@ import hmac import json import logging import os +import sys import uuid from datetime import timedelta, timezone from enum import Enum from io import BytesIO from secrets import compare_digest -from typing import Any, Callable, Collection, TypedDict, Union +from typing import Any, Callable, TypedDict, Union from django.apps import apps from django.conf import settings as django_settings @@ -49,6 +50,7 @@ import InvenTree.ready import InvenTree.tasks import InvenTree.validators import order.validators +import plugin.base.barcodes.helper import report.helpers import users.models from InvenTree.sanitizer import sanitize_svg @@ -56,6 +58,13 @@ from plugin import registry logger = logging.getLogger('inventree') +if sys.version_info >= (3, 11): + from typing import NotRequired +else: + + class NotRequired: + """NotRequired type helper is only supported with Python 3.11+.""" + class MetaMixin(models.Model): """A base class for InvenTree models to include shared meta fields. @@ -1167,7 +1176,7 @@ class InvenTreeSettingsKeyType(SettingsKeyType): requires_restart: If True, a server restart is required after changing the setting """ - requires_restart: bool + requires_restart: NotRequired[bool] class InvenTreeSetting(BaseInvenTreeSetting): @@ -1402,6 +1411,12 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'default': False, 'validator': bool, }, + 'BARCODE_GENERATION_PLUGIN': { + 'name': _('Barcode Generation Plugin'), + 'description': _('Plugin to use for internal barcode data generation'), + 'choices': plugin.base.barcodes.helper.barcode_plugins, + 'default': 'inventreebarcode', + }, 'PART_ENABLE_REVISION': { 'name': _('Part Revisions'), 'description': _('Enable revision field for Part'), diff --git a/src/backend/InvenTree/company/models.py b/src/backend/InvenTree/company/models.py index 58b0938568..ed56fbab17 100644 --- a/src/backend/InvenTree/company/models.py +++ b/src/backend/InvenTree/company/models.py @@ -474,6 +474,11 @@ class ManufacturerPart( """Return the API URL associated with the ManufacturerPart instance.""" return reverse('api-manufacturer-part-list') + @classmethod + def barcode_model_type_code(cls): + """Return the associated barcode model type code for this model.""" + return 'MP' + part = models.ForeignKey( 'part.Part', on_delete=models.CASCADE, @@ -676,6 +681,11 @@ class SupplierPart( """Return custom API filters for this particular instance.""" return {'manufacturer_part': {'part': self.part.pk}} + @classmethod + def barcode_model_type_code(cls): + """Return the associated barcode model type code for this model.""" + return 'SP' + def clean(self): """Custom clean action for the SupplierPart model. diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index 1a29a9941b..c2243184db 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -408,6 +408,11 @@ class PurchaseOrder(TotalPriceMixin, Order): return defaults + @classmethod + def barcode_model_type_code(cls): + """Return the associated barcode model type code for this model.""" + return 'PO' + @staticmethod def filterByDate(queryset, min_date, max_date): """Filter by 'minimum and maximum date range'. @@ -871,6 +876,11 @@ class SalesOrder(TotalPriceMixin, Order): return defaults + @classmethod + def barcode_model_type_code(cls): + """Return the associated barcode model type code for this model.""" + return 'SO' + @staticmethod def filterByDate(queryset, min_date, max_date): """Filter by "minimum and maximum date range". @@ -2035,6 +2045,11 @@ class ReturnOrder(TotalPriceMixin, Order): return defaults + @classmethod + def barcode_model_type_code(cls): + """Return the associated barcode model type code for this model.""" + return 'RO' + def __str__(self): """Render a string representation of this ReturnOrder.""" return f"{self.reference} - {self.customer.name if self.customer else _('no customer')}" diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py index 730cf5a270..f759241aa7 100644 --- a/src/backend/InvenTree/part/models.py +++ b/src/backend/InvenTree/part/models.py @@ -416,6 +416,11 @@ class Part( """Return API query filters for limiting field results against this instance.""" return {'variant_of': {'exclude_tree': self.pk}} + @classmethod + def barcode_model_type_code(cls): + """Return the associated barcode model type code for this model.""" + return 'PA' + def report_context(self): """Return custom report context information.""" return { @@ -426,11 +431,11 @@ class Part( 'name': self.name, 'parameters': self.parameters_map(), 'part': self, - 'qr_data': self.format_barcode(brief=True), + 'qr_data': self.barcode, 'qr_url': self.get_absolute_url(), 'revision': self.revision, 'test_template_list': self.getTestTemplates(), - 'test_templates': self.getTestTemplates(), + 'test_templates': self.getTestTemplateMap(), } def get_context_data(self, request, **kwargs): diff --git a/src/backend/InvenTree/plugin/base/barcodes/api.py b/src/backend/InvenTree/plugin/base/barcodes/api.py index 8cd75cf8b6..095f6a8d4a 100644 --- a/src/backend/InvenTree/plugin/base/barcodes/api.py +++ b/src/backend/InvenTree/plugin/base/barcodes/api.py @@ -6,16 +6,17 @@ from django.db.models import F from django.urls import path from django.utils.translation import gettext_lazy as _ -from rest_framework import permissions +from drf_spectacular.utils import extend_schema, extend_schema_view +from rest_framework import permissions, status from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.generics import CreateAPIView from rest_framework.response import Response import order.models +import plugin.base.barcodes.helper import stock.models from InvenTree.helpers import hash_barcode from plugin import registry -from plugin.builtin.barcodes.inventree_barcode import InvenTreeInternalBarcodePlugin from users.models import RuleSet from . import serializers as barcode_serializers @@ -129,6 +130,45 @@ class BarcodeScan(BarcodeView): return Response(result) +@extend_schema_view( + post=extend_schema(responses={200: barcode_serializers.BarcodeSerializer}) +) +class BarcodeGenerate(CreateAPIView): + """Endpoint for generating a barcode for a database object. + + The barcode is generated by the selected barcode plugin. + """ + + serializer_class = barcode_serializers.BarcodeGenerateSerializer + + def queryset(self): + """This API view does not have a queryset.""" + return None + + # Default permission classes (can be overridden) + permission_classes = [permissions.IsAuthenticated] + + def create(self, request, *args, **kwargs): + """Perform the barcode generation action.""" + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + model = serializer.validated_data.get('model') + pk = serializer.validated_data.get('pk') + model_cls = plugin.base.barcodes.helper.get_supported_barcode_models_map().get( + model, None + ) + + if model_cls is None: + raise ValidationError({'error': _('Model is not supported')}) + + model_instance = model_cls.objects.get(pk=pk) + + barcode_data = plugin.base.barcodes.helper.generate_barcode(model_instance) + + return Response({'barcode': barcode_data}, status=status.HTTP_200_OK) + + class BarcodeAssign(BarcodeView): """Endpoint for assigning a barcode to a stock item. @@ -161,7 +201,7 @@ class BarcodeAssign(BarcodeView): valid_labels = [] - for model in InvenTreeInternalBarcodePlugin.get_supported_barcode_models(): + for model in plugin.base.barcodes.helper.get_supported_barcode_models(): label = model.barcode_model_type() valid_labels.append(label) @@ -203,7 +243,7 @@ class BarcodeUnassign(BarcodeView): serializer.is_valid(raise_exception=True) data = serializer.validated_data - supported_models = InvenTreeInternalBarcodePlugin.get_supported_barcode_models() + supported_models = plugin.base.barcodes.helper.get_supported_barcode_models() supported_labels = [model.barcode_model_type() for model in supported_models] model_names = ', '.join(supported_labels) @@ -567,6 +607,8 @@ class BarcodeSOAllocate(BarcodeView): barcode_api_urls = [ + # Generate a barcode for a database object + path('generate/', BarcodeGenerate.as_view(), name='api-barcode-generate'), # Link a third-party barcode to an item (e.g. Part / StockItem / etc) path('link/', BarcodeAssign.as_view(), name='api-barcode-link'), # Unlink a third-party barcode from an item diff --git a/src/backend/InvenTree/plugin/base/barcodes/helper.py b/src/backend/InvenTree/plugin/base/barcodes/helper.py new file mode 100644 index 0000000000..0daba86b2d --- /dev/null +++ b/src/backend/InvenTree/plugin/base/barcodes/helper.py @@ -0,0 +1,48 @@ +"""Helper functions for barcode generation.""" + +import logging +from typing import Type + +import InvenTree.helpers_model +from InvenTree.models import InvenTreeBarcodeMixin + +logger = logging.getLogger('inventree') + + +def barcode_plugins() -> list: + """Return a list of plugin choices which can be used for barcode generation.""" + try: + from plugin import registry + + plugins = registry.with_mixin('barcode', active=True) + except Exception: + plugins = [] + + return [ + (plug.slug, plug.human_name) for plug in plugins if plug.has_barcode_generation + ] + + +def generate_barcode(model_instance: InvenTreeBarcodeMixin): + """Generate a barcode for a given model instance.""" + from common.settings import get_global_setting + from plugin import registry + + # Find the selected barcode generation plugin + slug = get_global_setting('BARCODE_GENERATION_PLUGIN', create=False) + + plugin = registry.get_plugin(slug) + + return plugin.generate(model_instance) + + +def get_supported_barcode_models() -> list[Type[InvenTreeBarcodeMixin]]: + """Returns a list of database models which support barcode functionality.""" + return InvenTree.helpers_model.getModelsWithMixin(InvenTreeBarcodeMixin) + + +def get_supported_barcode_models_map(): + """Return a mapping of barcode model types to the model class.""" + return { + model.barcode_model_type(): model for model in get_supported_barcode_models() + } diff --git a/src/backend/InvenTree/plugin/base/barcodes/mixins.py b/src/backend/InvenTree/plugin/base/barcodes/mixins.py index 929a037115..28fb1c49a8 100644 --- a/src/backend/InvenTree/plugin/base/barcodes/mixins.py +++ b/src/backend/InvenTree/plugin/base/barcodes/mixins.py @@ -10,6 +10,7 @@ from django.db.models import F, Q from django.utils.translation import gettext_lazy as _ from company.models import Company, SupplierPart +from InvenTree.models import InvenTreeBarcodeMixin from order.models import PurchaseOrder, PurchaseOrderStatus from plugin.base.integration.SettingsMixin import SettingsMixin from stock.models import StockLocation @@ -53,6 +54,30 @@ class BarcodeMixin: """ return None + @property + def has_barcode_generation(self): + """Does this plugin support barcode generation.""" + try: + # Attempt to call the generate method + self.generate(None) # type: ignore + except NotImplementedError: + # If a NotImplementedError is raised, then barcode generation is not supported + return False + except: + pass + + return True + + def generate(self, model_instance: InvenTreeBarcodeMixin): + """Generate barcode data for the given model instance. + + Arguments: + model_instance: The model instance to generate barcode data for. It is extending the InvenTreeBarcodeMixin. + + Returns: The generated barcode data. + """ + raise NotImplementedError('Generate must be implemented by a plugin') + class SupplierBarcodeMixin(BarcodeMixin): """Mixin that provides default implementations for scan functions for supplier barcodes. diff --git a/src/backend/InvenTree/plugin/base/barcodes/serializers.py b/src/backend/InvenTree/plugin/base/barcodes/serializers.py index 6ad15713b7..b31ab1818a 100644 --- a/src/backend/InvenTree/plugin/base/barcodes/serializers.py +++ b/src/backend/InvenTree/plugin/base/barcodes/serializers.py @@ -6,9 +6,9 @@ from django.utils.translation import gettext_lazy as _ from rest_framework import serializers import order.models +import plugin.base.barcodes.helper import stock.models from order.status_codes import PurchaseOrderStatus, SalesOrderStatus -from plugin.builtin.barcodes.inventree_barcode import InvenTreeInternalBarcodePlugin class BarcodeSerializer(serializers.Serializer): @@ -23,6 +23,30 @@ class BarcodeSerializer(serializers.Serializer): ) +class BarcodeGenerateSerializer(serializers.Serializer): + """Serializer for generating a barcode.""" + + model = serializers.CharField( + required=True, help_text=_('Model name to generate barcode for') + ) + + pk = serializers.IntegerField( + required=True, + help_text=_('Primary key of model object to generate barcode for'), + ) + + def validate_model(self, model: str): + """Validate the provided model.""" + supported_models = ( + plugin.base.barcodes.helper.get_supported_barcode_models_map() + ) + + if model not in supported_models.keys(): + raise ValidationError(_('Model is not supported')) + + return model + + class BarcodeAssignMixin(serializers.Serializer): """Serializer for linking and unlinking barcode to an internal class.""" @@ -30,7 +54,7 @@ class BarcodeAssignMixin(serializers.Serializer): """Generate serializer fields for each supported model type.""" super().__init__(*args, **kwargs) - for model in InvenTreeInternalBarcodePlugin.get_supported_barcode_models(): + for model in plugin.base.barcodes.helper.get_supported_barcode_models(): self.fields[model.barcode_model_type()] = ( serializers.PrimaryKeyRelatedField( queryset=model.objects.all(), @@ -45,7 +69,7 @@ class BarcodeAssignMixin(serializers.Serializer): """Return a list of model fields.""" fields = [ model.barcode_model_type() - for model in InvenTreeInternalBarcodePlugin.get_supported_barcode_models() + for model in plugin.base.barcodes.helper.get_supported_barcode_models() ] return fields diff --git a/src/backend/InvenTree/plugin/builtin/barcodes/inventree_barcode.py b/src/backend/InvenTree/plugin/builtin/barcodes/inventree_barcode.py index 78f694424d..d3d463d61d 100644 --- a/src/backend/InvenTree/plugin/builtin/barcodes/inventree_barcode.py +++ b/src/backend/InvenTree/plugin/builtin/barcodes/inventree_barcode.py @@ -9,28 +9,44 @@ references model objects actually exist in the database. import json +from django.core.validators import MaxLengthValidator, MinLengthValidator from django.utils.translation import gettext_lazy as _ +import plugin.base.barcodes.helper from InvenTree.helpers import hash_barcode -from InvenTree.helpers_model import getModelsWithMixin from InvenTree.models import InvenTreeBarcodeMixin from plugin import InvenTreePlugin -from plugin.mixins import BarcodeMixin +from plugin.mixins import BarcodeMixin, SettingsMixin -class InvenTreeInternalBarcodePlugin(BarcodeMixin, InvenTreePlugin): +class InvenTreeInternalBarcodePlugin(SettingsMixin, BarcodeMixin, InvenTreePlugin): """Builtin BarcodePlugin for matching and generating internal barcodes.""" NAME = 'InvenTreeBarcode' TITLE = _('InvenTree Barcodes') DESCRIPTION = _('Provides native support for barcodes') - VERSION = '2.0.0' + VERSION = '2.1.0' AUTHOR = _('InvenTree contributors') - @staticmethod - def get_supported_barcode_models(): - """Returns a list of database models which support barcode functionality.""" - return getModelsWithMixin(InvenTreeBarcodeMixin) + SETTINGS = { + 'INTERNAL_BARCODE_FORMAT': { + 'name': _('Internal Barcode Format'), + 'description': _('Select an internal barcode format'), + 'choices': [ + ('json', _('JSON barcodes (require more space)')), + ('short', _('Short barcodes (made for optimized space)')), + ], + 'default': 'json', + }, + 'SHORT_BARCODE_PREFIX': { + 'name': _('Short Barcode Prefix'), + 'description': _( + 'Customize the prefix used for short barcodes, may be useful for environments with multiple InvenTree instances' + ), + 'validator': [str, MinLengthValidator(4), MaxLengthValidator(4)], + 'default': 'INV-', + }, + } def format_matched_response(self, label, model, instance): """Format a response for the scanned data.""" @@ -53,7 +69,7 @@ class InvenTreeInternalBarcodePlugin(BarcodeMixin, InvenTreePlugin): except json.JSONDecodeError: pass - supported_models = self.get_supported_barcode_models() + supported_models = plugin.base.barcodes.helper.get_supported_barcode_models() if barcode_dict is not None and type(barcode_dict) is dict: # Look for various matches. First good match will be returned @@ -79,3 +95,18 @@ class InvenTreeInternalBarcodePlugin(BarcodeMixin, InvenTreePlugin): if instance is not None: return self.format_matched_response(label, model, instance) + + def generate(self, model_instance: InvenTreeBarcodeMixin): + """Generate a barcode for a given model instance.""" + barcode_format = self.get_setting('INTERNAL_BARCODE_FORMAT') + + if barcode_format == 'json': + return json.dumps({model_instance.barcode_model_type(): model_instance.pk}) + + if barcode_format == 'short': + prefix = self.get_setting('SHORT_BARCODE_PREFIX') + model_type_code = model_instance.barcode_model_type_code() + + return f'{prefix}{model_type_code}{model_instance.pk}' + + return None diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 3a65f2c2f6..e0fa3d3c6f 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -142,11 +142,16 @@ class StockLocation( """Return API url.""" return reverse('api-location-list') + @classmethod + def barcode_model_type_code(cls): + """Return the associated barcode model type code for this model.""" + return 'SL' + def report_context(self): """Return report context data for this StockLocation.""" return { 'location': self, - 'qr_data': self.format_barcode(brief=True), + 'qr_data': self.barcode, 'parent': self.parent, 'stock_location': self, 'stock_items': self.get_stock_items(), @@ -367,6 +372,11 @@ class StockItem( """Custom API instance filters.""" return {'parent': {'exclude_tree': self.pk}} + @classmethod + def barcode_model_type_code(cls): + """Return the associated barcode model type code for this model.""" + return 'SI' + def get_test_keys(self, include_installed=True): """Construct a flattened list of test 'keys' for this StockItem.""" keys = [] @@ -397,7 +407,7 @@ class StockItem( 'item': self, 'name': self.part.full_name, 'part': self.part, - 'qr_data': self.format_barcode(brief=True), + 'qr_data': self.barcode, 'qr_url': self.get_absolute_url(), 'parameters': self.part.parameters_map(), 'quantity': InvenTree.helpers.normalize(self.quantity), diff --git a/src/backend/InvenTree/templates/InvenTree/settings/barcode.html b/src/backend/InvenTree/templates/InvenTree/settings/barcode.html index 982548a58c..8da99c0a9a 100644 --- a/src/backend/InvenTree/templates/InvenTree/settings/barcode.html +++ b/src/backend/InvenTree/templates/InvenTree/settings/barcode.html @@ -16,6 +16,7 @@ {% include "InvenTree/settings/setting.html" with key="BARCODE_INPUT_DELAY" icon="fa-hourglass-half" %} {% include "InvenTree/settings/setting.html" with key="BARCODE_WEBCAM_SUPPORT" icon="fa-video" %} {% include "InvenTree/settings/setting.html" with key="BARCODE_SHOW_TEXT" icon="fa-closed-captioning" %} + {% include "InvenTree/settings/setting.html" with key="BARCODE_GENERATION_PLUGIN" icon="fa-qrcode" %} diff --git a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx index 59bec1ab13..66ea306e68 100644 --- a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx +++ b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx @@ -98,7 +98,8 @@ export default function SystemSettings() { 'BARCODE_ENABLE', 'BARCODE_INPUT_DELAY', 'BARCODE_WEBCAM_SUPPORT', - 'BARCODE_SHOW_TEXT' + 'BARCODE_SHOW_TEXT', + 'BARCODE_GENERATION_PLUGIN' ]} /> )