diff --git a/src/Controller/ScanController.php b/src/Controller/ScanController.php index 468b2de6..a841c605 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\LocalBarcodeScanResult; -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; diff --git a/src/Form/LabelSystem/ScanDialogType.php b/src/Form/LabelSystem/ScanDialogType.php index 4723bbf9..a12517dd 100644 --- a/src/Form/LabelSystem/ScanDialogType.php +++ b/src/Form/LabelSystem/ScanDialogType.php @@ -41,7 +41,7 @@ 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\EnumType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -72,7 +72,7 @@ class ScanDialogType extends AbstractType BarcodeSourceType::INTERNAL => 'scan_dialog.mode.internal', BarcodeSourceType::IPN => 'scan_dialog.mode.ipn', BarcodeSourceType::USER_DEFINED => 'scan_dialog.mode.user', - BarcodeSourceType::VENDOR => 'scan_dialog.mode.eigp' + BarcodeSourceType::EIGP114 => 'scan_dialog.mode.eigp' }, ]); diff --git a/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php similarity index 93% rename from src/Services/LabelSystem/Barcodes/BarcodeRedirector.php rename to src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php index 8a573321..8aecf180 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php +++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.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\Manufacturer; @@ -62,12 +62,12 @@ final class BarcodeRedirector /** * Determines the URL to which the user should be redirected, when scanning a QR code. * - * @param LocalBarcodeScanResult | VendorBarcodeScanResult $barcodeScan The result of the barcode scan + * @param LocalBarcodeScanResult | EIGP114BarcodeScanResult $barcodeScan The result of the barcode scan * @return string the URL to which should be redirected * * @throws EntityNotFoundException */ - public function getRedirectURL(LocalBarcodeScanResult | VendorBarcodeScanResult $barcodeScan): string + public function getRedirectURL(LocalBarcodeScanResult | EIGP114BarcodeScanResult $barcodeScan): string { if($barcodeScan instanceof LocalBarcodeScanResult) { return $this->getURLLocalBarcode($barcodeScan); @@ -102,7 +102,7 @@ final class BarcodeRedirector /** * Gets the URL to a part from a scan of a Vendor Barcode */ - private function getURLVendorBarcode(VendorBarcodeScanResult $barcodeScan): string + private function getURLVendorBarcode(EIGP114BarcodeScanResult $barcodeScan): string { $part = $this->getPartFromVendor($barcodeScan); return $this->urlGenerator->generate('app_part_show', ['id' => $part->getID()]); @@ -113,7 +113,7 @@ final class BarcodeRedirector * 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(VendorBarcodeScanResult $barcodeScan) : 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 diff --git a/src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanHelper.php similarity index 67% rename from src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php rename to src/Services/LabelSystem/BarcodeScanner/BarcodeScanHelper.php index 6dbd3219..322922ff 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; @@ -77,7 +77,7 @@ final class BarcodeScanHelper * @param BarcodeSourceType|null $type * @return LocalBarcodeScanResult */ - public function scanBarcodeContent(string $input, ?BarcodeSourceType $type = null): LocalBarcodeScanResult | VendorBarcodeScanResult + public function scanBarcodeContent(string $input, ?BarcodeSourceType $type = null): BarcodeScanResultInterface { //Do specific parsing if ($type === BarcodeSourceType::INTERNAL) { @@ -89,11 +89,17 @@ final class BarcodeScanHelper if ($type === BarcodeSourceType::IPN) { return $this->parseIPNBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode'); } - if ($type === BarcodeSourceType::VENDOR) { - return $this->parseFormat06Barcode($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 + + //If the barcode is formatted as EIGP114, we can parse it directly + if (EIGP114BarcodeScanResult::isFormat06Code($input)) { + return $this->parseEIGP114Barcode($input); + } + $result = $this->parseInternalBarcode($input); if ($result !== null) { @@ -112,108 +118,12 @@ final class BarcodeScanHelper return $result; } - $result = $this->parseFormat06Barcode($input); - if ($result !== null) { - return $result; - } - throw new InvalidArgumentException('Unknown barcode'); } - - /** - * Parses Format 06 Barcodes according to ISO/IEC 15434. That standard calls on ASC MH10 to specify - * the data identifiers, but these are way too many to incorporate here. EIGP 114.2018 is yet another standard - * based on Format 06 which specifies identifiers for the electronics industry. I've included the identifiers - * from that standard, plus the extra ones I found on Digikey and Mouser Bags. - * @param string $input what was read from the barcode - * @return ?array Array of the form ["Meaning" => "Value"] - */ - private function decodeFormat06Barcode(string $input): ?array + private function parseEIGP114Barcode(string $input): EIGP114BarcodeScanResult { - if(!str_starts_with($input, "[)>\u{1E}06\u{1D}")){ - return null; - } - if(str_ends_with($input, "\u{04}")){ - $input = substr($input, 0, -1); - } - - $barcodeParts = explode("\u{1D}",$input); - //get rid of the Format 06 identifier - array_shift($barcodeParts); - if (count($barcodeParts) < 2){ - return null; - } - - $fieldIds = [ - //IDs per EIGP 114.2018 - '6D' => 'Ship Date', - 'P' => 'Customer Part Number', - '1P' => 'Supplier Part Number', - 'Q' => 'Quantity', - 'K' => 'Purchase Order Part Number', - '4K' => 'Purchase Order Line Number', - '9D' => 'Date Code', - '10D' => 'Alternative Date Code', - '1T' => 'Lot Code', - '4L' => 'Country of Origin', - '3S' => 'Package ID 1', - '4S' => 'Package ID 2', - '5S' => 'Package ID 3', - '11K' => 'Packing List Number', - 'S' => 'Serial Number', - '33P' => 'BIN Code', - '13Q' => 'Package Count', - '2P' => 'Revision Number', - //IDs used by Digikey - '30P' => 'Digikey Part Number', - '1K' => 'Sales Order Number', - '10K' => 'Invoice Number', - '11Z' => 'Label Type', - '12Z' => 'Part ID', - '13Z' => 'NA', - '20Z' => 'Padding', - //IDs used by Mouser - '14K' => 'Position in Order', - '1V' => 'Manufacturer', - ]; - - $results = []; - - foreach($barcodeParts 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)) { - return null; - } - $meaning = $fieldIds[$matches[0]]; - $fieldValue = substr($part, strlen($matches[0])); - $results[$meaning] = $fieldValue; - - } - return $results; - } - - /** - * Decodes a Format06 Barcode and puts it into a VendorBarcodeScanResult - * See decodeFormat06Barcode for details - */ - private function parseFormat06Barcode(string $input): ?VendorBarcodeScanResult{ - $results = $this->decodeFormat06Barcode($input); - - if($results === null){ - return null; - } - - return new VendorBarcodeScanResult( - manufacturer_part_number: $results['Supplier Part Number'] ?? null, - vendor_part_number: $results['Digikey Part Number'] ?? null, - date_code: $results['Date Code'] ?? null, - quantity: $results['Quantity'] ?? null, - manufacturer: $results['Manufacturer'] ?? null, - ); + return EIGP114BarcodeScanResult::parseFormat06Code($input); } private function parseUserDefinedBarcode(string $input): ?LocalBarcodeScanResult diff --git a/src/Services/LabelSystem/Barcodes/VendorBarcodeScanResult.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanResultInterface.php similarity index 53% rename from src/Services/LabelSystem/Barcodes/VendorBarcodeScanResult.php rename to src/Services/LabelSystem/BarcodeScanner/BarcodeScanResultInterface.php index fc8cd2a3..862c0aa8 100644 --- a/src/Services/LabelSystem/Barcodes/VendorBarcodeScanResult.php +++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanResultInterface.php @@ -2,7 +2,7 @@ /* * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). * - * Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics) + * Copyright (C) 2019 - 2025 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 @@ -21,23 +21,9 @@ declare(strict_types=1); -namespace App\Services\LabelSystem\Barcodes; +namespace App\Services\LabelSystem\BarcodeScanner; -/** - * This class represents the result of a scan of a barcode that was printed by a third party - * and contains useful information about an item, like a vendor id or the order quantity - */ - -class VendorBarcodeScanResult +interface BarcodeScanResultInterface { - public function __construct( - public readonly ?string $vendor = null, - public readonly ?string $manufacturer_part_number = null, - public readonly ?string $vendor_part_number = null, - public readonly ?string $date_code = null, - public readonly ?string $quantity = null, - public readonly ?string $manufacturer = null - ) - { - } + } \ No newline at end of file diff --git a/src/Services/LabelSystem/Barcodes/BarcodeSourceType.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeSourceType.php similarity index 89% rename from src/Services/LabelSystem/Barcodes/BarcodeSourceType.php rename to src/Services/LabelSystem/BarcodeScanner/BarcodeSourceType.php index 06ff0bea..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 @@ -38,5 +38,8 @@ enum BarcodeSourceType */ case USER_DEFINED; - case VENDOR; + /** + * EIGP114 formatted barcodes like used by digikey, mouser, etc. + */ + case EIGP114; } \ No newline at end of file diff --git a/src/Services/LabelSystem/Barcodes/EIGP114Barcode.php b/src/Services/LabelSystem/BarcodeScanner/EIGP114BarcodeScanResult.php similarity index 98% rename from src/Services/LabelSystem/Barcodes/EIGP114Barcode.php rename to src/Services/LabelSystem/BarcodeScanner/EIGP114BarcodeScanResult.php index b4dced70..b9d5f30e 100644 --- a/src/Services/LabelSystem/Barcodes/EIGP114Barcode.php +++ b/src/Services/LabelSystem/BarcodeScanner/EIGP114BarcodeScanResult.php @@ -21,14 +21,14 @@ declare(strict_types=1); -namespace App\Services\LabelSystem\Barcodes; +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 EIGP114Barcode +class EIGP114BarcodeScanResult implements BarcodeScanResultInterface { /** diff --git a/src/Services/LabelSystem/Barcodes/LocalBarcodeScanResult.php b/src/Services/LabelSystem/BarcodeScanner/LocalBarcodeScanResult.php similarity index 91% rename from src/Services/LabelSystem/Barcodes/LocalBarcodeScanResult.php rename to src/Services/LabelSystem/BarcodeScanner/LocalBarcodeScanResult.php index 3f92fe14..5a24ddb4 100644 --- a/src/Services/LabelSystem/Barcodes/LocalBarcodeScanResult.php +++ b/src/Services/LabelSystem/BarcodeScanner/LocalBarcodeScanResult.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace App\Services\LabelSystem\Barcodes; +namespace App\Services\LabelSystem\BarcodeScanner; use App\Entity\LabelSystem\LabelSupportedElement; @@ -29,7 +29,7 @@ use App\Entity\LabelSystem\LabelSupportedElement; * 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 LocalBarcodeScanResult +class LocalBarcodeScanResult implements BarcodeScanResultInterface { public function __construct( public readonly LabelSupportedElement $target_type, diff --git a/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php b/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php index 4f85af1c..cf2a87be 100644 --- a/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php +++ b/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php @@ -42,9 +42,9 @@ declare(strict_types=1); namespace App\Tests\Services\LabelSystem\Barcodes; use App\Entity\LabelSystem\LabelSupportedElement; -use App\Services\LabelSystem\Barcodes\BarcodeRedirector; -use App\Services\LabelSystem\Barcodes\LocalBarcodeScanResult; -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; diff --git a/tests/Services/LabelSystem/Barcodes/BarcodeScanHelperTest.php b/tests/Services/LabelSystem/Barcodes/BarcodeScanHelperTest.php index ba41a6d4..e24c960b 100644 --- a/tests/Services/LabelSystem/Barcodes/BarcodeScanHelperTest.php +++ b/tests/Services/LabelSystem/Barcodes/BarcodeScanHelperTest.php @@ -42,10 +42,9 @@ declare(strict_types=1); namespace App\Tests\Services\LabelSystem\Barcodes; use App\Entity\LabelSystem\LabelSupportedElement; -use App\Services\LabelSystem\Barcodes\BarcodeScanHelper; -use App\Services\LabelSystem\Barcodes\LocalBarcodeScanResult; -use App\Services\LabelSystem\Barcodes\BarcodeSourceType; -use Com\Tecnick\Barcode\Barcode; +use App\Services\LabelSystem\BarcodeScanner\BarcodeScanHelper; +use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType; +use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class BarcodeScanHelperTest extends WebTestCase diff --git a/tests/Services/LabelSystem/Barcodes/EIGP114BarcodeTest.php b/tests/Services/LabelSystem/Barcodes/EIGP114BarcodeTest.php index c23ea06d..9eca7104 100644 --- a/tests/Services/LabelSystem/Barcodes/EIGP114BarcodeTest.php +++ b/tests/Services/LabelSystem/Barcodes/EIGP114BarcodeTest.php @@ -20,7 +20,7 @@ namespace App\Tests\Services\LabelSystem\Barcodes; -use App\Services\LabelSystem\Barcodes\EIGP114Barcode; +use App\Services\LabelSystem\BarcodeScanner\EIGP114BarcodeScanResult; use PHPUnit\Framework\TestCase; class EIGP114BarcodeTest extends TestCase @@ -30,7 +30,7 @@ class EIGP114BarcodeTest extends TestCase { //Generic barcode: - $barcode = new EIGP114Barcode([ + $barcode = new EIGP114BarcodeScanResult([ 'P' => '596-777A1-ND', '1P' => 'XAF4444', 'Q' => '3', @@ -42,7 +42,7 @@ class EIGP114BarcodeTest extends TestCase $this->assertNull($barcode->guessBarcodeVendor()); //Digikey barcode: - $barcode = new EIGP114Barcode([ + $barcode = new EIGP114BarcodeScanResult([ 'P' => '596-777A1-ND', '1P' => 'XAF4444', 'Q' => '3', @@ -54,7 +54,7 @@ class EIGP114BarcodeTest extends TestCase $this->assertEquals('digikey', $barcode->guessBarcodeVendor()); //Mouser barcode: - $barcode = new EIGP114Barcode([ + $barcode = new EIGP114BarcodeScanResult([ 'P' => '596-777A1-ND', '1P' => 'XAF4444', 'Q' => '3', @@ -67,7 +67,7 @@ class EIGP114BarcodeTest extends TestCase $this->assertEquals('mouser', $barcode->guessBarcodeVendor()); //Farnell barcode: - $barcode = new EIGP114Barcode([ + $barcode = new EIGP114BarcodeScanResult([ 'P' => '596-777A1-ND', '1P' => 'XAF4444', 'Q' => '3', @@ -82,25 +82,25 @@ class EIGP114BarcodeTest extends TestCase public function testIsFormat06Code(): void { - $this->assertFalse(EIGP114Barcode::isFormat06Code('')); - $this->assertFalse(EIGP114Barcode::isFormat06Code('test')); - $this->assertFalse(EIGP114Barcode::isFormat06Code('12232435ew4rf')); + $this->assertFalse(EIGP114BarcodeScanResult::isFormat06Code('')); + $this->assertFalse(EIGP114BarcodeScanResult::isFormat06Code('test')); + $this->assertFalse(EIGP114BarcodeScanResult::isFormat06Code('12232435ew4rf')); //Missing trailer - $this->assertFalse(EIGP114Barcode::isFormat06Code("[)>\x1E06\x1DP596-777A1-ND\x1D1PXAF4444\x1DQ3\x1D10D1452\x1D1TBF1103\x1D4LUS")); + $this->assertFalse(EIGP114BarcodeScanResult::isFormat06Code("[)>\x1E06\x1DP596-777A1-ND\x1D1PXAF4444\x1DQ3\x1D10D1452\x1D1TBF1103\x1D4LUS")); //Valid code - $this->assertTrue(EIGP114Barcode::isFormat06Code("[)>\x1E06\x1DP596-777A1-ND\x1D1PXAF4444\x1DQ3\x1D10D1452\x1D1TBF1103\x1D4LUS\x1E\x04")); + $this->assertTrue(EIGP114BarcodeScanResult::isFormat06Code("[)>\x1E06\x1DP596-777A1-ND\x1D1PXAF4444\x1DQ3\x1D10D1452\x1D1TBF1103\x1D4LUS\x1E\x04")); } public function testParseFormat06CodeInvalid(): void { $this->expectException(\InvalidArgumentException::class); - EIGP114Barcode::parseFormat06Code(''); + EIGP114BarcodeScanResult::parseFormat06Code(''); } public function testParseFormat06Code(): void { - $barcode = EIGP114Barcode::parseFormat06Code("[)>\x1E06\x1DP596-777A1-ND\x1D1PXAF4444\x1DQ3\x1D10D1452\x1D1TBF1103\x1D4LUS\x1E\x04"); + $barcode = EIGP114BarcodeScanResult::parseFormat06Code("[)>\x1E06\x1DP596-777A1-ND\x1D1PXAF4444\x1DQ3\x1D10D1452\x1D1TBF1103\x1D4LUS\x1E\x04"); $this->assertEquals([ 'P' => '596-777A1-ND', '1P' => 'XAF4444', @@ -113,7 +113,7 @@ class EIGP114BarcodeTest extends TestCase public function testDataParsing(): void { - $barcode = new EIGP114Barcode([ + $barcode = new EIGP114BarcodeScanResult([ 'P' => '596-777A1-ND', '1P' => 'XAF4444', 'Q' => '3', diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index c7dfa641..5adce468 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -12275,5 +12275,11 @@ Please note, that you can not impersonate a disabled user. If you try you will g User defined barcode (configured at part lot) + + + scan_dialog.mode.eigp + EIGP 114 barcode (e.g. the datamatrix codes on digikey and mouser orders) + +