]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Enhancement: add barcode frontend config (#9742)
authorshamoon <4887959+shamoon@users.noreply.github.com>
Sun, 11 May 2025 19:44:06 +0000 (12:44 -0700)
committerGitHub <noreply@github.com>
Sun, 11 May 2025 19:44:06 +0000 (19:44 +0000)
src-ui/src/app/components/admin/config/config.component.spec.ts
src-ui/src/app/data/paperless-config.ts
src/documents/barcodes.py
src/documents/tests/test_api_app_config.py
src/documents/tests/test_barcodes.py
src/paperless/config.py
src/paperless/migrations/0004_applicationconfiguration_barcode_asn_prefix_and_more.py [new file with mode: 0644]
src/paperless/models.py
src/paperless/serialisers.py

index 1915325904be00f3146735fbcd145ef1e2d1d329..079bd1420b03aec603e960af6483b53da00ca5be 100644 (file)
@@ -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', () => {
index 3ae485ff24c2632d58cb212c25d70871dc031755..3afca66ffae33ffe8f9d7d72e3e2a144ef30f87a 100644 (file)
@@ -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
 }
index 3b0c1d33bba29f0c35c75887d684ce1833231e58..fdb671d2c6f58e44cbf559624f066ac51b1ef34d 100644 (file)
@@ -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
index df5f9e2ad83081eb8c6a735267b41a1acb822088..479229af276395c8977816a048a7f6db4b5eee66 100644 (file)
@@ -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):
         """
index 03b0903dd992d96da7138c42e1ca4fd32feabbca..b2c28a82b0591c1901f91996d4347c7f5312c92b 100644 (file)
@@ -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(
index 8a40fc6c6ce5d18996a9123c917927dfc4bd57ee..fb3139d7943e8916e999a75164cd65c36da7933e 100644 (file)
@@ -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 (file)
index 0000000..2913ca8
--- /dev/null
@@ -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",
+            ),
+        ),
+    ]
index 1f6cfbcedd7fdd238b25d74aff312d48de4cd87a..1c44f1414f748f143de9b382fd5018acc6a74179 100644 (file)
@@ -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")
 
index 461eef587230ae1ce6d0e329e76612d2631a29ef..dd315f4db166b1e326ef50d9c6bc84d998c12ae2 100644 (file)
@@ -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)