diff --git a/.env.dev b/.env.dev new file mode 100644 index 00000000..e69de29b diff --git a/VERSION b/VERSION index 24a57f28..ace44233 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.14.5 +1.15.1 diff --git a/assets/controllers/pages/barcode_scan_controller.js b/assets/controllers/pages/barcode_scan_controller.js index 5f82a39e..368fef43 100644 --- a/assets/controllers/pages/barcode_scan_controller.js +++ b/assets/controllers/pages/barcode_scan_controller.js @@ -20,7 +20,7 @@ import {Controller} from "@hotwired/stimulus"; //import * as ZXing from "@zxing/library"; -import {Html5QrcodeScanner, Html5Qrcode} from "html5-qrcode"; +import {Html5QrcodeScanner, Html5Qrcode} from "@part-db/html5-qrcode"; /* stimulusFetch: 'lazy' */ export default class extends Controller { @@ -50,7 +50,7 @@ export default class extends Controller { }); this._scanner = new Html5QrcodeScanner(this.element.id, { - fps: 2, + fps: 10, qrbox: qrboxFunction, experimentalFeatures: { //This option improves reading quality on android chrome @@ -61,6 +61,11 @@ export default class extends Controller { this._scanner.render(this.onScanSuccess.bind(this)); } + disconnect() { + this._scanner.pause(); + this._scanner.clear(); + } + onScanSuccess(decodedText, decodedResult) { //Put our decoded Text into the input box document.getElementById('scan_dialog_input').value = decodedText; diff --git a/assets/js/app.js b/assets/js/app.js index fd7db935..43acec5d 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -44,4 +44,18 @@ import "./register_events"; import "./tristate_checkboxes"; //Define jquery globally -window.$ = window.jQuery = require("jquery") \ No newline at end of file +window.$ = window.jQuery = require("jquery"); + +//Use the local WASM file for the ZXing library +import { + setZXingModuleOverrides, +} from "barcode-detector/pure"; +import wasmFile from "../../node_modules/zxing-wasm/dist/reader/zxing_reader.wasm"; +setZXingModuleOverrides({ + locateFile: (path, prefix) => { + if (path.endsWith(".wasm")) { + return wasmFile; + } + return prefix + path; + }, +}); \ No newline at end of file diff --git a/bin/console b/bin/console index 88268a20..256c0a60 100755 --- a/bin/console +++ b/bin/console @@ -4,6 +4,10 @@ use App\Kernel; use Symfony\Bundle\FrameworkBundle\Console\Application; +if (!is_dir(dirname(__DIR__).'/vendor')) { + throw new LogicException('Dependencies are missing. Try running "composer install".'); +} + //Increase xdebug.max_nesting_level to 1000 if required (see issue #411) //Check if xdebug extension is active, and xdebug.max_nesting_level is set to 256 or lower if (extension_loaded('xdebug') && ((int) ini_get('xdebug.max_nesting_level')) <= 256) { diff --git a/composer.lock b/composer.lock index 05254d0c..a4bae356 100644 --- a/composer.lock +++ b/composer.lock @@ -2869,16 +2869,16 @@ }, { "name": "dompdf/dompdf", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "2d622faf9aa1f8f7f24dd094e49b5cf6c0c5d4e6" + "reference": "baf4084b27c7f4b5b7a221b19a94d11327664eb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/2d622faf9aa1f8f7f24dd094e49b5cf6c0c5d4e6", - "reference": "2d622faf9aa1f8f7f24dd094e49b5cf6c0c5d4e6", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/baf4084b27c7f4b5b7a221b19a94d11327664eb8", + "reference": "baf4084b27c7f4b5b7a221b19a94d11327664eb8", "shasum": "" }, "require": { @@ -2894,7 +2894,7 @@ "ext-json": "*", "ext-zip": "*", "mockery/mockery": "^1.3", - "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", "squizlabs/php_codesniffer": "^3.5", "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" }, @@ -2927,9 +2927,9 @@ "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v3.0.1" + "source": "https://github.com/dompdf/dompdf/tree/v3.0.2" }, - "time": "2024-12-05T14:59:38+00:00" + "time": "2024-12-27T20:27:37+00:00" }, { "name": "dompdf/php-font-lib", @@ -8052,16 +8052,16 @@ }, { "name": "spomky-labs/pki-framework", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "0b10c8b53366729417d6226ae89a665f9e2d61b6" + "reference": "5ac374c3e295c8b917208ff41b4d30f76668478c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/0b10c8b53366729417d6226ae89a665f9e2d61b6", - "reference": "0b10c8b53366729417d6226ae89a665f9e2d61b6", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/5ac374c3e295c8b917208ff41b4d30f76668478c", + "reference": "5ac374c3e295c8b917208ff41b4d30f76668478c", "shasum": "" }, "require": { @@ -8070,21 +8070,19 @@ "php": ">=8.1" }, "require-dev": { - "ekino/phpstan-banned-code": "^1.0", + "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", "ext-gmp": "*", "ext-openssl": "*", - "infection/infection": "^0.28", + "infection/infection": "^0.28|^0.29", "php-parallel-lint/php-parallel-lint": "^1.3", - "phpstan/extension-installer": "^1.3", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-beberlei-assert": "^1.0", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", + "phpstan/extension-installer": "^1.3|^2.0", + "phpstan/phpstan": "^1.8|^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.1|^2.0", + "phpstan/phpstan-strict-rules": "^1.3|^2.0", "phpunit/phpunit": "^10.1|^11.0", - "rector/rector": "^1.0", + "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", - "symfony/phpunit-bridge": "^6.4|^7.0", "symfony/string": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", "symplify/easy-coding-standard": "^12.0" @@ -8147,7 +8145,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.2.1" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.2.2" }, "funding": [ { @@ -8159,7 +8157,7 @@ "type": "patreon" } ], - "time": "2024-03-30T18:03:49+00:00" + "time": "2025-01-03T09:35:48+00:00" }, { "name": "symfony/apache-pack", @@ -8579,16 +8577,16 @@ }, { "name": "symfony/console", - "version": "v6.4.15", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f1fc6f47283e27336e7cebb9e8946c8de7bff9bd" + "reference": "799445db3f15768ecc382ac5699e6da0520a0a04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f1fc6f47283e27336e7cebb9e8946c8de7bff9bd", - "reference": "f1fc6f47283e27336e7cebb9e8946c8de7bff9bd", + "url": "https://api.github.com/repos/symfony/console/zipball/799445db3f15768ecc382ac5699e6da0520a0a04", + "reference": "799445db3f15768ecc382ac5699e6da0520a0a04", "shasum": "" }, "require": { @@ -8653,7 +8651,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.15" + "source": "https://github.com/symfony/console/tree/v6.4.17" }, "funding": [ { @@ -8669,7 +8667,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:19:14+00:00" + "time": "2024-12-07T12:07:30+00:00" }, { "name": "symfony/css-selector", @@ -8886,16 +8884,16 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v6.4.16", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "429a4b6786901afcc085ee16dc3f2be621f33488" + "reference": "2ba7e747a944b69f9f583c35173560afebbff995" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/429a4b6786901afcc085ee16dc3f2be621f33488", - "reference": "429a4b6786901afcc085ee16dc3f2be621f33488", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/2ba7e747a944b69f9f583c35173560afebbff995", + "reference": "2ba7e747a944b69f9f583c35173560afebbff995", "shasum": "" }, "require": { @@ -8974,7 +8972,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v6.4.16" + "source": "https://github.com/symfony/doctrine-bridge/tree/v6.4.17" }, "funding": [ { @@ -8990,7 +8988,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T12:00:20+00:00" + "time": "2024-12-18T10:42:42+00:00" }, { "name": "symfony/dotenv", @@ -9068,16 +9066,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.4.14", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9" + "reference": "37ad2380e8c1a8cf62a1200a5c10080b679b446c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/9e024324511eeb00983ee76b9aedc3e6ecd993d9", - "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/37ad2380e8c1a8cf62a1200a5c10080b679b446c", + "reference": "37ad2380e8c1a8cf62a1200a5c10080b679b446c", "shasum": "" }, "require": { @@ -9123,7 +9121,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.14" + "source": "https://github.com/symfony/error-handler/tree/v6.4.17" }, "funding": [ { @@ -9139,7 +9137,7 @@ "type": "tidelift" } ], - "time": "2024-11-05T15:34:40+00:00" + "time": "2024-12-06T13:30:51+00:00" }, { "name": "symfony/event-dispatcher", @@ -9429,16 +9427,16 @@ }, { "name": "symfony/finder", - "version": "v6.4.13", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "daea9eca0b08d0ed1dc9ab702a46128fd1be4958" + "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/daea9eca0b08d0ed1dc9ab702a46128fd1be4958", - "reference": "daea9eca0b08d0ed1dc9ab702a46128fd1be4958", + "url": "https://api.github.com/repos/symfony/finder/zipball/1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", + "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", "shasum": "" }, "require": { @@ -9473,7 +9471,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.13" + "source": "https://github.com/symfony/finder/tree/v6.4.17" }, "funding": [ { @@ -9489,7 +9487,7 @@ "type": "tidelift" } ], - "time": "2024-10-01T08:30:56+00:00" + "time": "2024-12-29T13:51:37+00:00" }, { "name": "symfony/flex", @@ -9658,16 +9656,16 @@ }, { "name": "symfony/framework-bundle", - "version": "v6.4.13", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "e8b0bd921f9bd35ea4d1508067c3f3f6e2036418" + "reference": "17d8ae2e7aa77154f942e8ac48849ac718b0963f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/e8b0bd921f9bd35ea4d1508067c3f3f6e2036418", - "reference": "e8b0bd921f9bd35ea4d1508067c3f3f6e2036418", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/17d8ae2e7aa77154f942e8ac48849ac718b0963f", + "reference": "17d8ae2e7aa77154f942e8ac48849ac718b0963f", "shasum": "" }, "require": { @@ -9787,7 +9785,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v6.4.13" + "source": "https://github.com/symfony/framework-bundle/tree/v6.4.17" }, "funding": [ { @@ -9803,27 +9801,27 @@ "type": "tidelift" } ], - "time": "2024-10-25T15:07:50+00:00" + "time": "2024-12-19T14:08:41+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.16", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "60a113666fa67e598abace38e5f46a0954d8833d" + "reference": "88898d842eb29d7e1a903724c94e90a6ca9c0509" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/60a113666fa67e598abace38e5f46a0954d8833d", - "reference": "60a113666fa67e598abace38e5f46a0954d8833d", + "url": "https://api.github.com/repos/symfony/http-client/zipball/88898d842eb29d7e1a903724c94e90a6ca9c0509", + "reference": "88898d842eb29d7e1a903724c94e90a6ca9c0509", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "~3.4.3|^3.5.1", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -9880,7 +9878,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.16" + "source": "https://github.com/symfony/http-client/tree/v6.4.17" }, "funding": [ { @@ -9896,7 +9894,7 @@ "type": "tidelift" } ], - "time": "2024-11-27T11:52:33+00:00" + "time": "2024-12-18T12:18:31+00:00" }, { "name": "symfony/http-client-contracts", @@ -10055,16 +10053,16 @@ }, { "name": "symfony/http-kernel", - "version": "v6.4.16", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "8838b5b21d807923b893ccbfc2cbeda0f1bc00f0" + "reference": "c5647393c5ce11833d13e4b70fff4b571d4ac710" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/8838b5b21d807923b893ccbfc2cbeda0f1bc00f0", - "reference": "8838b5b21d807923b893ccbfc2cbeda0f1bc00f0", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/c5647393c5ce11833d13e4b70fff4b571d4ac710", + "reference": "c5647393c5ce11833d13e4b70fff4b571d4ac710", "shasum": "" }, "require": { @@ -10149,7 +10147,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.16" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.17" }, "funding": [ { @@ -10165,7 +10163,7 @@ "type": "tidelift" } ], - "time": "2024-11-27T12:49:36+00:00" + "time": "2024-12-31T14:49:31+00:00" }, { "name": "symfony/intl", @@ -10332,16 +10330,16 @@ }, { "name": "symfony/mime", - "version": "v6.4.13", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "1de1cf14d99b12c7ebbb850491ec6ae3ed468855" + "reference": "ea87c8850a54ff039d3e0ab4ae5586dd4e6c0232" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/1de1cf14d99b12c7ebbb850491ec6ae3ed468855", - "reference": "1de1cf14d99b12c7ebbb850491ec6ae3ed468855", + "url": "https://api.github.com/repos/symfony/mime/zipball/ea87c8850a54ff039d3e0ab4ae5586dd4e6c0232", + "reference": "ea87c8850a54ff039d3e0ab4ae5586dd4e6c0232", "shasum": "" }, "require": { @@ -10397,7 +10395,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.13" + "source": "https://github.com/symfony/mime/tree/v6.4.17" }, "funding": [ { @@ -10413,7 +10411,7 @@ "type": "tidelift" } ], - "time": "2024-10-25T15:07:50+00:00" + "time": "2024-12-02T11:09:41+00:00" }, { "name": "symfony/monolog-bridge", @@ -11726,16 +11724,16 @@ }, { "name": "symfony/property-info", - "version": "v6.4.16", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "e4782ec1c2b6896e820896357f6a3d02249e63eb" + "reference": "38b125d78e67668159f75383a293ec0c5d3f2963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/e4782ec1c2b6896e820896357f6a3d02249e63eb", - "reference": "e4782ec1c2b6896e820896357f6a3d02249e63eb", + "url": "https://api.github.com/repos/symfony/property-info/zipball/38b125d78e67668159f75383a293ec0c5d3f2963", + "reference": "38b125d78e67668159f75383a293ec0c5d3f2963", "shasum": "" }, "require": { @@ -11790,7 +11788,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v6.4.16" + "source": "https://github.com/symfony/property-info/tree/v6.4.17" }, "funding": [ { @@ -11806,7 +11804,7 @@ "type": "tidelift" } ], - "time": "2024-11-27T10:18:02+00:00" + "time": "2024-12-26T19:01:29+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -13051,16 +13049,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v6.4.16", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "32ec012ed4f6426441a66014471bdb26674744be" + "reference": "238e1aac992b5231c66faf10131ace7bdba97065" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/32ec012ed4f6426441a66014471bdb26674744be", - "reference": "32ec012ed4f6426441a66014471bdb26674744be", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/238e1aac992b5231c66faf10131ace7bdba97065", + "reference": "238e1aac992b5231c66faf10131ace7bdba97065", "shasum": "" }, "require": { @@ -13140,7 +13138,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v6.4.16" + "source": "https://github.com/symfony/twig-bridge/tree/v6.4.17" }, "funding": [ { @@ -13156,7 +13154,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T11:59:11+00:00" + "time": "2024-12-19T14:08:41+00:00" }, { "name": "symfony/twig-bundle", @@ -13492,16 +13490,16 @@ }, { "name": "symfony/validator", - "version": "v6.4.16", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "9b0d1988b56511706bc91d96ead39acd77aaf34d" + "reference": "a3c19a0e542d427c207e22242043ef35b5b99a2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/9b0d1988b56511706bc91d96ead39acd77aaf34d", - "reference": "9b0d1988b56511706bc91d96ead39acd77aaf34d", + "url": "https://api.github.com/repos/symfony/validator/zipball/a3c19a0e542d427c207e22242043ef35b5b99a2c", + "reference": "a3c19a0e542d427c207e22242043ef35b5b99a2c", "shasum": "" }, "require": { @@ -13569,7 +13567,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v6.4.16" + "source": "https://github.com/symfony/validator/tree/v6.4.17" }, "funding": [ { @@ -13585,7 +13583,7 @@ "type": "tidelift" } ], - "time": "2024-11-27T09:48:51+00:00" + "time": "2024-12-29T12:50:19+00:00" }, { "name": "symfony/var-dumper", @@ -14911,16 +14909,16 @@ }, { "name": "web-auth/webauthn-lib", - "version": "4.9.1", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "fd7a0943c663b325e92ad562c2bcc943e77beeac" + "reference": "008b25171c27cf4813420d0de31cc059bcc71f1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/fd7a0943c663b325e92ad562c2bcc943e77beeac", - "reference": "fd7a0943c663b325e92ad562c2bcc943e77beeac", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/008b25171c27cf4813420d0de31cc059bcc71f1a", + "reference": "008b25171c27cf4813420d0de31cc059bcc71f1a", "shasum": "" }, "require": { @@ -14985,7 +14983,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/4.9.1" + "source": "https://github.com/web-auth/webauthn-lib/tree/4.9.2" }, "funding": [ { @@ -14997,20 +14995,20 @@ "type": "patreon" } ], - "time": "2024-07-16T18:36:36+00:00" + "time": "2025-01-04T09:47:58+00:00" }, { "name": "web-auth/webauthn-symfony-bundle", - "version": "4.9.1", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-symfony-bundle.git", - "reference": "fa6dfa596db1cbf159079c0bb76d746ed84c4a26" + "reference": "80aa16fa6f16ab8f017a4108ffcd2ecc12264c07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/fa6dfa596db1cbf159079c0bb76d746ed84c4a26", - "reference": "fa6dfa596db1cbf159079c0bb76d746ed84c4a26", + "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/80aa16fa6f16ab8f017a4108ffcd2ecc12264c07", + "reference": "80aa16fa6f16ab8f017a4108ffcd2ecc12264c07", "shasum": "" }, "require": { @@ -15070,7 +15068,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.9.1" + "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.9.2" }, "funding": [ { @@ -15082,11 +15080,11 @@ "type": "patreon" } ], - "time": "2024-07-11T09:06:25+00:00" + "time": "2025-01-04T09:38:56+00:00" }, { "name": "web-token/jwt-library", - "version": "3.4.6", + "version": "3.4.7", "source": { "type": "git", "url": "https://github.com/web-token/jwt-library.git", @@ -15168,7 +15166,7 @@ ], "support": { "issues": "https://github.com/web-token/jwt-library/issues", - "source": "https://github.com/web-token/jwt-library/tree/3.4.6" + "source": "https://github.com/web-token/jwt-library/tree/3.4.7" }, "funding": [ { @@ -15649,16 +15647,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -15701,9 +15699,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "phar-io/manifest", @@ -15873,16 +15871,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.0.4", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "50d276fc3bf1430ec315f2f109bbde2769821524" + "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/50d276fc3bf1430ec315f2f109bbde2769821524", - "reference": "50d276fc3bf1430ec315f2f109bbde2769821524", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", + "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", "shasum": "" }, "require": { @@ -15927,7 +15925,7 @@ "type": "github" } ], - "time": "2024-12-17T17:14:01+00:00" + "time": "2025-01-05T16:43:48+00:00" }, { "name": "phpstan/phpstan-doctrine", @@ -16050,16 +16048,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "1ef4dce2baabd464c2dd3109d051bad94efa1e79" + "reference": "c08cd8e54a08d651bc402d304cfa161c3c3766c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/1ef4dce2baabd464c2dd3109d051bad94efa1e79", - "reference": "1ef4dce2baabd464c2dd3109d051bad94efa1e79", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/c08cd8e54a08d651bc402d304cfa161c3c3766c4", + "reference": "c08cd8e54a08d651bc402d304cfa161c3c3766c4", "shasum": "" }, "require": { @@ -16115,9 +16113,9 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.0" + "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.1" }, - "time": "2024-11-06T10:13:40+00:00" + "time": "2025-01-04T13:58:15+00:00" }, { "name": "phpunit/php-code-coverage", @@ -16543,21 +16541,21 @@ }, { "name": "rector/rector", - "version": "2.0.4", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "df5de7b80deced1ea7f719a0b4d02e4aee87dd21" + "reference": "fa0cb009dc3df084bf549032ae4080a0481a2036" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/df5de7b80deced1ea7f719a0b4d02e4aee87dd21", - "reference": "df5de7b80deced1ea7f719a0b4d02e4aee87dd21", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/fa0cb009dc3df084bf549032ae4080a0481a2036", + "reference": "fa0cb009dc3df084bf549032ae4080a0481a2036", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.0.4" + "phpstan/phpstan": "^2.1.1" }, "conflict": { "rector/rector-doctrine": "*", @@ -16590,7 +16588,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.0.4" + "source": "https://github.com/rectorphp/rector/tree/2.0.6" }, "funding": [ { @@ -16598,7 +16596,7 @@ "type": "github" } ], - "time": "2024-12-26T23:06:19+00:00" + "time": "2025-01-06T10:38:36+00:00" }, { "name": "roave/security-advisories", @@ -16606,12 +16604,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "9dd07280fc4c5f6c3d2a3da5e4058fc7288c2a1d" + "reference": "6ec01b072baedc0e230b90c70e521007851c8f7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/9dd07280fc4c5f6c3d2a3da5e4058fc7288c2a1d", - "reference": "9dd07280fc4c5f6c3d2a3da5e4058fc7288c2a1d", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/6ec01b072baedc0e230b90c70e521007851c8f7c", + "reference": "6ec01b072baedc0e230b90c70e521007851c8f7c", "shasum": "" }, "conflict": { @@ -16733,7 +16731,7 @@ "datatables/datatables": "<1.10.10", "david-garcia/phpwhois": "<=4.3.1", "dbrisinajumi/d2files": "<1", - "dcat/laravel-admin": "<=2.1.3", + "dcat/laravel-admin": "<=2.1.3|==2.2.0.0-beta|==2.2.2.0-beta", "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", "desperado/xml-bundle": "<=0.1.7", @@ -16855,6 +16853,7 @@ "grumpydictator/firefly-iii": "<6.1.17", "gugoan/economizzer": "<=0.9.0.0-beta1", "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", + "guzzlehttp/oauth-subscriber": "<0.8.1", "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", "haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2", "harvesthq/chosen": "<1.8.7", @@ -17018,7 +17017,7 @@ "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", - "nilsteampassnet/teampass": "<3.0.10", + "nilsteampassnet/teampass": "<3.1.3.1-dev", "nonfiction/nterchange": "<4.1.1", "notrinos/notrinos-erp": "<=0.7", "noumo/easyii": "<=0.9", @@ -17079,10 +17078,10 @@ "phpmailer/phpmailer": "<6.5", "phpmussel/phpmussel": ">=1,<1.6", "phpmyadmin/phpmyadmin": "<5.2.1", - "phpmyfaq/phpmyfaq": "<3.2.5|==3.2.5", + "phpmyfaq/phpmyfaq": "<3.2.5|==3.2.5|>=3.2.10,<=4.0.1", "phpoffice/common": "<0.2.9", "phpoffice/phpexcel": "<1.8.1", - "phpoffice/phpspreadsheet": "<1.29.4|>=2,<2.1.3|>=2.2,<2.3.2|>=3.3,<3.4", + "phpoffice/phpspreadsheet": "<=1.29.6|>=2,<=2.1.5|>=2.2,<=2.3.4|>=3,<3.7", "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", "phpservermon/phpservermon": "<3.6", "phpsysinfo/phpsysinfo": "<3.4.3", @@ -17208,6 +17207,7 @@ "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", "ssddanbrown/bookstack": "<24.05.1", "starcitizentools/citizen-skin": ">=2.6.3,<2.31", + "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2", "statamic/cms": "<=5.16", "stormpath/sdk": "<9.9.99", "studio-42/elfinder": "<=2.1.64", @@ -17279,7 +17279,7 @@ "thelia/thelia": ">=2.1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", "thinkcmf/thinkcmf": "<6.0.8", - "thorsten/phpmyfaq": "<4", + "thorsten/phpmyfaq": "<=4.0.1", "tikiwiki/tiki-manager": "<=17.1", "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", "tinymce/tinymce": "<7.2", @@ -17363,7 +17363,7 @@ "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", "yeswiki/yeswiki": "<=4.4.4", - "yetiforce/yetiforce-crm": "<=6.4", + "yetiforce/yetiforce-crm": "<6.5", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", "yiisoft/yii": "<1.1.29", @@ -17453,7 +17453,7 @@ "type": "tidelift" } ], - "time": "2024-12-27T20:05:23+00:00" + "time": "2025-01-06T20:04:58+00:00" }, { "name": "sebastian/cli-parser", @@ -18803,16 +18803,16 @@ }, { "name": "symfony/web-profiler-bundle", - "version": "v6.4.16", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "2d58fd04ac0d3c6279cadd0105959083ef1d7f5b" + "reference": "979f8ee1a4f2464c20f3fef0d2111827fef2e97e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/2d58fd04ac0d3c6279cadd0105959083ef1d7f5b", - "reference": "2d58fd04ac0d3c6279cadd0105959083ef1d7f5b", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/979f8ee1a4f2464c20f3fef0d2111827fef2e97e", + "reference": "979f8ee1a4f2464c20f3fef0d2111827fef2e97e", "shasum": "" }, "require": { @@ -18865,7 +18865,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v6.4.16" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v6.4.17" }, "funding": [ { @@ -18881,20 +18881,20 @@ "type": "tidelift" } ], - "time": "2024-11-19T10:11:25+00:00" + "time": "2024-12-08T23:00:41+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "12.5.4", + "version": "12.5.5", "source": { "type": "git", "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", - "reference": "5673ecbc03eef9d7b2f563819c80e8e1ce0161be" + "reference": "16a6ac7f452e230fdcc81f1b35b2366903fcecf3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/5673ecbc03eef9d7b2f563819c80e8e1ce0161be", - "reference": "5673ecbc03eef9d7b2f563819c80e8e1ce0161be", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/16a6ac7f452e230fdcc81f1b35b2366903fcecf3", + "reference": "16a6ac7f452e230fdcc81f1b35b2366903fcecf3", "shasum": "" }, "require": { @@ -18930,7 +18930,7 @@ ], "support": { "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues", - "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.5.4" + "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.5.5" }, "funding": [ { @@ -18942,7 +18942,7 @@ "type": "github" } ], - "time": "2024-12-12T15:36:04+00:00" + "time": "2025-01-02T08:43:03+00:00" }, { "name": "theseer/tokenizer", @@ -19020,9 +19020,9 @@ "ext-json": "*", "ext-mbstring": "*" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/config/packages/nelmio_security.yaml b/config/packages/nelmio_security.yaml index 075ce930..1cb74da7 100644 --- a/config/packages/nelmio_security.yaml +++ b/config/packages/nelmio_security.yaml @@ -51,12 +51,16 @@ nelmio_security: img-src: - '*' - 'data:' + # Required for be able to load pictures in the QR code scanner + - 'blob:' style-src: - 'self' - 'unsafe-inline' - 'data:' script-src: - 'self' + # Required for loading the Wasm for the barcode scanner: + - 'wasm-unsafe-eval' object-src: - 'self' - 'data:' diff --git a/package.json b/package.json index 1fdf69d8..38656c72 100644 --- a/package.json +++ b/package.json @@ -66,12 +66,14 @@ "@ckeditor/ckeditor5-watchdog": "^44.0.0", "@ckeditor/ckeditor5-word-count": "^44.0.0", "@jbtronics/bs-treeview": "^1.0.1", + "@part-db/html5-qrcode": "^3.1.0", "@zxcvbn-ts/core": "^3.0.2", "@zxcvbn-ts/language-common": "^3.0.3", "@zxcvbn-ts/language-de": "^3.0.1", "@zxcvbn-ts/language-en": "^3.0.1", "@zxcvbn-ts/language-fr": "^3.0.1", "@zxcvbn-ts/language-ja": "^3.0.1", + "barcode-detector": "^2.3.1", "bootbox": "^6.0.0", "bootswatch": "^5.1.3", "bs-custom-file-input": "^1.3.4", @@ -87,7 +89,6 @@ "dompurify": "^3.0.3", "emoji.json": "^15.0.0", "exports-loader": "^5.0.0", - "html5-qrcode": "^2.2.1", "json-formatter-js": "^2.3.4", "jszip": "^3.2.0", "katex": "^0.16.0", diff --git a/src/Controller/LabelController.php b/src/Controller/LabelController.php index 09bec781..4950628b 100644 --- a/src/Controller/LabelController.php +++ b/src/Controller/LabelController.php @@ -108,8 +108,31 @@ class LabelController extends AbstractController $pdf_data = null; $filename = 'invalid.pdf'; - //Generate PDF either when the form is submitted and valid, or the form was not submit yet, and generate is set if (($form->isSubmitted() && $form->isValid()) || ($generate && !$form->isSubmitted() && $profile instanceof LabelProfile)) { + + //Check if the label should be saved as profile + if ($form->get('save_profile')->isClicked() && $this->isGranted('@labels.create_profiles')) { //@phpstan-ignore-line Phpstan does not recognize the isClicked method + //Retrieve the profile name from the form + $new_name = $form->get('save_profile_name')->getData(); + //ensure that the name is not empty + if ($new_name === '' || $new_name === null) { + $form->get('save_profile_name')->addError(new FormError($this->translator->trans('label_generator.profile_name_empty'))); + goto render; + } + + $profile = new LabelProfile(); + $profile->setName($form->get('save_profile_name')->getData()); + $profile->setOptions($form_options); + $this->em->persist($profile); + $this->em->flush(); + $this->addFlash('success', 'label_generator.profile_saved'); + + return $this->redirectToRoute('label_dialog_profile', [ + 'profile' => $profile->getID(), + 'target_id' => (string) $form->get('target_id')->getData() + ]); + } + $target_id = (string) $form->get('target_id')->getData(); $targets = $this->findObjects($form_options->getSupportedElement(), $target_id); if ($targets !== []) { @@ -132,6 +155,7 @@ class LabelController extends AbstractController } } + render: return $this->render('label_system/dialog.html.twig', [ 'form' => $form, 'pdf_data' => $pdf_data, diff --git a/src/Controller/ScanController.php b/src/Controller/ScanController.php index 77183c89..aebadd89 100644 --- a/src/Controller/ScanController.php +++ b/src/Controller/ScanController.php @@ -42,10 +42,10 @@ declare(strict_types=1); namespace App\Controller; use App\Form\LabelSystem\ScanDialogType; -use App\Services\LabelSystem\Barcodes\BarcodeScanHelper; -use App\Services\LabelSystem\Barcodes\BarcodeRedirector; -use App\Services\LabelSystem\Barcodes\BarcodeScanResult; -use App\Services\LabelSystem\Barcodes\BarcodeSourceType; +use App\Services\LabelSystem\BarcodeScanner\BarcodeRedirector; +use App\Services\LabelSystem\BarcodeScanner\BarcodeScanHelper; +use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType; +use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult; use Doctrine\ORM\EntityNotFoundException; use InvalidArgumentException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -77,13 +77,21 @@ class ScanController extends AbstractController $mode = $form['mode']->getData(); } + $infoModeData = null; + if ($input !== null) { try { $scan_result = $this->barcodeNormalizer->scanBarcodeContent($input, $mode ?? null); - try { - return $this->redirect($this->barcodeParser->getRedirectURL($scan_result)); - } catch (EntityNotFoundException) { - $this->addFlash('success', 'scan.qr_not_found'); + //Perform a redirect if the info mode is not enabled + if (!$form['info_mode']->getData()) { + try { + return $this->redirect($this->barcodeParser->getRedirectURL($scan_result)); + } catch (EntityNotFoundException) { + $this->addFlash('success', 'scan.qr_not_found'); + } + } else { //Otherwise retrieve infoModeData + $infoModeData = $scan_result->getDecodedForInfoMode(); + } } catch (InvalidArgumentException) { $this->addFlash('error', 'scan.format_unknown'); @@ -92,6 +100,7 @@ class ScanController extends AbstractController return $this->render('label_system/scanner/scanner.html.twig', [ 'form' => $form, + 'infoModeData' => $infoModeData, ]); } @@ -109,7 +118,7 @@ class ScanController extends AbstractController throw new InvalidArgumentException('Unknown type: '.$type); } //Construct the scan result manually, as we don't have a barcode here - $scan_result = new BarcodeScanResult( + $scan_result = new LocalBarcodeScanResult( target_type: BarcodeScanHelper::QR_TYPE_MAP[$type], target_id: $id, //The routes are only used on the internal generated QR codes diff --git a/src/DataFixtures/PartFixtures.php b/src/DataFixtures/PartFixtures.php index a9290cbf..0c8ea36d 100644 --- a/src/DataFixtures/PartFixtures.php +++ b/src/DataFixtures/PartFixtures.php @@ -106,7 +106,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface $partLot2->setComment('Test'); $partLot2->setNeedsRefill(true); $partLot2->setStorageLocation($manager->find(StorageLocation::class, 3)); - $partLot2->setVendorBarcode('lot2_vendor_barcode'); + $partLot2->setUserBarcode('lot2_vendor_barcode'); $part->addPartLot($partLot2); $orderdetail = new Orderdetail(); diff --git a/src/Entity/Parts/PartLot.php b/src/Entity/Parts/PartLot.php index c1b7da8a..d893e6de 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -68,7 +68,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ORM\Index(columns: ['needs_refill'], name: 'part_lots_idx_needs_refill')] #[ORM\Index(columns: ['vendor_barcode'], name: 'part_lots_idx_barcode')] #[ValidPartLot] -#[UniqueEntity(['vendor_barcode'], message: 'validator.part_lot.vendor_barcode_must_be_unique')] +#[UniqueEntity(['user_barcode'], message: 'validator.part_lot.vendor_barcode_must_be_unique')] #[ApiResource( operations: [ new Get(security: 'is_granted("read", object)'), @@ -166,10 +166,10 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * @var string|null The content of the barcode of this part lot (e.g. a barcode on the package put by the vendor) */ - #[ORM\Column(type: Types::STRING, nullable: true)] + #[ORM\Column(name: "vendor_barcode", type: Types::STRING, nullable: true)] #[Groups(['part_lot:read', 'part_lot:write'])] #[Length(max: 255)] - protected ?string $vendor_barcode = null; + protected ?string $user_barcode = null; public function __clone() { @@ -375,19 +375,19 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named * null if no barcode is set. * @return string|null */ - public function getVendorBarcode(): ?string + public function getUserBarcode(): ?string { - return $this->vendor_barcode; + return $this->user_barcode; } /** * Set the content of the barcode of this part lot (e.g. a barcode on the package put by the vendor). - * @param string|null $vendor_barcode + * @param string|null $user_barcode * @return $this */ - public function setVendorBarcode(?string $vendor_barcode): PartLot + public function setUserBarcode(?string $user_barcode): PartLot { - $this->vendor_barcode = $vendor_barcode; + $this->user_barcode = $user_barcode; return $this; } diff --git a/src/EventSubscriber/WebpackAutoPathSubscriber.php b/src/EventSubscriber/WebpackAutoPathSubscriber.php deleted file mode 100644 index f040a646..00000000 --- a/src/EventSubscriber/WebpackAutoPathSubscriber.php +++ /dev/null @@ -1,57 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\EventSubscriber; - -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\WebpackEncoreBundle\Event\RenderAssetTagEvent; - -/** - * This class fixes the wrong pathes generated by webpack using the auto publicPath mode. - * Basically it replaces the wrong /auto/ part of the path with the correct /build/ in all encore entrypoints. - */ -class WebpackAutoPathSubscriber implements EventSubscriberInterface -{ - public static function getSubscribedEvents(): array - { - return [ - RenderAssetTagEvent::class => 'onRenderAssetTag' - ]; - } - - public function onRenderAssetTag(RenderAssetTagEvent $event): void - { - if ($event->isScriptTag()) { - $event->setAttribute('src', $this->resolveAuto($event->getUrl())); - } - if ($event->isLinkTag()) { - $event->setAttribute('href', $this->resolveAuto($event->getUrl())); - } - } - - private function resolveAuto(string $path): string - { - //Replace the first occurence of /auto/ with /build/ to get the correct path - return preg_replace('/\/auto\//', '/build/', $path, 1); - } -} \ No newline at end of file diff --git a/src/Form/LabelSystem/LabelDialogType.php b/src/Form/LabelSystem/LabelDialogType.php index 33c79797..f2710b19 100644 --- a/src/Form/LabelSystem/LabelDialogType.php +++ b/src/Form/LabelSystem/LabelDialogType.php @@ -71,6 +71,22 @@ class LabelDialogType extends AbstractType 'label' => false, 'disabled' => !$this->security->isGranted('@labels.edit_options') || $options['disable_options'], ]); + + $builder->add('save_profile_name', TextType::class, [ + 'required' => false, + 'attr' =>[ + 'placeholder' => 'label_generator.save_profile_name', + ] + ]); + + $builder->add('save_profile', SubmitType::class, [ + 'label' => 'label_generator.save_profile', + 'disabled' => !$this->security->isGranted('@labels.create_profiles'), + 'attr' => [ + 'class' => 'btn btn-outline-success' + ] + ]); + $builder->add('update', SubmitType::class, [ 'label' => 'label_generator.update', ]); diff --git a/src/Form/LabelSystem/ScanDialogType.php b/src/Form/LabelSystem/ScanDialogType.php index 5230dd5a..13ff8e6f 100644 --- a/src/Form/LabelSystem/ScanDialogType.php +++ b/src/Form/LabelSystem/ScanDialogType.php @@ -41,8 +41,9 @@ declare(strict_types=1); namespace App\Form\LabelSystem; -use App\Services\LabelSystem\Barcodes\BarcodeSourceType; +use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; @@ -55,6 +56,8 @@ class ScanDialogType extends AbstractType { $builder->add('input', TextType::class, [ 'label' => 'scan_dialog.input', + //Do not trim the input, otherwise this damages Format06 barcodes which end with non-printable characters + 'trim' => false, 'attr' => [ 'autofocus' => true, 'id' => 'scan_dialog_input', @@ -71,9 +74,14 @@ class ScanDialogType extends AbstractType null => 'scan_dialog.mode.auto', BarcodeSourceType::INTERNAL => 'scan_dialog.mode.internal', BarcodeSourceType::IPN => 'scan_dialog.mode.ipn', - BarcodeSourceType::VENDOR => 'scan_dialog.mode.vendor', + BarcodeSourceType::USER_DEFINED => 'scan_dialog.mode.user', + BarcodeSourceType::EIGP114 => 'scan_dialog.mode.eigp' }, + ]); + $builder->add('info_mode', CheckboxType::class, [ + 'label' => 'scan_dialog.info_mode', + 'required' => false, ]); $builder->add('submit', SubmitType::class, [ diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index 4a71f03a..b1d2ebea 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -101,6 +101,8 @@ class PartBaseType extends AbstractType 'dto_value' => $dto?->category, 'label' => 'part.edit.category', 'disable_not_selectable' => true, + //Do not require category for new parts, so that the user must select the category by hand and cannot forget it (the requirement is handled by the constraint in the entity) + 'required' => !$new_part, ]) ->add('footprint', StructuralEntityType::class, [ 'class' => Footprint::class, diff --git a/src/Form/Part/PartLotType.php b/src/Form/Part/PartLotType.php index da061c7f..7d545340 100644 --- a/src/Form/Part/PartLotType.php +++ b/src/Form/Part/PartLotType.php @@ -103,10 +103,12 @@ class PartLotType extends AbstractType 'help' => 'part_lot.owner.help', ]); - $builder->add('vendor_barcode', TextType::class, [ - 'label' => 'part_lot.edit.vendor_barcode', + $builder->add('user_barcode', TextType::class, [ + 'label' => 'part_lot.edit.user_barcode', 'help' => 'part_lot.edit.vendor_barcode.help', 'required' => false, + //Do not remove whitespace chars on the beginning and end of the string + 'trim' => false, ]); } diff --git a/src/Serializer/PartNormalizer.php b/src/Serializer/PartNormalizer.php index 650b0214..e527548f 100644 --- a/src/Serializer/PartNormalizer.php +++ b/src/Serializer/PartNormalizer.php @@ -94,7 +94,14 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Norm public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { - return !isset($context[self::ALREADY_CALLED]) && is_array($data) && is_a($type, Part::class, true); + //Only denormalize if we are doing a file import operation + if (!($context['partdb_import'] ?? false)) { + return false; + } + + //Only make the denormalizer available on import operations + return !isset($context[self::ALREADY_CALLED]) + && is_array($data) && is_a($type, Part::class, true); } private function normalizeKeys(array &$data): array diff --git a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php index 215e4355..3c1e4cca 100644 --- a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php +++ b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php @@ -37,6 +37,7 @@ use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; +use App\Repository\Parts\CategoryRepository; use App\Services\InfoProviderSystem\DTOs\FileDTO; use App\Services\InfoProviderSystem\DTOs\ParameterDTO; use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; @@ -158,7 +159,10 @@ final class DTOtoEntityConverter $entity->setMass($dto->mass); //Try to map the category to an existing entity (but never create a new one) - $entity->setCategory($this->em->getRepository(Category::class)->findForInfoProvider($dto->category)); + if ($dto->category) { + //@phpstan-ignore-next-line For some reason php does not recognize the repo returns a category + $entity->setCategory($this->em->getRepository(Category::class)->findForInfoProvider($dto->category)); + } $entity->setManufacturer($this->getOrCreateEntity(Manufacturer::class, $dto->manufacturer)); $entity->setFootprint($this->getOrCreateEntity(Footprint::class, $dto->footprint)); diff --git a/src/Services/InfoProviderSystem/Providers/MouserProvider.php b/src/Services/InfoProviderSystem/Providers/MouserProvider.php index 84f5d65b..c36fab66 100644 --- a/src/Services/InfoProviderSystem/Providers/MouserProvider.php +++ b/src/Services/InfoProviderSystem/Providers/MouserProvider.php @@ -205,7 +205,7 @@ class MouserProvider implements InfoProviderInterface if (isset($arr['SearchResults'])) { $products = $arr['SearchResults']['Parts'] ?? []; } else { - throw new \RuntimeException('Unknown response format'); + throw new \RuntimeException('Unknown response format: ' .json_encode($arr, JSON_THROW_ON_ERROR)); } $result = []; diff --git a/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php new file mode 100644 index 00000000..2de7c035 --- /dev/null +++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php @@ -0,0 +1,166 @@ +. + */ + +declare(strict_types=1); + +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace App\Services\LabelSystem\BarcodeScanner; + +use App\Entity\LabelSystem\LabelSupportedElement; +use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityNotFoundException; +use InvalidArgumentException; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeRedirectorTest + */ +final class BarcodeRedirector +{ + public function __construct(private readonly UrlGeneratorInterface $urlGenerator, private readonly EntityManagerInterface $em) + { + } + + /** + * Determines the URL to which the user should be redirected, when scanning a QR code. + * + * @param BarcodeScanResultInterface $barcodeScan The result of the barcode scan + * @return string the URL to which should be redirected + * + * @throws EntityNotFoundException + */ + public function getRedirectURL(BarcodeScanResultInterface $barcodeScan): string + { + if($barcodeScan instanceof LocalBarcodeScanResult) { + return $this->getURLLocalBarcode($barcodeScan); + } + + if ($barcodeScan instanceof EIGP114BarcodeScanResult) { + return $this->getURLVendorBarcode($barcodeScan); + } + + throw new InvalidArgumentException('Unknown $barcodeScan type: '.get_class($barcodeScan)); + } + + private function getURLLocalBarcode(LocalBarcodeScanResult $barcodeScan): string + { + switch ($barcodeScan->target_type) { + case LabelSupportedElement::PART: + return $this->urlGenerator->generate('app_part_show', ['id' => $barcodeScan->target_id]); + case LabelSupportedElement::PART_LOT: + //Try to determine the part to the given lot + $lot = $this->em->find(PartLot::class, $barcodeScan->target_id); + if (!$lot instanceof PartLot) { + throw new EntityNotFoundException(); + } + + return $this->urlGenerator->generate('app_part_show', ['id' => $lot->getPart()->getID()]); + + case LabelSupportedElement::STORELOCATION: + return $this->urlGenerator->generate('part_list_store_location', ['id' => $barcodeScan->target_id]); + + default: + throw new InvalidArgumentException('Unknown $type: '.$barcodeScan->target_type->name); + } + } + + /** + * Gets the URL to a part from a scan of a Vendor Barcode + */ + private function getURLVendorBarcode(EIGP114BarcodeScanResult $barcodeScan): string + { + $part = $this->getPartFromVendor($barcodeScan); + return $this->urlGenerator->generate('app_part_show', ['id' => $part->getID()]); + } + + /** + * Gets a part from a scan of a Vendor Barcode by filtering for parts + * with the same Info Provider Id or, if that fails, by looking for parts with a + * matching manufacturer product number. Only returns the first matching part. + */ + private function getPartFromVendor(EIGP114BarcodeScanResult $barcodeScan) : Part + { + // first check via the info provider ID (e.g. Vendor ID). This might fail if the part was not added via + // the info provider system or if the part was bought from a different vendor than the data was retrieved + // from. + if($barcodeScan->digikeyPartNumber) { + $qb = $this->em->getRepository(Part::class)->createQueryBuilder('part'); + //Lower() to be case insensitive + $qb->where($qb->expr()->like('LOWER(part.providerReference.provider_id)', 'LOWER(:vendor_id)')); + $qb->setParameter('vendor_id', $barcodeScan->digikeyPartNumber); + $results = $qb->getQuery()->getResult(); + if ($results) { + return $results[0]; + } + } + + if(!$barcodeScan->supplierPartNumber){ + throw new EntityNotFoundException(); + } + + //Fallback to the manufacturer part number. This may return false positives, since it is common for + //multiple manufacturers to use the same part number for their version of a common product + //We assume the user is able to realize when this returns the wrong part + //If the barcode specifies the manufacturer we try to use that as well + $mpnQb = $this->em->getRepository(Part::class)->createQueryBuilder('part'); + $mpnQb->where($mpnQb->expr()->like('LOWER(part.manufacturer_product_number)', 'LOWER(:mpn)')); + $mpnQb->setParameter('mpn', $barcodeScan->supplierPartNumber); + + if($barcodeScan->mouserManufacturer){ + $manufacturerQb = $this->em->getRepository(Manufacturer::class)->createQueryBuilder("manufacturer"); + $manufacturerQb->where($manufacturerQb->expr()->like("LOWER(manufacturer.name)", "LOWER(:manufacturer_name)")); + $manufacturerQb->setParameter("manufacturer_name", $barcodeScan->mouserManufacturer); + $manufacturers = $manufacturerQb->getQuery()->getResult(); + + if($manufacturers) { + $mpnQb->andWhere($mpnQb->expr()->eq("part.manufacturer", ":manufacturer")); + $mpnQb->setParameter("manufacturer", $manufacturers); + } + + } + + $results = $mpnQb->getQuery()->getResult(); + if($results){ + return $results[0]; + } + throw new EntityNotFoundException(); + } +} diff --git a/src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanHelper.php similarity index 81% rename from src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php rename to src/Services/LabelSystem/BarcodeScanner/BarcodeScanHelper.php index c9750ea3..e5930b36 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php +++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanHelper.php @@ -39,7 +39,7 @@ declare(strict_types=1); * along with this program. If not, see . */ -namespace App\Services\LabelSystem\Barcodes; +namespace App\Services\LabelSystem\BarcodeScanner; use App\Entity\LabelSystem\LabelSupportedElement; use App\Entity\Parts\Part; @@ -75,20 +75,23 @@ final class BarcodeScanHelper * will try to guess the type. * @param string $input * @param BarcodeSourceType|null $type - * @return BarcodeScanResult + * @return BarcodeScanResultInterface */ - public function scanBarcodeContent(string $input, ?BarcodeSourceType $type = null): BarcodeScanResult + public function scanBarcodeContent(string $input, ?BarcodeSourceType $type = null): BarcodeScanResultInterface { //Do specific parsing if ($type === BarcodeSourceType::INTERNAL) { return $this->parseInternalBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode'); } - if ($type === BarcodeSourceType::VENDOR) { - return $this->parseVendorBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode'); + if ($type === BarcodeSourceType::USER_DEFINED) { + return $this->parseUserDefinedBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode'); } if ($type === BarcodeSourceType::IPN) { return $this->parseIPNBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode'); } + if ($type === BarcodeSourceType::EIGP114) { + return $this->parseEIGP114Barcode($input); + } //Null means auto and we try the different formats $result = $this->parseInternalBarcode($input); @@ -97,12 +100,17 @@ final class BarcodeScanHelper return $result; } - //Try to parse as vendor barcode - $result = $this->parseVendorBarcode($input); + //Try to parse as User defined barcode + $result = $this->parseUserDefinedBarcode($input); if ($result !== null) { return $result; } + //If the barcode is formatted as EIGP114, we can parse it directly + if (EIGP114BarcodeScanResult::isFormat06Code($input)) { + return $this->parseEIGP114Barcode($input); + } + //Try to parse as IPN barcode $result = $this->parseIPNBarcode($input); if ($result !== null) { @@ -112,11 +120,16 @@ final class BarcodeScanHelper throw new InvalidArgumentException('Unknown barcode'); } - private function parseVendorBarcode(string $input): ?BarcodeScanResult + private function parseEIGP114Barcode(string $input): EIGP114BarcodeScanResult + { + return EIGP114BarcodeScanResult::parseFormat06Code($input); + } + + private function parseUserDefinedBarcode(string $input): ?LocalBarcodeScanResult { $lot_repo = $this->entityManager->getRepository(PartLot::class); //Find only the first result - $results = $lot_repo->findBy(['vendor_barcode' => $input], limit: 1); + $results = $lot_repo->findBy(['user_barcode' => $input], limit: 1); if (count($results) === 0) { return null; @@ -124,14 +137,14 @@ final class BarcodeScanHelper //We found a part, so use it to create the result $lot = $results[0]; - return new BarcodeScanResult( + return new LocalBarcodeScanResult( target_type: LabelSupportedElement::PART_LOT, target_id: $lot->getID(), - source_type: BarcodeSourceType::VENDOR + source_type: BarcodeSourceType::USER_DEFINED ); } - private function parseIPNBarcode(string $input): ?BarcodeScanResult + private function parseIPNBarcode(string $input): ?LocalBarcodeScanResult { $part_repo = $this->entityManager->getRepository(Part::class); //Find only the first result @@ -143,7 +156,7 @@ final class BarcodeScanHelper //We found a part, so use it to create the result $part = $results[0]; - return new BarcodeScanResult( + return new LocalBarcodeScanResult( target_type: LabelSupportedElement::PART, target_id: $part->getID(), source_type: BarcodeSourceType::IPN @@ -155,9 +168,9 @@ final class BarcodeScanHelper * If the barcode could not be parsed at all, null is returned. If the barcode is a valid format, but could * not be found in the database, an exception is thrown. * @param string $input - * @return BarcodeScanResult|null + * @return LocalBarcodeScanResult|null */ - private function parseInternalBarcode(string $input): ?BarcodeScanResult + private function parseInternalBarcode(string $input): ?LocalBarcodeScanResult { $input = trim($input); $matches = []; @@ -167,7 +180,7 @@ final class BarcodeScanHelper //Extract parts from QR code's URL if (preg_match('#^https?://.*/scan/(\w+)/(\d+)/?$#', $input, $matches)) { - return new BarcodeScanResult( + return new LocalBarcodeScanResult( target_type: self::QR_TYPE_MAP[strtolower($matches[1])], target_id: (int) $matches[2], source_type: BarcodeSourceType::INTERNAL @@ -183,7 +196,7 @@ final class BarcodeScanHelper throw new InvalidArgumentException('Unknown prefix '.$prefix); } - return new BarcodeScanResult( + return new LocalBarcodeScanResult( target_type: self::PREFIX_TYPE_MAP[$prefix], target_id: $id, source_type: BarcodeSourceType::INTERNAL @@ -199,7 +212,7 @@ final class BarcodeScanHelper throw new InvalidArgumentException('Unknown prefix '.$prefix); } - return new BarcodeScanResult( + return new LocalBarcodeScanResult( target_type: self::PREFIX_TYPE_MAP[$prefix], target_id: $id, source_type: BarcodeSourceType::INTERNAL @@ -208,7 +221,7 @@ final class BarcodeScanHelper //Legacy Part-DB location labels used $L00336 format if (preg_match('#^\$L(\d{5,})$#', $input, $matches)) { - return new BarcodeScanResult( + return new LocalBarcodeScanResult( target_type: LabelSupportedElement::STORELOCATION, target_id: (int) $matches[1], source_type: BarcodeSourceType::INTERNAL @@ -217,7 +230,7 @@ final class BarcodeScanHelper //Legacy Part-DB used EAN8 barcodes for part labels. Format 0000001(2) (note the optional 8th digit => checksum) if (preg_match('#^(\d{7})\d?$#', $input, $matches)) { - return new BarcodeScanResult( + return new LocalBarcodeScanResult( target_type: LabelSupportedElement::PART, target_id: (int) $matches[1], source_type: BarcodeSourceType::INTERNAL diff --git a/src/Services/LabelSystem/BarcodeScanner/BarcodeScanResultInterface.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanResultInterface.php new file mode 100644 index 00000000..88130351 --- /dev/null +++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanResultInterface.php @@ -0,0 +1,36 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\LabelSystem\BarcodeScanner; + +interface BarcodeScanResultInterface +{ + /** + * Returns all data that was decoded from the barcode in a format, that can be shown in a table to the user. + * The return values of this function are not meant to be parsed by code again, but should just give a information + * to the user. + * The keys of the returned array are the first column of the table and the values are the second column. + * @return array + */ + public function getDecodedForInfoMode(): array; +} \ No newline at end of file diff --git a/src/Services/LabelSystem/Barcodes/BarcodeSourceType.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeSourceType.php similarity index 83% rename from src/Services/LabelSystem/Barcodes/BarcodeSourceType.php rename to src/Services/LabelSystem/BarcodeScanner/BarcodeSourceType.php index 20ba316a..40f707de 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeSourceType.php +++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeSourceType.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace App\Services\LabelSystem\Barcodes; +namespace App\Services\LabelSystem\BarcodeScanner; /** * This enum represents the different types, where a barcode/QR-code can be generated from @@ -32,9 +32,14 @@ enum BarcodeSourceType case INTERNAL; /** This barcode is containing an internal part number (IPN) */ case IPN; + /** - * This barcode is a custom barcode from a third party like a vendor, which was set via the vendor_barcode - * field of a part lot. + * This barcode is a user defined barcode defined on a part lot */ - case VENDOR; + case USER_DEFINED; + + /** + * EIGP114 formatted barcodes like used by digikey, mouser, etc. + */ + case EIGP114; } \ No newline at end of file diff --git a/src/Services/LabelSystem/BarcodeScanner/EIGP114BarcodeScanResult.php b/src/Services/LabelSystem/BarcodeScanner/EIGP114BarcodeScanResult.php new file mode 100644 index 00000000..0b4f4b56 --- /dev/null +++ b/src/Services/LabelSystem/BarcodeScanner/EIGP114BarcodeScanResult.php @@ -0,0 +1,332 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\LabelSystem\BarcodeScanner; + +/** + * This class represents the content of a EIGP114 barcode. + * Based on PR 811, EIGP 114.2018 (https://www.ecianow.org/assets/docs/GIPC/EIGP-114.2018%20ECIA%20Labeling%20Specification%20for%20Product%20and%20Shipment%20Identification%20in%20the%20Electronics%20Industry%20-%202D%20Barcode.pdf), + * , https://forum.digikey.com/t/digikey-product-labels-decoding-digikey-barcodes/41097 + */ +class EIGP114BarcodeScanResult implements BarcodeScanResultInterface +{ + + /** + * @var string|null Ship date in format YYYYMMDD + */ + public readonly ?string $shipDate; + + /** + * @var string|null Customer assigned part number – Optional based on + * agreements between Distributor and Supplier + */ + public readonly ?string $customerPartNumber; + + /** + * @var string|null Supplier assigned part number + */ + public readonly ?string $supplierPartNumber; + + /** + * @var int|null Quantity of product + */ + public readonly ?int $quantity; + + /** + * @var string|null Customer assigned purchase order number + */ + public readonly ?string $customerPO; + + /** + * @var string|null Line item number from PO. Required on Logistic Label when + * used on back of Packing Slip. See Section 4.9 + */ + public readonly ?string $customerPOLine; + + /** + * 9D - YYWW (Year and Week of Manufacture). ) If no date code is used + * for a particular part, this field should be populated with N/T + * to indicate the product is Not Traceable by this data field. + * @var string|null + */ + public readonly ?string $dateCode; + + /** + * 10D - YYWW (Year and Week of Manufacture). ) If no date code is used + * for a particular part, this field should be populated with N/T + * to indicate the product is Not Traceable by this data field. + * @var string|null + */ + public readonly ?string $alternativeDateCode; + + /** + * Traceability number assigned to a batch or group of items. If + * no lot code is used for a particular part, this field should be + * populated with N/T to indicate the product is Not Traceable + * by this data field. + * @var string|null + */ + public readonly ?string $lotCode; + + /** + * Country where part was manufactured. Two-letter code from + * ISO 3166 country code list + * @var string|null + */ + public readonly ?string $countryOfOrigin; + + /** + * @var string|null Unique alphanumeric number assigned by supplier + * 3S - Package ID for Inner Pack when part of a mixed Logistic + * Carton. Always used in conjunction with a mixed logistic label + * with a 5S data identifier for Package ID. + */ + public readonly ?string $packageId1; + + /** + * @var string|null + * 4S - Package ID for Logistic Carton with like items + */ + public readonly ?string $packageId2; + + /** + * @var string|null + * 5S - Package ID for Logistic Carton with mixed items + */ + public readonly ?string $packageId3; + + /** + * @var string|null Unique alphanumeric number assigned by supplier. + */ + public readonly ?string $packingListNumber; + + /** + * @var string|null Ship date in format YYYYMMDD + */ + public readonly ?string $serialNumber; + + /** + * @var string|null Code for sorting and classifying LEDs. Use when applicable + */ + public readonly ?string $binCode; + + /** + * @var int|null Sequential carton count in format “#/#” or “# of #” + */ + public readonly ?int $packageCount; + + /** + * @var string|null Alphanumeric string assigned by the supplier to distinguish + * from one closely-related design variation to another. Use as + * required or when applicable + */ + public readonly ?string $revisionNumber; + + /** + * @var string|null Digikey Extension: This is not represented in the ECIA spec, but the field being used is found in the ANSI MH10.8.2-2016 spec on which the ECIA spec is based. In the ANSI spec it is called First Level (Supplier Assigned) Part Number. + */ + public readonly ?string $digikeyPartNumber; + + /** + * @var string|null Digikey Extension: This can be shared across multiple invoices and time periods and is generated as an order enters our system from any vector (web, API, phone order, etc.) + */ + public readonly ?string $digikeySalesOrderNumber; + + /** + * @var string|null Digikey extension: This is typically assigned per shipment as items are being released to be picked in the warehouse. A SO can have many Invoice numbers + */ + public readonly ?string $digikeyInvoiceNumber; + + /** + * @var string|null Digikey extension: This is for internal DigiKey purposes and defines the label type. + */ + public readonly ?string $digikeyLabelType; + + /** + * @var string|null You will also see this as the last part of a URL for a product detail page. Ex https://www.digikey.com/en/products/detail/w%C3%BCrth-elektronik/860010672008/5726907 + */ + public readonly ?string $digikeyPartID; + + /** + * @var string|null Digikey Extension: For internal use of Digikey. Probably not needed + */ + public readonly ?string $digikeyNA; + + /** + * @var string|null Digikey Extension: This is a field of varying length used to keep the barcode approximately the same size between labels. It is safe to ignore. + */ + public readonly ?string $digikeyPadding; + + public readonly ?string $mouserPositionInOrder; + + public readonly ?string $mouserManufacturer; + + + + /** + * + * @param array $data The fields of the EIGP114 barcode, where the key is the field name and the value is the field content + */ + public function __construct(public readonly array $data) + { + //IDs per EIGP 114.2018 + $this->shipDate = $data['6D'] ?? null; + $this->customerPartNumber = $data['P'] ?? null; + $this->supplierPartNumber = $data['1P'] ?? null; + $this->quantity = isset($data['Q']) ? (int)$data['Q'] : null; + $this->customerPO = $data['K'] ?? null; + $this->customerPOLine = $data['4K'] ?? null; + $this->dateCode = $data['9D'] ?? null; + $this->alternativeDateCode = $data['10D'] ?? null; + $this->lotCode = $data['1T'] ?? null; + $this->countryOfOrigin = $data['4L'] ?? null; + $this->packageId1 = $data['3S'] ?? null; + $this->packageId2 = $data['4S'] ?? null; + $this->packageId3 = $data['5S'] ?? null; + $this->packingListNumber = $data['11K'] ?? null; + $this->serialNumber = $data['S'] ?? null; + $this->binCode = $data['33P'] ?? null; + $this->packageCount = isset($data['13Q']) ? (int)$data['13Q'] : null; + $this->revisionNumber = $data['2P'] ?? null; + //IDs used by Digikey + $this->digikeyPartNumber = $data['30P'] ?? null; + $this->digikeySalesOrderNumber = $data['1K'] ?? null; + $this->digikeyInvoiceNumber = $data['10K'] ?? null; + $this->digikeyLabelType = $data['11Z'] ?? null; + $this->digikeyPartID = $data['12Z'] ?? null; + $this->digikeyNA = $data['13Z'] ?? null; + $this->digikeyPadding = $data['20Z'] ?? null; + //IDs used by Mouser + $this->mouserPositionInOrder = $data['14K'] ?? null; + $this->mouserManufacturer = $data['1V'] ?? null; + } + + /** + * Tries to guess the vendor of the barcode based on the supplied data field. + * This is experimental and should not be relied upon. + * @return string|null The guessed vendor as smallcase string (e.g. "digikey", "mouser", etc.), or null if the vendor could not be guessed + */ + public function guessBarcodeVendor(): ?string + { + //If the barcode data contains the digikey extensions, we assume it is a digikey barcode + if (isset($this->data['13Z']) || isset($this->data['20Z']) || isset($this->data['12Z']) || isset($this->data['11Z'])) { + return 'digikey'; + } + + //If the barcode data contains the mouser extensions, we assume it is a mouser barcode + if (isset($this->data['14K']) || isset($this->data['1V'])) { + return 'mouser'; + } + + //According to this thread (https://github.com/inventree/InvenTree/issues/853), Newark/element14 codes contains a "3P" field + if (isset($this->data['3P'])) { + return 'element14'; + } + + return null; + } + + /** + * Checks if the given input is a valid format06 formatted data. + * This just perform a simple check for the header, the content might be malformed still. + * @param string $input + * @return bool + */ + public static function isFormat06Code(string $input): bool + { + //Code must begin with [)>06 + if(!str_starts_with($input, "[)>\u{1E}06\u{1D}")){ + return false; + } + + //Digikey does not put a trailer onto the barcode, so we just check for the header + + return true; + } + + /** + * Parses a format06 code a returns a new instance of this class + * @param string $input + * @return self + */ + public static function parseFormat06Code(string $input): self + { + //Ensure that the input is a valid format06 code + if (!self::isFormat06Code($input)) { + throw new \InvalidArgumentException("The given input is not a valid format06 code"); + } + + //Remove the trailer, if present + if (str_ends_with($input, "\u{1E}\u{04}")){ + $input = substr($input, 5, -2); + } + + //Split the input into the different fields (using the separator) + $parts = explode("\u{1D}", $input); + + //The first field is the format identifier, which we do not need + array_shift($parts); + + //Split the fields into key-value pairs + $results = []; + + foreach($parts as $part) { + //^ 0* ([1-9]? \d* [A-Z]) + //Start of the string Leading zeros are discarded Not a zero Any number of digits single uppercase Letter + // 00 1 4 K + + if(!preg_match('/^0*([1-9]?\d*[A-Z])/', $part, $matches)) { + throw new \LogicException("Could not parse field: $part"); + } + //Extract the key + $key = $matches[0]; + //Extract the field value + $fieldValue = substr($part, strlen($matches[0])); + + $results[$key] = $fieldValue; + } + + return new self($results); + } + + public function getDecodedForInfoMode(): array + { + $tmp = [ + 'Barcode type' => 'EIGP114', + 'Guessed vendor from barcode' => $this->guessBarcodeVendor() ?? 'Unknown', + ]; + + //Iterate over all fields of this object and add them to the array if they are not null + foreach((array) $this as $key => $value) { + //Skip data key + if ($key === 'data') { + continue; + } + if($value !== null) { + $tmp[$key] = $value; + } + } + + return $tmp; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/Barcodes/BarcodeScanResult.php b/src/Services/LabelSystem/BarcodeScanner/LocalBarcodeScanResult.php similarity index 66% rename from src/Services/LabelSystem/Barcodes/BarcodeScanResult.php rename to src/Services/LabelSystem/BarcodeScanner/LocalBarcodeScanResult.php index 7f1315b3..050aff6f 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeScanResult.php +++ b/src/Services/LabelSystem/BarcodeScanner/LocalBarcodeScanResult.php @@ -21,14 +21,15 @@ declare(strict_types=1); -namespace App\Services\LabelSystem\Barcodes; +namespace App\Services\LabelSystem\BarcodeScanner; use App\Entity\LabelSystem\LabelSupportedElement; /** - * This class represents the result of a barcode scan, with the target type and the ID of the element + * This class represents the result of a barcode scan of a barcode that uniquely identifies a local entity, + * like an internally generated barcode or a barcode that was added manually to the system by a user */ -class BarcodeScanResult +class LocalBarcodeScanResult implements BarcodeScanResultInterface { public function __construct( public readonly LabelSupportedElement $target_type, @@ -36,4 +37,13 @@ class BarcodeScanResult public readonly BarcodeSourceType $source_type, ) { } + + public function getDecodedForInfoMode(): array + { + return [ + 'Barcode type' => $this->source_type->name, + 'Target type' => $this->target_type->name, + 'Target ID' => $this->target_id, + ]; + } } \ No newline at end of file diff --git a/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php b/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php deleted file mode 100644 index bc21b787..00000000 --- a/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php +++ /dev/null @@ -1,89 +0,0 @@ -. - */ - -declare(strict_types=1); - -/** - * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). - * - * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -namespace App\Services\LabelSystem\Barcodes; - -use App\Entity\LabelSystem\LabelSupportedElement; -use App\Entity\Parts\PartLot; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\EntityNotFoundException; -use InvalidArgumentException; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; - -/** - * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeRedirectorTest - */ -final class BarcodeRedirector -{ - public function __construct(private readonly UrlGeneratorInterface $urlGenerator, private readonly EntityManagerInterface $em) - { - } - - /** - * Determines the URL to which the user should be redirected, when scanning a QR code. - * - * @param BarcodeScanResult $barcodeScan The result of the barcode scan - * @return string the URL to which should be redirected - * - * @throws EntityNotFoundException - */ - public function getRedirectURL(BarcodeScanResult $barcodeScan): string - { - switch ($barcodeScan->target_type) { - case LabelSupportedElement::PART: - return $this->urlGenerator->generate('app_part_show', ['id' => $barcodeScan->target_id]); - case LabelSupportedElement::PART_LOT: - //Try to determine the part to the given lot - $lot = $this->em->find(PartLot::class, $barcodeScan->target_id); - if (!$lot instanceof PartLot) { - throw new EntityNotFoundException(); - } - - return $this->urlGenerator->generate('app_part_show', ['id' => $lot->getPart()->getID()]); - - case LabelSupportedElement::STORELOCATION: - return $this->urlGenerator->generate('part_list_store_location', ['id' => $barcodeScan->target_id]); - - default: - throw new InvalidArgumentException('Unknown $type: '.$barcodeScan->target_type->name); - } - } -} diff --git a/symfony.lock b/symfony.lock index ac622cb0..c7471b73 100644 --- a/symfony.lock +++ b/symfony.lock @@ -408,15 +408,15 @@ "version": "v4.2.3" }, "symfony/console": { - "version": "5.3", + "version": "6.4", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", + "branch": "main", "version": "5.3", - "ref": "da0c8be8157600ad34f10ff0c9cc91232522e047" + "ref": "1781ff40d8a17d87cf53f8d4cf0c8346ed2bb461" }, "files": [ - "./bin/console" + "bin/console" ] }, "symfony/css-selector": { @@ -468,15 +468,16 @@ "version": "v4.2.3" }, "symfony/flex": { - "version": "1.19", + "version": "2.4", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "1.0", - "ref": "146251ae39e06a95be0fe3d13c807bcf3938b172" + "version": "2.4", + "ref": "52e9754527a15e2b79d9a610f98185a1fe46622a" }, "files": [ - ".env" + ".env", + ".env.dev" ] }, "symfony/form": { diff --git a/templates/label_system/dialog.html.twig b/templates/label_system/dialog.html.twig index a845d12e..50db99e7 100644 --- a/templates/label_system/dialog.html.twig +++ b/templates/label_system/dialog.html.twig @@ -59,7 +59,7 @@
{{ profile.name ?? '-' }} - {% if profile %} + {% if profile and is_granted("edit", profile) %} {% endif %} @@ -91,6 +91,25 @@
+ + {% if is_granted("@labels.read_profiles") %} + + {% endif %} + +
+
+
+ {{ form_widget(form.save_profile_name) }} + {{ form_widget(form.save_profile) }} +
+ {{ form_errors(form.save_profile_name) }} +
+
+ diff --git a/templates/label_system/scanner/scanner.html.twig b/templates/label_system/scanner/scanner.html.twig index 39f4e140..1f978a9b 100644 --- a/templates/label_system/scanner/scanner.html.twig +++ b/templates/label_system/scanner/scanner.html.twig @@ -23,4 +23,22 @@ {{ form_end(form) }} + + {% if infoModeData %} +
+

{% trans %}label_scanner.decoded_info.title{% endtrans %}

+ + + + {% for key, value in infoModeData %} + + + + + {% endfor %} + +
{{ key }}{{ value }}
+ + {% endif %} + {% endblock %} diff --git a/templates/parts/edit/edit_form_styles.html.twig b/templates/parts/edit/edit_form_styles.html.twig index 6658fa80..cf9a40b9 100644 --- a/templates/parts/edit/edit_form_styles.html.twig +++ b/templates/parts/edit/edit_form_styles.html.twig @@ -108,7 +108,7 @@
{{ form_row(form.comment) }} {{ form_row(form.owner) }} - {{ form_row(form.vendor_barcode) }} + {{ form_row(form.user_barcode) }}
diff --git a/tests/Controller/RedirectControllerTest.php b/tests/Controller/RedirectControllerTest.php index 029c93f5..f62c05c3 100644 --- a/tests/Controller/RedirectControllerTest.php +++ b/tests/Controller/RedirectControllerTest.php @@ -115,6 +115,62 @@ class RedirectControllerTest extends WebTestCase $this->client->followRedirects(false); $this->client->request('GET', $input_path); - $this->assertResponseRedirects($redirect_path); + self::assertResponseRedirects($redirect_path); } + + /** + * Test if the user is redirected to the localized version of a page, based on his settings. + * We simulate the situation of a reverse proxy here, by adding a prefix to the path. + * + * @dataProvider urlAddLocaleDataProvider + * @group slow + */ + public function testAddLocaleReverseProxy(?string $user_locale, string $input_path, string $redirect_path): void + { + //Input path remains unchanged, as this is what the server receives from the proxy + + //Redirect path must contain the proxy prefix + $redirect_path = 'http://localhost'. '/proxy' . $redirect_path; + + /** @var User $user */ + $user = $this->userRepo->findOneBy(['name' => 'user']); + //Set user locale + $user->setLanguage($user_locale); + $this->em->flush(); + + $this->client->followRedirects(false); + $this->client->request('GET', $input_path, [], [], ['HTTP_X_FORWARDED_PREFIX' => '/proxy']); + self::assertResponseRedirects($redirect_path); + } + + + /** + * Test if the user is redirected to the localized version of a page, based on his settings. + * We simulate the situation of serving Part-DB in a subfolder here. + * + * @dataProvider urlAddLocaleDataProvider + * @group slow + */ + public function testAddLocaleSubfolder(?string $user_locale, string $input_path, string $redirect_path): void + { + //Prefix our path with the proxy prefix + $input_path = '/folder'.$input_path; + + //Redirect path is absolute + $redirect_path = 'http://localhost'. '/folder' . $redirect_path; + + /** @var User $user */ + $user = $this->userRepo->findOneBy(['name' => 'user']); + //Set user locale + $user->setLanguage($user_locale); + $this->em->flush(); + + $this->client->followRedirects(false); + $this->client->request('GET', $input_path, [], [], [ + 'SCRIPT_FILENAME' => '/var/www/html/folder/public/index.php', + 'PHP_SELF' => '/folder/index.php', + ]); + self::assertResponseRedirects($redirect_path); + } + } diff --git a/tests/Serializer/PartNormalizerTest.php b/tests/Serializer/PartNormalizerTest.php index cd93d93c..9baff750 100644 --- a/tests/Serializer/PartNormalizerTest.php +++ b/tests/Serializer/PartNormalizerTest.php @@ -80,7 +80,10 @@ class PartNormalizerTest extends WebTestCase $this->assertFalse($this->service->supportsDenormalization(new \stdClass(), Part::class)); $this->assertFalse($this->service->supportsDenormalization('string', Part::class)); $this->assertFalse($this->service->supportsDenormalization(['a' => 'b'], \stdClass::class)); - $this->assertTrue($this->service->supportsDenormalization(['a' => 'b'], Part::class)); + + //Only support denormalization, if CSV import + $this->assertFalse($this->service->supportsDenormalization(['a' => 'b'], Part::class)); + $this->assertTrue($this->service->supportsDenormalization(['a' => 'b'], Part::class, null, ['partdb_import' => true])); } public function testDenormalize(): void diff --git a/tests/Services/EntityMergers/Mergers/PartMergerTest.php b/tests/Services/EntityMergers/Mergers/PartMergerTest.php index fc60ca23..bf02744a 100644 --- a/tests/Services/EntityMergers/Mergers/PartMergerTest.php +++ b/tests/Services/EntityMergers/Mergers/PartMergerTest.php @@ -148,7 +148,7 @@ class PartMergerTest extends KernelTestCase public function testMergeOfPartLots(): void { $lot1 = (new PartLot())->setAmount(2)->setNeedsRefill(true); - $lot2 = (new PartLot())->setInstockUnknown(true)->setVendorBarcode('test'); + $lot2 = (new PartLot())->setInstockUnknown(true)->setUserBarcode('test'); $lot3 = (new PartLot())->setDescription('lot3')->setAmount(3); $lot4 = (new PartLot())->setDescription('lot4')->setComment('comment'); diff --git a/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php b/tests/Services/LabelSystem/BarcodeScanner/BarcodeRedirectorTest.php similarity index 76% rename from tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php rename to tests/Services/LabelSystem/BarcodeScanner/BarcodeRedirectorTest.php index b2b94bab..58030f93 100644 --- a/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php +++ b/tests/Services/LabelSystem/BarcodeScanner/BarcodeRedirectorTest.php @@ -39,12 +39,12 @@ declare(strict_types=1); * along with this program. If not, see . */ -namespace App\Tests\Services\LabelSystem\Barcodes; +namespace App\Tests\Services\LabelSystem\BarcodeScanner; use App\Entity\LabelSystem\LabelSupportedElement; -use App\Services\LabelSystem\Barcodes\BarcodeRedirector; -use App\Services\LabelSystem\Barcodes\BarcodeScanResult; -use App\Services\LabelSystem\Barcodes\BarcodeSourceType; +use App\Services\LabelSystem\BarcodeScanner\BarcodeRedirector; +use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType; +use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult; use Doctrine\ORM\EntityNotFoundException; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -60,17 +60,17 @@ final class BarcodeRedirectorTest extends KernelTestCase public static function urlDataProvider(): \Iterator { - yield [new BarcodeScanResult(LabelSupportedElement::PART, 1, BarcodeSourceType::INTERNAL), '/en/part/1']; + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 1, BarcodeSourceType::INTERNAL), '/en/part/1']; //Part lot redirects to Part info page (Part lot 1 is associated with part 3) - yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 1, BarcodeSourceType::INTERNAL), '/en/part/3']; - yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 1, BarcodeSourceType::INTERNAL), '/en/store_location/1/parts']; + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 1, BarcodeSourceType::INTERNAL), '/en/part/3']; + yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 1, BarcodeSourceType::INTERNAL), '/en/store_location/1/parts']; } /** * @dataProvider urlDataProvider * @group DB */ - public function testGetRedirectURL(BarcodeScanResult $scanResult, string $url): void + public function testGetRedirectURL(LocalBarcodeScanResult $scanResult, string $url): void { $this->assertSame($url, $this->service->getRedirectURL($scanResult)); } @@ -79,7 +79,7 @@ final class BarcodeRedirectorTest extends KernelTestCase { $this->expectException(EntityNotFoundException::class); //If we encounter an invalid lot, we must throw an exception - $this->service->getRedirectURL(new BarcodeScanResult(LabelSupportedElement::PART_LOT, + $this->service->getRedirectURL(new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 12_345_678, BarcodeSourceType::INTERNAL)); } } diff --git a/tests/Services/LabelSystem/Barcodes/BarcodeScanHelperTest.php b/tests/Services/LabelSystem/BarcodeScanner/BarcodeScanHelperTest.php similarity index 56% rename from tests/Services/LabelSystem/Barcodes/BarcodeScanHelperTest.php rename to tests/Services/LabelSystem/BarcodeScanner/BarcodeScanHelperTest.php index 65cb02d4..74ae068a 100644 --- a/tests/Services/LabelSystem/Barcodes/BarcodeScanHelperTest.php +++ b/tests/Services/LabelSystem/BarcodeScanner/BarcodeScanHelperTest.php @@ -39,13 +39,14 @@ declare(strict_types=1); * along with this program. If not, see . */ -namespace App\Tests\Services\LabelSystem\Barcodes; +namespace App\Tests\Services\LabelSystem\BarcodeScanner; use App\Entity\LabelSystem\LabelSupportedElement; -use App\Services\LabelSystem\Barcodes\BarcodeScanHelper; -use App\Services\LabelSystem\Barcodes\BarcodeScanResult; -use App\Services\LabelSystem\Barcodes\BarcodeSourceType; -use Com\Tecnick\Barcode\Barcode; +use App\Services\LabelSystem\BarcodeScanner\BarcodeScanHelper; +use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultInterface; +use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType; +use App\Services\LabelSystem\BarcodeScanner\EIGP114BarcodeScanResult; +use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class BarcodeScanHelperTest extends WebTestCase @@ -61,56 +62,67 @@ class BarcodeScanHelperTest extends WebTestCase public static function dataProvider(): \Iterator { //QR URL content: - yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 1, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 1, BarcodeSourceType::INTERNAL), 'https://localhost:8000/scan/lot/1']; - yield [new BarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL), 'https://localhost:8000/scan/part/123']; - yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 4, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 4, BarcodeSourceType::INTERNAL), 'http://foo.bar/part-db/scan/location/4']; //Current Code39 format: - yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL), 'L0010']; - yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 123, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 123, BarcodeSourceType::INTERNAL), 'L0123']; - yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 123456, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 123456, BarcodeSourceType::INTERNAL), 'L123456']; - yield [new BarcodeScanResult(LabelSupportedElement::PART, 2, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 2, BarcodeSourceType::INTERNAL), 'P0002']; //Development phase Code39 barcodes: - yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL), 'L-000010']; - yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL), 'Lß000010']; - yield [new BarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL), 'P-000123']; - yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 123, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 123, BarcodeSourceType::INTERNAL), 'S-000123']; - yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 12_345_678, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 12_345_678, BarcodeSourceType::INTERNAL), 'L-12345678']; //Legacy storelocation format - yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 336, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 336, BarcodeSourceType::INTERNAL), '$L00336']; - yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 12_345_678, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 12_345_678, BarcodeSourceType::INTERNAL), '$L12345678']; //Legacy Part format - yield [new BarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL), '0000123']; - yield [new BarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL), '00001236']; - yield [new BarcodeScanResult(LabelSupportedElement::PART, 1_234_567, BarcodeSourceType::INTERNAL), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 1_234_567, BarcodeSourceType::INTERNAL), '12345678']; //Test IPN barcode - yield [new BarcodeScanResult(LabelSupportedElement::PART, 2, BarcodeSourceType::IPN), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 2, BarcodeSourceType::IPN), 'IPN123']; //Test vendor barcode - yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 2,BarcodeSourceType::VENDOR), + yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 2,BarcodeSourceType::USER_DEFINED), 'lot2_vendor_barcode']; + + $eigp114Result = new EIGP114BarcodeScanResult([ + 'P' => '596-777A1-ND', + '1P' => 'XAF4444', + 'Q' => '3', + '10D' => '1452', + '1T' => 'BF1103', + '4L' => 'US', + ]); + + yield [$eigp114Result, "[)>\x1E06\x1DP596-777A1-ND\x1D1PXAF4444\x1DQ3\x1D10D1452\x1D1TBF1103\x1D4LUS\x1E\x04"]; } public static function invalidDataProvider(): \Iterator @@ -131,7 +143,7 @@ class BarcodeScanHelperTest extends WebTestCase /** * @dataProvider dataProvider */ - public function testNormalizeBarcodeContent(BarcodeScanResult $expected, string $input): void + public function testNormalizeBarcodeContent(BarcodeScanResultInterface $expected, string $input): void { $this->assertEquals($expected, $this->service->scanBarcodeContent($input)); } diff --git a/tests/Services/LabelSystem/BarcodeScanner/EIGP114BarcodeScanResultTest.php b/tests/Services/LabelSystem/BarcodeScanner/EIGP114BarcodeScanResultTest.php new file mode 100644 index 00000000..aa5e4fbc --- /dev/null +++ b/tests/Services/LabelSystem/BarcodeScanner/EIGP114BarcodeScanResultTest.php @@ -0,0 +1,154 @@ +. + */ + +namespace App\Tests\Services\LabelSystem\BarcodeScanner; + +use App\Services\LabelSystem\BarcodeScanner\EIGP114BarcodeScanResult; +use PHPUnit\Framework\TestCase; + +class EIGP114BarcodeScanResultTest extends TestCase +{ + + public function testGuessBarcodeVendor(): void + { + //Generic barcode: + + $barcode = new EIGP114BarcodeScanResult([ + 'P' => '596-777A1-ND', + '1P' => 'XAF4444', + 'Q' => '3', + '10D' => '1452', + '1T' => 'BF1103', + '4L' => 'US', + ]); + + $this->assertNull($barcode->guessBarcodeVendor()); + + //Digikey barcode: + $barcode = new EIGP114BarcodeScanResult([ + 'P' => '596-777A1-ND', + '1P' => 'XAF4444', + 'Q' => '3', + '10D' => '1452', + '1T' => 'BF1103', + '4L' => 'US', + '13Z' => 'Digi-Key', + ]); + $this->assertEquals('digikey', $barcode->guessBarcodeVendor()); + + //Mouser barcode: + $barcode = new EIGP114BarcodeScanResult([ + 'P' => '596-777A1-ND', + '1P' => 'XAF4444', + 'Q' => '3', + '10D' => '1452', + '1T' => 'BF1103', + '4L' => 'US', + '1V' => 'Mouser', + ]); + + $this->assertEquals('mouser', $barcode->guessBarcodeVendor()); + + //Farnell barcode: + $barcode = new EIGP114BarcodeScanResult([ + 'P' => '596-777A1-ND', + '1P' => 'XAF4444', + 'Q' => '3', + '10D' => '1452', + '1T' => 'BF1103', + '4L' => 'US', + '3P' => 'Farnell', + ]); + + $this->assertEquals('element14', $barcode->guessBarcodeVendor()); + } + + public function testIsFormat06Code(): void + { + $this->assertFalse(EIGP114BarcodeScanResult::isFormat06Code('')); + $this->assertFalse(EIGP114BarcodeScanResult::isFormat06Code('test')); + $this->assertFalse(EIGP114BarcodeScanResult::isFormat06Code('12232435ew4rf')); + + //Valid code (with trailer) + $this->assertTrue(EIGP114BarcodeScanResult::isFormat06Code("[)>\x1E06\x1DP596-777A1-ND\x1D1PXAF4444\x1DQ3\x1D10D1452\x1D1TBF1103\x1D4LUS\x1E\x04")); + + //Valid code (digikey, without trailer) + $this->assertTrue(EIGP114BarcodeScanResult::isFormat06Code("[)>\x1e06\x1dPQ1045-ND\x1d1P364019-01\x1d30PQ1045-ND\x1dK12432 TRAVIS FOSS P\x1d1K85732873\x1d10K103332956\x1d9D231013\x1d1TQJ13P\x1d11K1\x1d4LTW\x1dQ3\x1d11ZPICK\x1d12Z7360988\x1d13Z999999\x1d20Z0000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); + } + + public function testParseFormat06CodeInvalid(): void + { + $this->expectException(\InvalidArgumentException::class); + EIGP114BarcodeScanResult::parseFormat06Code(''); + } + + public function testParseFormat06Code(): void + { + $barcode = EIGP114BarcodeScanResult::parseFormat06Code("[)>\x1E06\x1DP596-777A1-ND\x1D1PXAF4444\x1DQ3\x1D10D1452\x1D1TBF1103\x1D4LUS\x1E\x04"); + $this->assertEquals([ + 'P' => '596-777A1-ND', + '1P' => 'XAF4444', + 'Q' => '3', + '10D' => '1452', + '1T' => 'BF1103', + '4L' => 'US', + ], $barcode->data); + } + + public function testDataParsing(): void + { + $barcode = new EIGP114BarcodeScanResult([ + 'P' => '596-777A1-ND', + '1P' => 'XAF4444', + 'Q' => '3', + '10D' => '1452', + '1T' => 'BF1103', + '4L' => 'US', + ]); + + $this->assertEquals('596-777A1-ND', $barcode->customerPartNumber); + $this->assertEquals('XAF4444', $barcode->supplierPartNumber); + $this->assertEquals(3, $barcode->quantity); + $this->assertEquals('1452', $barcode->alternativeDateCode); + $this->assertEquals('BF1103', $barcode->lotCode); + $this->assertEquals('US', $barcode->countryOfOrigin); + } + + public function testDigikeyParsing(): void + { + $barcode = EIGP114BarcodeScanResult::parseFormat06Code("[)>\x1e06\x1dPQ1045-ND\x1d1P364019-01\x1d30PQ1045-ND\x1dK12432 TRAVIS FOSS P\x1d1K85732873\x1d10K103332956\x1d9D231013\x1d1TQJ13P\x1d11K1\x1d4LTW\x1dQ3\x1d11ZPICK\x1d12Z7360988\x1d13Z999999\x1d20Z0000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + + $this->assertEquals('digikey', $barcode->guessBarcodeVendor()); + + $this->assertEquals('Q1045-ND', $barcode->customerPartNumber); + $this->assertEquals('364019-01', $barcode->supplierPartNumber); + $this->assertEquals(3, $barcode->quantity); + $this->assertEquals('231013', $barcode->dateCode); + $this->assertEquals('QJ13P', $barcode->lotCode); + $this->assertEquals('TW', $barcode->countryOfOrigin); + $this->assertEquals('Q1045-ND', $barcode->digikeyPartNumber); + $this->assertEquals('85732873', $barcode->digikeySalesOrderNumber); + $this->assertEquals('103332956', $barcode->digikeyInvoiceNumber); + $this->assertEquals('PICK', $barcode->digikeyLabelType); + $this->assertEquals('7360988', $barcode->digikeyPartID); + $this->assertEquals('999999', $barcode->digikeyNA); + $this->assertEquals('0000000000000000000000000000000000000000000000000000000000000000000000000000000000000', $barcode->digikeyPadding); + } +} diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf index 0386e0b1..6a0ccda9 100644 --- a/translations/messages.de.xlf +++ b/translations/messages.de.xlf @@ -12223,5 +12223,101 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön Erzeugter Code + + + info_providers.search.show_existing_part + Bestehendes Bauteil anzeigen + + + + + info_providers.search.edit_existing_part + Bestehendes Bauteil bearbeiten + + + + + info_providers.search.existing_part_found.short + Bauteil existiert bereits + + + + + info_providers.search.existing_part_found + Dieses Bauteil (oder ein sehr ähnliches) existiert bereits in der Datenbank. Bitte überprüfen Sie, ob es das gleiche ist und ob Sie dies erneut erstellen möchten! + + + + + info_providers.search.update_existing_part + Bestehendes Bauteil von Informationsquelle aktualisieren + + + + + part.create_from_info_provider.no_category_yet + Die Kategorie konnte nicht automatisch von der Informationsquelle ermittelt werden. Überprüfen Sie die Daten und wählen Sie die Kategorie händisch aus. + + + + + part_lot.edit.user_barcode + Benutzer-Barcode + + + + + scan_dialog.mode.user + Benutzer definierter Barcode (in Bauteilebestand konfiguriert) + + + + + scan_dialog.mode.eigp + EIGP 114 Barcode (z. B. der Datamatrix code auf Digikey und Mouser Bauteilen) + + + + + scan_dialog.info_mode + Info Modus (Barcode dekodieren und Inhalte anzeigen, aber nicht zum Bauteil weiterleiten) + + + + + label_scanner.decoded_info.title + Dekodierte Informationen + + + + + label_generator.edit_profiles + Profile bearbeiten + + + + + label_generator.profile_name_empty + Der Profilname darf nicht leer sein! + + + + + label_generator.save_profile_name + Profilname + + + + + label_generator.save_profile + Als neues Profil speichern + + + + + label_generator.profile_saved + Profil gespeichert! + + diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 9c45b4be..6e4c47cb 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -12263,5 +12263,65 @@ Please note, that you can not impersonate a disabled user. If you try you will g Category could not be automatically determined by the info provider. Review the data and select the category manually. + + + part_lot.edit.user_barcode + User barcode + + + + + scan_dialog.mode.user + User defined barcode (configured at part lot) + + + + + scan_dialog.mode.eigp + EIGP 114 barcode (e.g. the datamatrix codes on digikey and mouser orders) + + + + + scan_dialog.info_mode + Info mode (Decode barcode and show its contents, but do not redirect to part) + + + + + label_scanner.decoded_info.title + Decoded information + + + + + label_generator.edit_profiles + Edit profiles + + + + + label_generator.profile_name_empty + Profile name must not be empty! + + + + + label_generator.save_profile_name + Profile name + + + + + label_generator.save_profile + Save as new profile + + + + + label_generator.profile_saved + Profile saved! + + diff --git a/translations/security.de.xlf b/translations/security.de.xlf index ef0a6b80..a0498be8 100644 --- a/translations/security.de.xlf +++ b/translations/security.de.xlf @@ -1,17 +1,23 @@ - + user.login_error.user_disabled Ihr Account ist deaktiviert! Kontaktiere einen Administrator, wenn Sie denken, dass dies ein Fehler ist. - + saml.error.cannot_login_local_user_per_saml Sie können sich per SSO nicht als lokaler Nutzer einloggen! Nutzen Sie stattdessen ihr lokales Passwort. + + + saml.error.cannot_login_saml_user_locally + Sie können sich nicht mittels lokaler Authentifizierung als SAML Benutzer einloggen! Benutzen Sie stattdessen den SSO login. + + diff --git a/translations/security.it.xlf b/translations/security.it.xlf index 9b45cd1e..9200458c 100644 --- a/translations/security.it.xlf +++ b/translations/security.it.xlf @@ -1,17 +1,23 @@ - + user.login_error.user_disabled Il tuo account è disabilitato! Contatta un amministratore se ritieni che non sia corretto. - + saml.error.cannot_login_local_user_per_saml Non è possibile accedere come utente locale tramite SSO! Usare invece la password locale. + + + saml.error.cannot_login_saml_user_locally + Non si può usare l'autenticazione locale per effettuare l'accesso come utente SAML! Usare l'accesso SSO. + + diff --git a/webpack.config.js b/webpack.config.js index d5243897..43e04997 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -36,8 +36,8 @@ if (!Encore.isRuntimeEnvironmentConfigured()) { Encore // directory where compiled assets will be stored .setOutputPath('public/build/') - // This value doesn't matter, as the public path is set to auto later down. This is just to prevent a warning - .setPublicPath('/build') + // Do not use a / prefix, here as that would break asset loading when serving Part-DB under a prefix! + .setPublicPath('build') // only needed for CDN's or subdirectory deploy (this should not be needeed, as we use auto public path) //.setManifestKeyPrefix('build/') @@ -59,6 +59,12 @@ Encore .addEntry('app', './assets/js/app.js') .addEntry('webauthn_tfa', './assets/js/webauthn_tfa.js') + //Configure to just output the zxing wasm file, without parsing it + .addRule({ + test: /zxing_reader\.wasm$/, + type: "asset/resource" + }) + //.addEntry('page1', './assets/js/page1.js') //.addEntry('page2', './assets/js/page2.js') @@ -189,6 +195,10 @@ if (Encore.isDev()) { module.exports = Encore.getWebpackConfig(); -//Enable webpack auto public path (this only works in combination with WebpackAutoPathSubscriber!!) +//Enable webassembly support +module.exports.experiments = module.exports.experiments || {}; +module.exports.experiments.asyncWebAssembly = true; + +//Enable webpack auto public path //We do it here to supress a warning caused by webpack Encore module.exports.output.publicPath = 'auto'; diff --git a/yarn.lock b/yarn.lock index fbb935eb..b5adc3e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,58 +2,58 @@ # yarn lockfile v1 -"@algolia/autocomplete-core@1.17.8": - version "1.17.8" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.17.8.tgz#769e365d0e24233658eeb15a0a8b4572016c0924" - integrity sha512-WKQS6+83DYB7bsCN7HUxPGzwR1ZtKy+E8WybnuxnsGzQG/zd4j96R4USqsjz0/tOQVAA4hnlqNn9LtcaOIjCyQ== +"@algolia/autocomplete-core@1.17.9": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.17.9.tgz#83374c47dc72482aa45d6b953e89377047f0dcdc" + integrity sha512-O7BxrpLDPJWWHv/DLA9DRFWs+iY1uOJZkqUwjS5HSZAGcl0hIVCQ97LTLewiZmZ402JYUrun+8NqFP+hCknlbQ== dependencies: - "@algolia/autocomplete-plugin-algolia-insights" "1.17.8" - "@algolia/autocomplete-shared" "1.17.8" + "@algolia/autocomplete-plugin-algolia-insights" "1.17.9" + "@algolia/autocomplete-shared" "1.17.9" -"@algolia/autocomplete-js@1.17.8", "@algolia/autocomplete-js@^1.17.0": - version "1.17.8" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-js/-/autocomplete-js-1.17.8.tgz#2d4a5928d12bd51e16d84388e893431526bd542f" - integrity sha512-WQ5xZ5sY16UH6V0+ACpe2NoKPWs9L/m5uQQn8eNiM7jsHShonEhwV3/kB897k1TovfHqhtH0gPXXMN95taZAqA== +"@algolia/autocomplete-js@1.17.9", "@algolia/autocomplete-js@^1.17.0": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-js/-/autocomplete-js-1.17.9.tgz#2f7e5599fdfe7d6c44b82ac5b64662b487615465" + integrity sha512-51xmsIfApj8LEceArnLe3UfS8HSgJJa70FUEYpnLryO6+KZL0pDPQHw1uVw6e5yJiKN0KelDmk2Sa2uJ+gVKJw== dependencies: - "@algolia/autocomplete-core" "1.17.8" - "@algolia/autocomplete-preset-algolia" "1.17.8" - "@algolia/autocomplete-shared" "1.17.8" + "@algolia/autocomplete-core" "1.17.9" + "@algolia/autocomplete-preset-algolia" "1.17.9" + "@algolia/autocomplete-shared" "1.17.9" htm "^3.1.1" preact "^10.13.2" -"@algolia/autocomplete-plugin-algolia-insights@1.17.8": - version "1.17.8" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.8.tgz#e16f56e4c023bf3776347b6382b4a19e2c0341a8" - integrity sha512-aNbLpDONZejsQKdozt0c6mFvUc8yINpv6WgHyJ9oZm4GFkwtbe0KWTlowyNIO/yRaoGC+Y0BkmSLMnGImy01eQ== +"@algolia/autocomplete-plugin-algolia-insights@1.17.9": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.9.tgz#74c86024d09d09e8bfa3dd90b844b77d9f9947b6" + integrity sha512-u1fEHkCbWF92DBeB/KHeMacsjsoI0wFhjZtlCq2ddZbAehshbZST6Hs0Avkc0s+4UyBGbMDnSuXHLuvRWK5iDQ== dependencies: - "@algolia/autocomplete-shared" "1.17.8" + "@algolia/autocomplete-shared" "1.17.9" "@algolia/autocomplete-plugin-recent-searches@^1.17.0": - version "1.17.8" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-recent-searches/-/autocomplete-plugin-recent-searches-1.17.8.tgz#1e7c6459cb909e899d9933a8297ba35106e77f82" - integrity sha512-bwH/fus481LyRMFhM1BTaiWDkHgmJ1ojlRxUSIPEVauszZFd0cLtWUf4FEwkoQS3NOUpj9xMJr5zVFmq4AoXHA== + version "1.17.9" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-recent-searches/-/autocomplete-plugin-recent-searches-1.17.9.tgz#607c9317834d1bc2661f2e036dd7eb4e87e180f6" + integrity sha512-UWJoCFKprKwDEuzfXWt34+Yf01qlR5P1pWEfWeYTNnmV27P3prw6sZKy7anE8+yXVkmiDss6oMVNyYyYZ7+ZtQ== dependencies: - "@algolia/autocomplete-core" "1.17.8" - "@algolia/autocomplete-js" "1.17.8" - "@algolia/autocomplete-preset-algolia" "1.17.8" - "@algolia/autocomplete-shared" "1.17.8" + "@algolia/autocomplete-core" "1.17.9" + "@algolia/autocomplete-js" "1.17.9" + "@algolia/autocomplete-preset-algolia" "1.17.9" + "@algolia/autocomplete-shared" "1.17.9" -"@algolia/autocomplete-preset-algolia@1.17.8": - version "1.17.8" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.8.tgz#cdcc63993ee69a9e9fb6ed518e8039af33709db1" - integrity sha512-EHGmvfV9Y6HzDlTSt/AAdOthVTH8zgr6A4h9ehheDsAjqsyXj9uNvMAd4lq5bOJs+MZCWTcxpK+Btr9Tcihr3g== +"@algolia/autocomplete-preset-algolia@1.17.9": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.9.tgz#911f3250544eb8ea4096fcfb268f156b085321b5" + integrity sha512-Na1OuceSJeg8j7ZWn5ssMu/Ax3amtOwk76u4h5J4eK2Nx2KB5qt0Z4cOapCsxot9VcEN11ADV5aUSlQF4RhGjQ== dependencies: - "@algolia/autocomplete-shared" "1.17.8" + "@algolia/autocomplete-shared" "1.17.9" -"@algolia/autocomplete-shared@1.17.8": - version "1.17.8" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.8.tgz#aad70ec1bda2ae75f284c23b52295c2e6ada9493" - integrity sha512-2w26RGB9zlyrnPpT/TpB0pbtukmUVYrKVt9ydlHnpPU7FgoiblUBpzzQTVzqMqYASGGjURaKLu7FakmaTIkClg== +"@algolia/autocomplete-shared@1.17.9": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.9.tgz#5f38868f7cb1d54b014b17a10fc4f7e79d427fa8" + integrity sha512-iDf05JDQ7I0b7JEA/9IektxN/80a2MZ1ToohfmNS3rfeuQnIKI3IJlIafD0xu4StbtQTghx9T3Maa97ytkXenQ== "@algolia/autocomplete-theme-classic@^1.17.0": - version "1.17.8" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-theme-classic/-/autocomplete-theme-classic-1.17.8.tgz#cd635a6240da44dcd3aec72b0e5bea0382248710" - integrity sha512-70IJMktChk6mFTqegBYZk5MR0vquX+JEUG7rc4Jo75LTbe3BN4Lh6nUwaBXoLpx4v1zMID1vOsJ0Zv1M/3q3Jw== + version "1.17.9" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-theme-classic/-/autocomplete-theme-classic-1.17.9.tgz#df73d5f6a3c4674d46df9add3bb751bfb1bcf144" + integrity sha512-nCv0uv5J5r9riF1rJLfUY3AAJ11IbFifNocYINWG4cyOctHGoiK5ZDyYdQOLA98TCVR7hUywIYpvVCJBZnjGaw== "@ampproject/remapping@^2.2.0": version "2.3.0" @@ -1664,10 +1664,10 @@ base64-js "1.3.1" unicode-trie "^2.0.0" -"@foliojs-fork/pdfkit@^0.15.2": - version "0.15.2" - resolved "https://registry.yarnpkg.com/@foliojs-fork/pdfkit/-/pdfkit-0.15.2.tgz#6dbe57ed45f1dc022d0219f3810071b9007e347e" - integrity sha512-Wpj6BH4DGn+zAWmCk9agdbAw3Zxt+MpemjssLfYdnretWpZ014uR6Zo51E4ftVP75UA8a7mtt4TiCu09lIKsBw== +"@foliojs-fork/pdfkit@^0.15.3": + version "0.15.3" + resolved "https://registry.yarnpkg.com/@foliojs-fork/pdfkit/-/pdfkit-0.15.3.tgz#590b31e770a98e2af62ce44f268a0d06b41ff32f" + integrity sha512-Obc0Wmy3bm7BINFVvPhcl2rnSSK61DQrlHU8aXnAqDk9LCjWdUOPwhgD8Ywz5VtuFjRxmVOM/kQ/XLIBjDvltw== dependencies: "@foliojs-fork/fontkit" "^1.9.2" "@foliojs-fork/linebreak" "^1.1.1" @@ -1680,44 +1680,44 @@ resolved "https://registry.yarnpkg.com/@foliojs-fork/restructure/-/restructure-2.0.2.tgz#73759aba2aff1da87b7c4554e6839c70d43c92b4" integrity sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA== -"@formatjs/ecma402-abstract@2.3.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.1.tgz#cdeb3ffe1aeea9c4284b85b7e37e8e8615314c39" - integrity sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw== +"@formatjs/ecma402-abstract@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz#0ee291effe7ee2c340742a6c95d92eacb5e6c00a" + integrity sha512-6sE5nyvDloULiyOMbOTJEEgWL32w+VHkZQs8S02Lnn8Y/O5aQhjOEXwWzvR7SsBE/exxlSpY2EsWZgqHbtLatg== dependencies: - "@formatjs/fast-memoize" "2.2.5" - "@formatjs/intl-localematcher" "0.5.9" + "@formatjs/fast-memoize" "2.2.6" + "@formatjs/intl-localematcher" "0.5.10" decimal.js "10" tslib "2" -"@formatjs/fast-memoize@2.2.5": - version "2.2.5" - resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.5.tgz#54a4a1793d773b72c372d3dcab3595149aee7880" - integrity sha512-6PoewUMrrcqxSoBXAOJDiW1m+AmkrAj0RiXnOMD59GRaswjXhm3MDhgepXPBgonc09oSirAJTsAggzAGQf6A6g== +"@formatjs/fast-memoize@2.2.6": + version "2.2.6" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.6.tgz#fac0a84207a1396be1f1aa4ee2805b179e9343d1" + integrity sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw== dependencies: tslib "2" -"@formatjs/icu-messageformat-parser@2.9.7": - version "2.9.7" - resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.7.tgz#84abd5c86ef2ad7cb82da63b3380c33808efb6da" - integrity sha512-cuEHyRM5VqLQobANOjtjlgU7+qmk9Q3fDQuBiRRJ3+Wp3ZoZhpUPtUfuimZXsir6SaI2TaAJ+SLo9vLnV5QcbA== +"@formatjs/icu-messageformat-parser@2.9.8": + version "2.9.8" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.8.tgz#118e7156f8a8db6b27b650f09334db21456c681f" + integrity sha512-hZlLNI3+Lev8IAXuwehLoN7QTKqbx3XXwFW1jh0AdIA9XJdzn9Uzr+2LLBspPm/PX0+NLIfykj/8IKxQqHUcUQ== dependencies: - "@formatjs/ecma402-abstract" "2.3.1" - "@formatjs/icu-skeleton-parser" "1.8.11" + "@formatjs/ecma402-abstract" "2.3.2" + "@formatjs/icu-skeleton-parser" "1.8.12" tslib "2" -"@formatjs/icu-skeleton-parser@1.8.11": - version "1.8.11" - resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.11.tgz#e7c9918274dfa0c1c2bca1ab6e15ef49b10cf0bb" - integrity sha512-8LlHHE/yL/zVJZHAX3pbKaCjZKmBIO6aJY1mkVh4RMSEu/2WRZ4Ysvv3kKXJ9M8RJLBHdnk1/dUQFdod1Dt7Dw== +"@formatjs/icu-skeleton-parser@1.8.12": + version "1.8.12" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.12.tgz#43076747cdbe0f23bfac2b2a956bd8219716680d" + integrity sha512-QRAY2jC1BomFQHYDMcZtClqHR55EEnB96V7Xbk/UiBodsuFc5kujybzt87+qj1KqmJozFhk6n4KiT1HKwAkcfg== dependencies: - "@formatjs/ecma402-abstract" "2.3.1" + "@formatjs/ecma402-abstract" "2.3.2" tslib "2" -"@formatjs/intl-localematcher@0.5.9": - version "0.5.9" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.9.tgz#43c6ee22be85b83340bcb09bdfed53657a2720db" - integrity sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA== +"@formatjs/intl-localematcher@0.5.10": + version "0.5.10" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz#1e0bd3fc1332c1fe4540cfa28f07e9227b659a58" + integrity sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q== dependencies: tslib "2" @@ -1874,6 +1874,13 @@ resolved "https://registry.yarnpkg.com/@orchidjs/unicode-variants/-/unicode-variants-1.1.2.tgz#1fd71791a67fdd1591ebe0dcaadd3964537a824e" integrity sha512-5DobW1CHgnBROOEpFlEXytED5OosEWESFvg/VYmH0143oXcijYTprRYJTs+55HzGM4IqxiLFSuqEzu9mPNwVsA== +"@part-db/html5-qrcode@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@part-db/html5-qrcode/-/html5-qrcode-3.1.1.tgz#96bf5e57796f3ec72ee6191ac7b8604298c98dfa" + integrity sha512-6lH77mvu9ClwIfKHWSOW1+jz+SkCJnbm/8XTuzqlcUWUIW8NRHC5x1wU7svV4gtoirqvBlQvaz1MaopVmfOAqQ== + dependencies: + barcode-detector "^2.3.1" + "@polka/url@^1.0.0-next.24": version "1.0.0-next.28" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" @@ -1932,6 +1939,16 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== +"@types/dom-webcodecs@0.1.11": + version "0.1.11" + resolved "https://registry.yarnpkg.com/@types/dom-webcodecs/-/dom-webcodecs-0.1.11.tgz#2e36e5cc71789551f107e2fe15d956845fa19567" + integrity sha512-yPEZ3z7EohrmOxbk/QTAa0yonMFkNkjnVXqbGb7D4rMr+F1dGQ8ZUFxXkyLLJuiICPejZ0AZE9Rrk9wUCczx4A== + +"@types/emscripten@^1.39.13": + version "1.39.13" + resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.13.tgz#afeb1648648dc096efe57983e20387627306e2aa" + integrity sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw== + "@types/eslint-scope@^3.7.7": version "3.7.7" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" @@ -1991,9 +2008,9 @@ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@*": - version "22.10.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.2.tgz#a485426e6d1fdafc7b0d4c7b24e2c78182ddabb9" - integrity sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ== + version "22.10.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.5.tgz#95af89a3fb74a2bb41ef9927f206e6472026e48b" + integrity sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ== dependencies: undici-types "~6.20.0" @@ -2401,6 +2418,14 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +barcode-detector@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/barcode-detector/-/barcode-detector-2.3.1.tgz#91d0d4e3b913d87f94bae5d024b6b9b5cedc420c" + integrity sha512-D9KEtrquS1tmBZduxBZl8qublIKnRrFqD8TAHDYcLCyrHQBo+vitIxmjMJ61LvXjXyAMalOlO7q0Oh/9Rl2PbQ== + dependencies: + "@types/dom-webcodecs" "0.1.11" + zxing-wasm "1.3.4" + base64-js@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" @@ -3110,11 +3135,11 @@ data-view-byte-offset@^1.0.1: is-data-view "^1.0.1" datatables.net-bs5@^2, datatables.net-bs5@^2.0.0: - version "2.1.8" - resolved "https://registry.yarnpkg.com/datatables.net-bs5/-/datatables.net-bs5-2.1.8.tgz#860717c4ee85ecb84812ba9a73fb1204aa2a68b6" - integrity sha512-YlGws8eI3iw/1AmKJH18+YMzm/UgGb6o9s14KAC24QT1/8anolm8GnVAgGcwUcvHm3hn1i8A5QXqgbqeMRINeg== + version "2.2.0" + resolved "https://registry.yarnpkg.com/datatables.net-bs5/-/datatables.net-bs5-2.2.0.tgz#ba80819825b96827d6361f8385b092c2a657a8af" + integrity sha512-klqdrghM0BkBWulaWiGdKwdZyPHK+8coBGjhSQAZe/eQBnr7OLC8wl7NqPfQuVXv4J74HgO6K1gIYHKHbHjrtA== dependencies: - datatables.net "2.1.8" + datatables.net "2.2.0" jquery ">=1.7" datatables.net-buttons-bs5@^3.0.0: @@ -3202,10 +3227,10 @@ datatables.net-select@2.1.0: datatables.net "^2" jquery ">=1.7" -datatables.net@2.1.8, datatables.net@^2, datatables.net@^2.0.0: - version "2.1.8" - resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-2.1.8.tgz#9b020f18e927cc924d72411f62dc595cc688669b" - integrity sha512-47ULt+U4bcjbuGTpTlT6SnCuSFVRBxxdWa6X3NfvTObBJ2BZU0o+JUIl05wQ6cABNIavjbAV51gpgvFsMHL9zA== +datatables.net@2.2.0, datatables.net@^2, datatables.net@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-2.2.0.tgz#d7e92ede9a3bf23ac4c360e475fbe6313f16fd56" + integrity sha512-q/G5ylL+AhMLFFHNYQAgvooCZtmbudc7wwzKkCZI4B1HnYec4SqnWtcTFjC7P6EtL1UGyx16FcjQ0U84S86mLg== dependencies: jquery ">=1.7" @@ -3365,9 +3390,9 @@ domutils@^2.5.2, domutils@^2.8.0: domhandler "^4.2.0" domutils@^3.0.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.1.tgz#b39f4c390a1ae6f6a2c56a5f5a16d6438b6bce28" - integrity sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw== + version "3.2.2" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78" + integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== dependencies: dom-serializer "^2.0.0" domelementtype "^2.3.0" @@ -3388,9 +3413,9 @@ duplexer@^0.1.2: integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== electron-to-chromium@^1.5.73: - version "1.5.76" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz#db20295c5061b68f07c8ea4dfcbd701485d94a3d" - integrity sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ== + version "1.5.78" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.78.tgz#223cdc76a5d15ac731136e68430e92cb8d612d13" + integrity sha512-UmwIt7HRKN1rsJfddG5UG7rCTCTAKoS9JeOy/R0zSenAyaZ8SU3RuXlwcratxhdxGRNpk03iq8O7BA3W7ibLVw== emoji-regex@^7.0.1: version "7.0.3" @@ -3449,10 +3474,10 @@ error-stack-parser@^2.1.4: dependencies: stackframe "^1.3.4" -es-abstract@^1.23.2, es-abstract@^1.23.5, es-abstract@^1.23.6: - version "1.23.8" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.8.tgz#99754723118355d82fcef9ce4c90ccbcd5d2a285" - integrity sha512-lfab8IzDn6EpI1ibZakcgS6WsfEBiB+43cuJo+wgylx1xKXf+Sp+YR3vFuQwC/u3sxYwV8Cxe3B0DpVUu/WiJQ== +es-abstract@^1.23.2, es-abstract@^1.23.5, es-abstract@^1.23.9: + version "1.23.9" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.9.tgz#5b45994b7de78dada5c1bebf1379646b32b9d606" + integrity sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA== dependencies: array-buffer-byte-length "^1.0.2" arraybuffer.prototype.slice "^1.0.4" @@ -3465,10 +3490,11 @@ es-abstract@^1.23.2, es-abstract@^1.23.5, es-abstract@^1.23.6: es-define-property "^1.0.1" es-errors "^1.3.0" es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" + es-set-tostringtag "^2.1.0" es-to-primitive "^1.3.0" function.prototype.name "^1.1.8" - get-intrinsic "^1.2.6" + get-intrinsic "^1.2.7" + get-proto "^1.0.0" get-symbol-description "^1.1.0" globalthis "^1.0.4" gopd "^1.2.0" @@ -3489,11 +3515,12 @@ es-abstract@^1.23.2, es-abstract@^1.23.5, es-abstract@^1.23.6: object-inspect "^1.13.3" object-keys "^1.1.1" object.assign "^4.1.7" - own-keys "^1.0.0" + own-keys "^1.0.1" regexp.prototype.flags "^1.5.3" safe-array-concat "^1.1.3" safe-push-apply "^1.0.0" safe-regex-test "^1.1.0" + set-proto "^1.0.0" string.prototype.trim "^1.2.10" string.prototype.trimend "^1.0.9" string.prototype.trimstart "^1.0.8" @@ -3531,14 +3558,15 @@ es-object-atoms@^1.0.0: dependencies: es-errors "^1.3.0" -es-set-tostringtag@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== dependencies: - get-intrinsic "^1.2.4" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" has-tostringtag "^1.0.2" - hasown "^2.0.1" + hasown "^2.0.2" es-to-primitive@^1.3.0: version "1.3.0" @@ -3655,15 +3683,15 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.0.3, fast-glob@^3.2.11: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.4" + micromatch "^4.0.8" fast-json-stable-stringify@^2.0.0: version "2.1.0" @@ -3671,9 +3699,9 @@ fast-json-stable-stringify@^2.0.0: integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-uri@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241" - integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== + version "3.0.5" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.5.tgz#19f5f9691d0dab9b85861a7bb5d98fca961da9cd" + integrity sha512-5JnBCWpFlMo0a3ciDy/JckMzzv1U9coZrIhedq+HXxxUfDTAiS0LA8OKVao4G9BxmCVck/jtA5r3KAtRWEyD8Q== fastest-levenshtein@1.0.16, fastest-levenshtein@^1.0.12, fastest-levenshtein@^1.0.16: version "1.0.16" @@ -3811,21 +3839,29 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.6.tgz#43dd3dd0e7b49b82b2dfcad10dc824bf7fc265d5" - integrity sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA== +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.7.tgz#dcfcb33d3272e15f445d15124bc0a216189b9044" + integrity sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA== dependencies: call-bind-apply-helpers "^1.0.1" - dunder-proto "^1.0.0" es-define-property "^1.0.1" es-errors "^1.3.0" es-object-atoms "^1.0.0" function-bind "^1.1.2" + get-proto "^1.0.0" gopd "^1.2.0" has-symbols "^1.1.0" hasown "^2.0.2" - math-intrinsics "^1.0.0" + math-intrinsics "^1.1.0" + +get-proto@^1.0.0, get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" get-symbol-description@^1.1.0: version "1.1.0" @@ -3979,14 +4015,14 @@ has-symbols@^1.0.0, has-symbols@^1.0.3, has-symbols@^1.1.0: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== -has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: +has-tostringtag@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: has-symbols "^1.0.3" -hasown@^2.0.1, hasown@^2.0.2: +hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== @@ -4008,11 +4044,6 @@ html-escaper@^2.0.2: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -html5-qrcode@^2.2.1: - version "2.3.8" - resolved "https://registry.yarnpkg.com/html5-qrcode/-/html5-qrcode-2.3.8.tgz#0b0cdf7a9926cfd4be530e13a51db47592adfa0d" - integrity sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ== - htmlparser2@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" @@ -4109,13 +4140,13 @@ interpret@^3.1.1: integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== intl-messageformat@^10.2.5: - version "10.7.10" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.7.10.tgz#fc8fc8c13b0a4104ba08dc2f5f9225f14945bcb7" - integrity sha512-hp7iejCBiJdW3zmOe18FdlJu8U/JsADSDiBPQhfdSeI8B9POtvPRvPh3nMlvhYayGMKLv6maldhR7y3Pf1vkpw== + version "10.7.11" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.7.11.tgz#f24893b2a64e7b5ec29f9eceb4f1a58bde1346e0" + integrity sha512-IB2N1tmI24k2EFH3PWjU7ivJsnWyLwOWOva0jnXFa29WzB6fb0JZ5EMQGu+XN5lDtjHYFo0/UooP67zBwUg7rQ== dependencies: - "@formatjs/ecma402-abstract" "2.3.1" - "@formatjs/fast-memoize" "2.2.5" - "@formatjs/icu-messageformat-parser" "2.9.7" + "@formatjs/ecma402-abstract" "2.3.2" + "@formatjs/fast-memoize" "2.2.6" + "@formatjs/icu-messageformat-parser" "2.9.8" tslib "2" is-arguments@^1.1.1: @@ -4141,11 +4172,14 @@ is-arrayish@^0.2.1: integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-async-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" - integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.0.tgz#1d1080612c493608e93168fc4458c245074c06a6" + integrity sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ== dependencies: - has-tostringtag "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" is-bigint@^1.1.0: version "1.1.0" @@ -4231,11 +4265,14 @@ is-fullwidth-code-point@^3.0.0: integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-function@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" + integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== dependencies: - has-tostringtag "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.0" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.3" @@ -4640,11 +4677,11 @@ marked@4.0.12: integrity sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ== marked@^15.0.4: - version "15.0.4" - resolved "https://registry.yarnpkg.com/marked/-/marked-15.0.4.tgz#864dbf50227b6507646c771c2ef5f0de2924833e" - integrity sha512-TCHvDqmb3ZJ4PWG7VEGVgtefA5/euFmsIhxtD0XsBxI39gUSKL81mIRFdt0AiNQozUahd4ke98ZdirExd/vSEw== + version "15.0.6" + resolved "https://registry.yarnpkg.com/marked/-/marked-15.0.6.tgz#8165f16afb6f4b30a35bdcee657c3b8415820a8f" + integrity sha512-Y07CUOE+HQXbVDCGl3LXggqJDbXDP2pArc2C1N1RRMN0ONiShoSsIInMd5Gsxupe7fKLpgimTV+HOJ9r7bA+pg== -math-intrinsics@^1.0.0, math-intrinsics@^1.1.0: +math-intrinsics@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== @@ -4669,7 +4706,7 @@ merge2@^1.2.3, merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromatch@^4.0.0, micromatch@^4.0.4: +micromatch@^4.0.0, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -4938,10 +4975,10 @@ opener@^1.5.2: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== -own-keys@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.0.tgz#4ab4f9758185bd8f2716f95453ea7da72fb56c09" - integrity sha512-HcuIjzpjrUbqZPGzWHVg95Bc2Y37KoY5n66QQyEGMzrIWVKHsgHcv8/Aq5Cu3qFUQJzMSPVP8MD3oaFoaME1lg== +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== dependencies: get-intrinsic "^1.2.6" object-keys "^1.1.1" @@ -5071,12 +5108,12 @@ path-type@^4.0.0: integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== pdfmake@^0.2.2: - version "0.2.17" - resolved "https://registry.yarnpkg.com/pdfmake/-/pdfmake-0.2.17.tgz#64beeb0b09c7e0ade39b6d4b379371818cea3da5" - integrity sha512-ODOp1T232yr/HGjdYCq888paBE7RDCflCOSRDUtR9CyfXneOmnMPZJl8dxP9zEXbKiv9vfk9Z/3eK2V2B/Wx/Q== + version "0.2.18" + resolved "https://registry.yarnpkg.com/pdfmake/-/pdfmake-0.2.18.tgz#0be32a9274466494a69285193b64f61f3198ea4e" + integrity sha512-Fe+GnMS8EVZu5rci/CDaQ+xmUoHvx8P+rvIlrwSYM6A5c7Aik8G6lpJbddhjBE2jXGjv6WcUCFCB06uZbjxkMw== dependencies: "@foliojs-fork/linebreak" "^1.1.2" - "@foliojs-fork/pdfkit" "^0.15.2" + "@foliojs-fork/pdfkit" "^0.15.3" iconv-lite "^0.6.3" xmldoc "^1.3.0" @@ -5130,11 +5167,11 @@ possible-typed-array-names@^1.0.0: integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== postcss-calc@^10.0.2: - version "10.0.2" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-10.0.2.tgz#15f01635a27b9d38913a98c4ef2877f5b715b439" - integrity sha512-DT/Wwm6fCKgpYVI7ZEWuPJ4az8hiEHtCUeYjZXqU7Ou4QqYh1Df2yCQ7Ca6N7xqKPFkxN3fhf+u9KSoOCJNAjg== + version "10.1.0" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-10.1.0.tgz#82548b9d52891b87cf6181a445bea4b78e2eedfb" + integrity sha512-uQ/LDGsf3mgsSUEXmAt3VsCSHR3aKqtEIkmB+4PhzYwRYOW5MZs/GhCCFpsOtJJkP6EC6uGipbrnaTjqaJZcJw== dependencies: - postcss-selector-parser "^6.1.2" + postcss-selector-parser "^7.0.0" postcss-value-parser "^4.2.0" postcss-calc@^9.0.1: @@ -5732,17 +5769,17 @@ rechoir@^0.8.0: resolve "^1.20.0" reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.9.tgz#c905f3386008de95a62315f3ea8630404be19e2f" - integrity sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q== + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== dependencies: call-bind "^1.0.8" define-properties "^1.2.1" - dunder-proto "^1.0.1" - es-abstract "^1.23.6" + es-abstract "^1.23.9" es-errors "^1.3.0" - get-intrinsic "^1.2.6" - gopd "^1.2.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" which-builtin-type "^1.2.1" regenerate-unicode-properties@^10.2.0: @@ -5780,13 +5817,15 @@ regex-parser@^2.2.11: integrity sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg== regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.3: - version "1.5.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz#b3ae40b1d2499b8350ab2c3fe6ef3845d3a96f42" - integrity sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ== + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" define-properties "^1.2.1" es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" set-function-name "^2.0.2" regexpu-core@^6.2.0: @@ -6036,6 +6075,15 @@ set-function-name@^2.0.2: functions-have-names "^1.2.3" has-property-descriptors "^1.0.2" +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -6931,3 +6979,10 @@ yocto-queue@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110" integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g== + +zxing-wasm@1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/zxing-wasm/-/zxing-wasm-1.3.4.tgz#4bc45b78dc3594278bb0c24233bfb035ca9efab1" + integrity sha512-9l0QymyATF19FmI92QHe7Dayb+BUN7P7zFAt5iDgTnUf0dFWokz6GVA/W9EepjW5q8s3e89fIE/7uxpX27yqEQ== + dependencies: + "@types/emscripten" "^1.39.13"