From db00617c6aba905c9b14b2bb2dcf4028bba42856 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 2 Apr 2025 12:23:28 -0700 Subject: [PATCH] Enhancement: add barcode frontend config --- .../admin/config/config.component.spec.ts | 4 +- src-ui/src/app/data/paperless-config.ts | 89 ++++++++++++++++ src/documents/tests/test_api_app_config.py | 55 ++++++---- src/paperless/config.py | 46 +++++++- ...nfiguration_barcode_asn_prefix_and_more.py | 100 ++++++++++++++++++ src/paperless/models.py | 81 ++++++++++++++ src/paperless/serialisers.py | 3 + 7 files changed, 353 insertions(+), 25 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/tests/test_api_app_config.py b/src/documents/tests/test_api_app_config.py index df5f9e2ad..71855b8d4 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): diff --git a/src/paperless/config.py b/src/paperless/config.py index 8a40fc6c6..85e681009 100644 --- a/src/paperless/config.py +++ b/src/paperless/config.py @@ -55,6 +55,17 @@ class OcrConfig(OutputTypeConfig): max_image_pixel: float | None = dataclasses.field(init=False) color_conversion_strategy: str = dataclasses.field(init=False) user_args: dict[str, str] | None = dataclasses.field(init=False) + 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: super().__post_init__() @@ -96,9 +107,42 @@ class OcrConfig(OutputTypeConfig): user_args = json.loads(settings.OCR_USER_ARGS) except json.JSONDecodeError: user_args = {} - self.user_args = user_args + 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)