From 6a5be992c094d5a25c2884a0897296a01e62988b Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 11 May 2025 12:44:06 -0700 Subject: [PATCH] Enhancement: add barcode frontend config (#9742) --- .../admin/config/config.component.spec.ts | 4 +- src-ui/src/app/data/paperless-config.ts | 89 ++++++++++++++++ src/documents/barcodes.py | 73 ++++++++----- src/documents/tests/test_api_app_config.py | 57 ++++++---- src/documents/tests/test_barcodes.py | 22 ++++ src/paperless/config.py | 57 +++++++++- ...nfiguration_barcode_asn_prefix_and_more.py | 100 ++++++++++++++++++ src/paperless/models.py | 81 ++++++++++++++ src/paperless/serialisers.py | 3 + 9 files changed, 437 insertions(+), 49 deletions(-) create mode 100644 src/paperless/migrations/0004_applicationconfiguration_barcode_asn_prefix_and_more.py diff --git a/src-ui/src/app/components/admin/config/config.component.spec.ts b/src-ui/src/app/components/admin/config/config.component.spec.ts index 191532590..079bd1420 100644 --- a/src-ui/src/app/components/admin/config/config.component.spec.ts +++ b/src-ui/src/app/components/admin/config/config.component.spec.ts @@ -105,9 +105,9 @@ describe('ConfigComponent', () => { it('should support JSON validation for e.g. user_args', () => { component.configForm.patchValue({ user_args: '{ foo bar }' }) - expect(component.errors).toEqual({ user_args: 'Invalid JSON' }) + expect(component.errors['user_args']).toEqual('Invalid JSON') component.configForm.patchValue({ user_args: '{ "foo": "bar" }' }) - expect(component.errors).toEqual({ user_args: null }) + expect(component.errors['user_args']).toBeNull() }) it('should upload file, show error if necessary', () => { diff --git a/src-ui/src/app/data/paperless-config.ts b/src-ui/src/app/data/paperless-config.ts index 3ae485ff2..3afca66ff 100644 --- a/src-ui/src/app/data/paperless-config.ts +++ b/src-ui/src/app/data/paperless-config.ts @@ -49,6 +49,7 @@ export enum ConfigOptionType { export const ConfigCategory = { General: $localize`General Settings`, OCR: $localize`OCR Settings`, + Barcode: $localize`Barcode Settings`, } export interface ConfigOption { @@ -180,6 +181,83 @@ export const PaperlessConfigOptions: ConfigOption[] = [ config_key: 'PAPERLESS_APP_TITLE', category: ConfigCategory.General, }, + { + key: 'barcodes_enabled', + title: $localize`Enable Barcodes`, + type: ConfigOptionType.Boolean, + config_key: 'PAPERLESS_CONSUMER_ENABLE_BARCODES', + category: ConfigCategory.Barcode, + }, + { + key: 'barcode_enable_tiff_support', + title: $localize`Enable TIFF Support`, + type: ConfigOptionType.Boolean, + config_key: 'PAPERLESS_CONSUMER_BARCODE_TIFF_SUPPORT', + category: ConfigCategory.Barcode, + }, + { + key: 'barcode_string', + title: $localize`Barcode String`, + type: ConfigOptionType.String, + config_key: 'PAPERLESS_CONSUMER_BARCODE_STRING', + category: ConfigCategory.Barcode, + }, + { + key: 'barcode_retain_split_pages', + title: $localize`Retain Split Pages`, + type: ConfigOptionType.Boolean, + config_key: 'PAPERLESS_CONSUMER_BARCODE_RETAIN_SPLIT_PAGES', + category: ConfigCategory.Barcode, + }, + { + key: 'barcode_enable_asn', + title: $localize`Enable ASN`, + type: ConfigOptionType.Boolean, + config_key: 'PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE', + category: ConfigCategory.Barcode, + }, + { + key: 'barcode_asn_prefix', + title: $localize`ASN Prefix`, + type: ConfigOptionType.String, + config_key: 'PAPERLESS_CONSUMER_ASN_BARCODE_PREFIX', + category: ConfigCategory.Barcode, + }, + { + key: 'barcode_upscale', + title: $localize`Upscale`, + type: ConfigOptionType.Number, + config_key: 'PAPERLESS_CONSUMER_BARCODE_UPSCALE', + category: ConfigCategory.Barcode, + }, + { + key: 'barcode_dpi', + title: $localize`DPI`, + type: ConfigOptionType.Number, + config_key: 'PAPERLESS_CONSUMER_BARCODE_DPI', + category: ConfigCategory.Barcode, + }, + { + key: 'barcode_max_pages', + title: $localize`Max Pages`, + type: ConfigOptionType.Number, + config_key: 'PAPERLESS_CONSUMER_BARCODE_MAX_PAGES', + category: ConfigCategory.Barcode, + }, + { + key: 'barcode_enable_tag', + title: $localize`Enable Tag Detection`, + type: ConfigOptionType.Boolean, + config_key: 'PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE', + category: ConfigCategory.Barcode, + }, + { + key: 'barcode_tag_mapping', + title: $localize`Tag Mapping`, + type: ConfigOptionType.JSON, + config_key: 'PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING', + category: ConfigCategory.Barcode, + }, ] export interface PaperlessConfig extends ObjectWithId { @@ -198,4 +276,15 @@ export interface PaperlessConfig extends ObjectWithId { user_args: object app_logo: string app_title: string + barcodes_enabled: boolean + barcode_enable_tiff_support: boolean + barcode_string: string + barcode_retain_split_pages: boolean + barcode_enable_asn: boolean + barcode_asn_prefix: string + barcode_upscale: number + barcode_dpi: number + barcode_max_pages: number + barcode_enable_tag: boolean + barcode_tag_mapping: object } diff --git a/src/documents/barcodes.py b/src/documents/barcodes.py index 3b0c1d33b..fdb671d2c 100644 --- a/src/documents/barcodes.py +++ b/src/documents/barcodes.py @@ -15,13 +15,16 @@ from pikepdf import Pdf from documents.converters import convert_from_tiff_to_pdf from documents.data_models import ConsumableDocument +from documents.data_models import DocumentMetadataOverrides from documents.models import Tag from documents.plugins.base import ConsumeTaskPlugin from documents.plugins.base import StopConsumeTaskError +from documents.plugins.helpers import ProgressManager from documents.plugins.helpers import ProgressStatusOptions from documents.utils import copy_basic_file_stats from documents.utils import copy_file_with_basic_stats from documents.utils import maybe_override_pixel_limit +from paperless.config import BarcodeConfig if TYPE_CHECKING: from collections.abc import Callable @@ -39,6 +42,7 @@ class Barcode: page: int value: str + settings: BarcodeConfig @property def is_separator(self) -> bool: @@ -46,7 +50,7 @@ class Barcode: Returns True if the barcode value equals the configured separation value, False otherwise """ - return self.value == settings.CONSUMER_BARCODE_STRING + return self.value == self.settings.barcode_string @property def is_asn(self) -> bool: @@ -54,7 +58,7 @@ class Barcode: Returns True if the barcode value matches the configured ASN prefix, False otherwise """ - return self.value.startswith(settings.CONSUMER_ASN_BARCODE_PREFIX) + return self.value.startswith(self.settings.barcode_asn_prefix) class BarcodePlugin(ConsumeTaskPlugin): @@ -67,17 +71,41 @@ class BarcodePlugin(ConsumeTaskPlugin): - ASN from barcode detection is enabled or - Barcode support is enabled and the mime type is supported """ - if settings.CONSUMER_BARCODE_TIFF_SUPPORT: + if self.settings.barcode_enable_tiff_support: supported_mimes: set[str] = {"application/pdf", "image/tiff"} else: supported_mimes = {"application/pdf"} return ( - settings.CONSUMER_ENABLE_ASN_BARCODE - or settings.CONSUMER_ENABLE_BARCODES - or settings.CONSUMER_ENABLE_TAG_BARCODE + self.settings.barcode_enable_asn + or self.settings.barcodes_enabled + or self.settings.barcode_enable_tag ) and self.input_doc.mime_type in supported_mimes + def get_settings(self) -> BarcodeConfig: + """ + Returns the settings for this plugin (Django settings or app config) + """ + return BarcodeConfig() + + def __init__( + self, + input_doc: ConsumableDocument, + metadata: DocumentMetadataOverrides, + status_mgr: ProgressManager, + base_tmp_dir: Path, + task_id: str, + ) -> None: + super().__init__( + input_doc, + metadata, + status_mgr, + base_tmp_dir, + task_id, + ) + # need these for able_to_run + self.settings = self.get_settings() + def setup(self) -> None: self.temp_dir = tempfile.TemporaryDirectory( dir=self.base_tmp_dir, @@ -99,7 +127,7 @@ class BarcodePlugin(ConsumeTaskPlugin): # try reading tags from barcodes if ( - settings.CONSUMER_ENABLE_TAG_BARCODE + self.settings.barcode_enable_tag and (tags := self.tags) is not None and len(tags) > 0 ): @@ -110,7 +138,7 @@ class BarcodePlugin(ConsumeTaskPlugin): logger.info(f"Found tags in barcode: {tags}") # Lastly attempt to split documents - if settings.CONSUMER_ENABLE_BARCODES and ( + if self.settings.barcodes_enabled and ( separator_pages := self.get_separation_pages() ): # We have pages to split against @@ -155,10 +183,7 @@ class BarcodePlugin(ConsumeTaskPlugin): # Update/overwrite an ASN if possible # After splitting, as otherwise each split document gets the same ASN - if ( - settings.CONSUMER_ENABLE_ASN_BARCODE - and (located_asn := self.asn) is not None - ): + if self.settings.barcode_enable_asn and (located_asn := self.asn) is not None: logger.info(f"Found ASN in barcode: {located_asn}") self.metadata.asn = located_asn @@ -245,8 +270,8 @@ class BarcodePlugin(ConsumeTaskPlugin): # Get limit from configuration barcode_max_pages: int = ( num_of_pages - if settings.CONSUMER_BARCODE_MAX_PAGES == 0 - else settings.CONSUMER_BARCODE_MAX_PAGES + if self.settings.barcode_max_pages == 0 + else self.settings.barcode_max_pages ) if barcode_max_pages < num_of_pages: # pragma: no cover @@ -261,7 +286,7 @@ class BarcodePlugin(ConsumeTaskPlugin): # Convert page to image page = convert_from_path( self.pdf_file, - dpi=settings.CONSUMER_BARCODE_DPI, + dpi=self.settings.barcode_dpi, output_folder=self.temp_dir.name, first_page=current_page_number + 1, last_page=current_page_number + 1, @@ -272,7 +297,7 @@ class BarcodePlugin(ConsumeTaskPlugin): logger.debug(f"Image is at {page_filepath}") # Upscale image if configured - factor = settings.CONSUMER_BARCODE_UPSCALE + factor = self.settings.barcode_upscale if factor > 1.0: logger.debug( f"Upscaling image by {factor} for better barcode detection", @@ -285,7 +310,7 @@ class BarcodePlugin(ConsumeTaskPlugin): # Detect barcodes for barcode_value in reader(page): self.barcodes.append( - Barcode(current_page_number, barcode_value), + Barcode(current_page_number, barcode_value, self.settings), ) # Delete temporary image file @@ -308,7 +333,7 @@ class BarcodePlugin(ConsumeTaskPlugin): def asn(self) -> int | None: """ Search the parsed barcodes for any ASNs. - The first barcode that starts with CONSUMER_ASN_BARCODE_PREFIX + The first barcode that starts with barcode_asn_prefix is considered the ASN to be used. Returns the detected ASN (or None) """ @@ -317,7 +342,7 @@ class BarcodePlugin(ConsumeTaskPlugin): # Ensure the barcodes have been read self.detect() - # get the first barcode that starts with CONSUMER_ASN_BARCODE_PREFIX + # get the first barcode that starts with barcode_asn_prefix asn_text: str | None = next( (x.value for x in self.barcodes if x.is_asn), None, @@ -326,7 +351,7 @@ class BarcodePlugin(ConsumeTaskPlugin): if asn_text: logger.debug(f"Found ASN Barcode: {asn_text}") # remove the prefix and remove whitespace - asn_text = asn_text[len(settings.CONSUMER_ASN_BARCODE_PREFIX) :].strip() + asn_text = asn_text[len(self.settings.barcode_asn_prefix) :].strip() # remove non-numeric parts of the remaining string asn_text = re.sub(r"\D", "", asn_text) @@ -356,9 +381,9 @@ class BarcodePlugin(ConsumeTaskPlugin): for raw in tag_texts.split(","): try: tag_str: str | None = None - for regex in settings.CONSUMER_TAG_BARCODE_MAPPING: + for regex in self.settings.barcode_tag_mapping: if re.match(regex, raw, flags=re.IGNORECASE): - sub = settings.CONSUMER_TAG_BARCODE_MAPPING[regex] + sub = self.settings.barcode_tag_mapping[regex] tag_str = ( re.sub(regex, sub, raw, flags=re.IGNORECASE) if sub @@ -394,13 +419,13 @@ class BarcodePlugin(ConsumeTaskPlugin): """ # filter all barcodes for the separator string # get the page numbers of the separating barcodes - retain = settings.CONSUMER_BARCODE_RETAIN_SPLIT_PAGES + retain = self.settings.barcode_retain_split_pages separator_pages = { bc.page: retain for bc in self.barcodes if bc.is_separator and (not retain or (retain and bc.page > 0)) } # as below, dont include the first page if retain is enabled - if not settings.CONSUMER_ENABLE_ASN_BARCODE: + if not self.settings.barcode_enable_asn: return separator_pages # add the page numbers of the ASN barcodes diff --git a/src/documents/tests/test_api_app_config.py b/src/documents/tests/test_api_app_config.py index df5f9e2ad..479229af2 100644 --- a/src/documents/tests/test_api_app_config.py +++ b/src/documents/tests/test_api_app_config.py @@ -32,28 +32,39 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - json.dumps(response.data[0]), - json.dumps( - { - "id": 1, - "user_args": None, - "output_type": None, - "pages": None, - "language": None, - "mode": None, - "skip_archive_file": None, - "image_dpi": None, - "unpaper_clean": None, - "deskew": None, - "rotate_pages": None, - "rotate_pages_threshold": None, - "max_image_pixels": None, - "color_conversion_strategy": None, - "app_title": None, - "app_logo": None, - }, - ), + self.maxDiff = None + + self.assertDictEqual( + response.data[0], + { + "id": 1, + "output_type": None, + "pages": None, + "language": None, + "mode": None, + "skip_archive_file": None, + "image_dpi": None, + "unpaper_clean": None, + "deskew": None, + "rotate_pages": None, + "rotate_pages_threshold": None, + "max_image_pixels": None, + "color_conversion_strategy": None, + "user_args": None, + "app_title": None, + "app_logo": None, + "barcodes_enabled": None, + "barcode_enable_tiff_support": None, + "barcode_string": None, + "barcode_retain_split_pages": None, + "barcode_enable_asn": None, + "barcode_asn_prefix": None, + "barcode_upscale": None, + "barcode_dpi": None, + "barcode_max_pages": None, + "barcode_enable_tag": None, + "barcode_tag_mapping": None, + }, ) def test_api_get_ui_settings_with_config(self): @@ -118,6 +129,7 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase): { "user_args": "", "language": "", + "barcode_tag_mapping": "", }, ), content_type="application/json", @@ -126,6 +138,7 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase): config = ApplicationConfiguration.objects.first() self.assertEqual(config.user_args, None) self.assertEqual(config.language, None) + self.assertEqual(config.barcode_tag_mapping, None) def test_api_replace_app_logo(self): """ diff --git a/src/documents/tests/test_barcodes.py b/src/documents/tests/test_barcodes.py index 03b0903dd..b2c28a82b 100644 --- a/src/documents/tests/test_barcodes.py +++ b/src/documents/tests/test_barcodes.py @@ -22,6 +22,7 @@ from documents.tests.utils import DocumentConsumeDelayMixin from documents.tests.utils import DummyProgressManager from documents.tests.utils import FileSystemAssertsMixin from documents.tests.utils import SampleDirMixin +from paperless.models import ApplicationConfiguration try: import zxingcpp # noqa: F401 @@ -547,6 +548,27 @@ class TestBarcode( }, ) + def test_barcode_config(self): + """ + GIVEN: + - Barcode app config is set (settings are not) + WHEN: + - Document with barcode is processed + THEN: + - The barcode config is used + """ + app_config = ApplicationConfiguration.objects.first() + app_config.barcodes_enabled = True + app_config.barcode_string = "CUSTOM BARCODE" + app_config.save() + test_file = self.BARCODE_SAMPLE_DIR / "barcode-39-custom.pdf" + with self.get_reader(test_file) as reader: + reader.detect() + separator_page_numbers = reader.get_separation_pages() + + self.assertEqual(reader.pdf_file, test_file) + self.assertDictEqual(separator_page_numbers, {0: False}) + @override_settings(CONSUMER_BARCODE_SCANNER="PYZBAR") class TestBarcodeNewConsume( diff --git a/src/paperless/config.py b/src/paperless/config.py index 8a40fc6c6..fb3139d79 100644 --- a/src/paperless/config.py +++ b/src/paperless/config.py @@ -96,10 +96,65 @@ class OcrConfig(OutputTypeConfig): user_args = json.loads(settings.OCR_USER_ARGS) except json.JSONDecodeError: user_args = {} - self.user_args = user_args +@dataclasses.dataclass +class BarcodeConfig(BaseConfig): + """ + Barcodes settings + """ + + barcodes_enabled: bool = dataclasses.field(init=False) + barcode_enable_tiff_support: bool = dataclasses.field(init=False) + barcode_string: str = dataclasses.field(init=False) + barcode_retain_split_pages: bool = dataclasses.field(init=False) + barcode_enable_asn: bool = dataclasses.field(init=False) + barcode_asn_prefix: str = dataclasses.field(init=False) + barcode_upscale: float = dataclasses.field(init=False) + barcode_dpi: int = dataclasses.field(init=False) + barcode_max_pages: int = dataclasses.field(init=False) + barcode_enable_tag: bool = dataclasses.field(init=False) + barcode_tag_mapping: dict[str, str] = dataclasses.field(init=False) + + def __post_init__(self) -> None: + app_config = self._get_config_instance() + + self.barcodes_enabled = ( + app_config.barcodes_enabled or settings.CONSUMER_ENABLE_BARCODES + ) + self.barcode_enable_tiff_support = ( + app_config.barcode_enable_tiff_support + or settings.CONSUMER_BARCODE_TIFF_SUPPORT + ) + self.barcode_string = ( + app_config.barcode_string or settings.CONSUMER_BARCODE_STRING + ) + self.barcode_retain_split_pages = ( + app_config.barcode_retain_split_pages + or settings.CONSUMER_BARCODE_RETAIN_SPLIT_PAGES + ) + self.barcode_enable_asn = ( + app_config.barcode_enable_asn or settings.CONSUMER_ENABLE_ASN_BARCODE + ) + self.barcode_asn_prefix = ( + app_config.barcode_asn_prefix or settings.CONSUMER_ASN_BARCODE_PREFIX + ) + self.barcode_upscale = ( + app_config.barcode_upscale or settings.CONSUMER_BARCODE_UPSCALE + ) + self.barcode_dpi = app_config.barcode_dpi or settings.CONSUMER_BARCODE_DPI + self.barcode_max_pages = ( + app_config.barcode_max_pages or settings.CONSUMER_BARCODE_MAX_PAGES + ) + self.barcode_enable_tag = ( + app_config.barcode_enable_tag or settings.CONSUMER_ENABLE_TAG_BARCODE + ) + self.barcode_tag_mapping = ( + app_config.barcode_tag_mapping or settings.CONSUMER_TAG_BARCODE_MAPPING + ) + + @dataclasses.dataclass class GeneralConfig(BaseConfig): """ diff --git a/src/paperless/migrations/0004_applicationconfiguration_barcode_asn_prefix_and_more.py b/src/paperless/migrations/0004_applicationconfiguration_barcode_asn_prefix_and_more.py new file mode 100644 index 000000000..2913ca836 --- /dev/null +++ b/src/paperless/migrations/0004_applicationconfiguration_barcode_asn_prefix_and_more.py @@ -0,0 +1,100 @@ +# Generated by Django 5.1.7 on 2025-04-02 19:21 + +import django.core.validators +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + dependencies = [ + ("paperless", "0003_alter_applicationconfiguration_max_image_pixels"), + ] + + operations = [ + migrations.AddField( + model_name="applicationconfiguration", + name="barcode_asn_prefix", + field=models.CharField( + blank=True, + max_length=32, + null=True, + verbose_name="Sets the ASN barcode prefix", + ), + ), + migrations.AddField( + model_name="applicationconfiguration", + name="barcode_dpi", + field=models.PositiveIntegerField( + null=True, + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="Sets the barcode DPI", + ), + ), + migrations.AddField( + model_name="applicationconfiguration", + name="barcode_enable_asn", + field=models.BooleanField(null=True, verbose_name="Enables ASN barcode"), + ), + migrations.AddField( + model_name="applicationconfiguration", + name="barcode_enable_tag", + field=models.BooleanField(null=True, verbose_name="Enables tag barcode"), + ), + migrations.AddField( + model_name="applicationconfiguration", + name="barcode_enable_tiff_support", + field=models.BooleanField( + null=True, + verbose_name="Enables barcode TIFF support", + ), + ), + migrations.AddField( + model_name="applicationconfiguration", + name="barcode_max_pages", + field=models.PositiveIntegerField( + null=True, + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="Sets the maximum pages for barcode", + ), + ), + migrations.AddField( + model_name="applicationconfiguration", + name="barcode_retain_split_pages", + field=models.BooleanField(null=True, verbose_name="Retains split pages"), + ), + migrations.AddField( + model_name="applicationconfiguration", + name="barcode_string", + field=models.CharField( + blank=True, + max_length=32, + null=True, + verbose_name="Sets the barcode string", + ), + ), + migrations.AddField( + model_name="applicationconfiguration", + name="barcode_tag_mapping", + field=models.JSONField( + null=True, + verbose_name="Sets the tag barcode mapping", + ), + ), + migrations.AddField( + model_name="applicationconfiguration", + name="barcode_upscale", + field=models.FloatField( + null=True, + validators=[django.core.validators.MinValueValidator(1.0)], + verbose_name="Sets the barcode upscale factor", + ), + ), + migrations.AddField( + model_name="applicationconfiguration", + name="barcodes_enabled", + field=models.BooleanField( + null=True, + verbose_name="Enables barcode scanning", + ), + ), + ] diff --git a/src/paperless/models.py b/src/paperless/models.py index 1f6cfbced..1c44f1414 100644 --- a/src/paperless/models.py +++ b/src/paperless/models.py @@ -167,6 +167,10 @@ class ApplicationConfiguration(AbstractSingletonModel): null=True, ) + """ + Settings for the Paperless application + """ + app_title = models.CharField( verbose_name=_("Application title"), null=True, @@ -184,6 +188,83 @@ class ApplicationConfiguration(AbstractSingletonModel): upload_to="logo/", ) + """ + Settings for the barcode scanner + """ + + # PAPERLESS_CONSUMER_ENABLE_BARCODES + barcodes_enabled = models.BooleanField( + verbose_name=_("Enables barcode scanning"), + null=True, + ) + + # PAPERLESS_CONSUMER_BARCODE_TIFF_SUPPORT + barcode_enable_tiff_support = models.BooleanField( + verbose_name=_("Enables barcode TIFF support"), + null=True, + ) + + # PAPERLESS_CONSUMER_BARCODE_STRING + barcode_string = models.CharField( + verbose_name=_("Sets the barcode string"), + null=True, + blank=True, + max_length=32, + ) + + # PAPERLESS_CONSUMER_BARCODE_RETAIN_SPLIT_PAGES + barcode_retain_split_pages = models.BooleanField( + verbose_name=_("Retains split pages"), + null=True, + ) + + # PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE + barcode_enable_asn = models.BooleanField( + verbose_name=_("Enables ASN barcode"), + null=True, + ) + + # PAPERLESS_CONSUMER_ASN_BARCODE_PREFIX + barcode_asn_prefix = models.CharField( + verbose_name=_("Sets the ASN barcode prefix"), + null=True, + blank=True, + max_length=32, + ) + + # PAPERLESS_CONSUMER_BARCODE_UPSCALE + barcode_upscale = models.FloatField( + verbose_name=_("Sets the barcode upscale factor"), + null=True, + validators=[MinValueValidator(1.0)], + ) + + # PAPERLESS_CONSUMER_BARCODE_DPI + barcode_dpi = models.PositiveIntegerField( + verbose_name=_("Sets the barcode DPI"), + null=True, + validators=[MinValueValidator(1)], + ) + + # PAPERLESS_CONSUMER_BARCODE_MAX_PAGES + barcode_max_pages = models.PositiveIntegerField( + verbose_name=_("Sets the maximum pages for barcode"), + null=True, + validators=[MinValueValidator(1)], + ) + + # PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE + barcode_enable_tag = models.BooleanField( + verbose_name=_("Enables tag barcode"), + null=True, + ) + + # PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING + barcode_tag_mapping = models.JSONField( + verbose_name=_("Sets the tag barcode mapping"), + null=True, + ) + class Meta: verbose_name = _("paperless application settings") diff --git a/src/paperless/serialisers.py b/src/paperless/serialisers.py index 461eef587..dd315f4db 100644 --- a/src/paperless/serialisers.py +++ b/src/paperless/serialisers.py @@ -185,11 +185,14 @@ class ProfileSerializer(serializers.ModelSerializer): class ApplicationConfigurationSerializer(serializers.ModelSerializer): user_args = serializers.JSONField(binary=True, allow_null=True) + barcode_tag_mapping = serializers.JSONField(binary=True, allow_null=True) def run_validation(self, data): # Empty strings treated as None to avoid unexpected behavior if "user_args" in data and data["user_args"] == "": data["user_args"] = None + if "barcode_tag_mapping" in data and data["barcode_tag_mapping"] == "": + data["barcode_tag_mapping"] = None if "language" in data and data["language"] == "": data["language"] = None return super().run_validation(data)