From f45960e4bebf58448f0e3cc76629c63590d64d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 22 Feb 2026 01:28:41 +0100 Subject: [PATCH] Refactored BarcodeRedirector logic to be more universal --- src/Controller/ScanController.php | 12 +- ...ector.php => BarcodeScanResultHandler.php} | 208 ++++++++---------- ...t.php => BarcodeScanResultHandlerTest.php} | 8 +- 3 files changed, 101 insertions(+), 127 deletions(-) rename src/Services/LabelSystem/BarcodeScanner/{BarcodeRedirector.php => BarcodeScanResultHandler.php} (68%) rename tests/Services/LabelSystem/BarcodeScanner/{BarcodeRedirectorTest.php => BarcodeScanResultHandlerTest.php} (95%) diff --git a/src/Controller/ScanController.php b/src/Controller/ScanController.php index 1b339e9d..55bf2e22 100644 --- a/src/Controller/ScanController.php +++ b/src/Controller/ScanController.php @@ -43,7 +43,7 @@ namespace App\Controller; use App\Form\LabelSystem\ScanDialogType; use App\Services\InfoProviderSystem\Providers\LCSCProvider; -use App\Services\LabelSystem\BarcodeScanner\BarcodeRedirector; +use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultHandler; use App\Services\LabelSystem\BarcodeScanner\BarcodeScanHelper; use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultInterface; use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType; @@ -71,7 +71,7 @@ use \App\Entity\Parts\StorageLocation; class ScanController extends AbstractController { public function __construct( - protected BarcodeRedirector $barcodeParser, + protected BarcodeScanResultHandler $barcodeParser, protected BarcodeScanHelper $barcodeNormalizer, private readonly ProviderRegistry $providerRegistry, private readonly PartInfoRetriever $infoRetriever, @@ -103,7 +103,7 @@ class ScanController extends AbstractController // If not in info mode, mimic “normal scan” behavior: redirect if possible. if (!$infoMode) { try { - $url = $this->barcodeParser->getRedirectURL($scan); + $url = $this->barcodeParser->getInfoURL($scan); return $this->redirect($url); } catch (EntityNotFoundException) { // Decoded OK, but no part is found. If it’s a vendor code, redirect to create. @@ -153,7 +153,7 @@ class ScanController extends AbstractController source_type: BarcodeSourceType::INTERNAL ); - return $this->redirect($this->barcodeParser->getRedirectURL($scan_result)); + return $this->redirect($this->barcodeParser->getInfoURL($scan_result)); } catch (EntityNotFoundException) { $this->addFlash('success', 'scan.qr_not_found'); @@ -338,7 +338,7 @@ class ScanController extends AbstractController $targetFound = false; try { - $redirectUrl = $this->barcodeParser->getRedirectURL($scan); + $redirectUrl = $this->barcodeParser->getInfoURL($scan); $targetFound = true; } catch (EntityNotFoundException) { } @@ -350,7 +350,7 @@ class ScanController extends AbstractController $locations = []; if ($targetFound) { - $part = $this->barcodeParser->resolvePartOrNull($scan); + $part = $this->barcodeParser->resolvePart($scan); if ($part instanceof Part) { $partName = $part->getName(); diff --git a/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanResultHandler.php similarity index 68% rename from src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php rename to src/Services/LabelSystem/BarcodeScanner/BarcodeScanResultHandler.php index 32e5fb77..22928236 100644 --- a/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php +++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanResultHandler.php @@ -52,11 +52,13 @@ use InvalidArgumentException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; /** + * This class handles the result of a barcode scan and determines further actions, like which URL the user should be redirected to. + * * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeRedirectorTest */ -final class BarcodeRedirector +final readonly class BarcodeScanResultHandler { - public function __construct(private readonly UrlGeneratorInterface $urlGenerator, private readonly EntityManagerInterface $em) + public function __construct(private UrlGeneratorInterface $urlGenerator, private EntityManagerInterface $em) { } @@ -68,25 +70,21 @@ final class BarcodeRedirector * * @throws EntityNotFoundException */ - public function getRedirectURL(BarcodeScanResultInterface $barcodeScan): string + public function getInfoURL(BarcodeScanResultInterface $barcodeScan): string { + //For our internal barcode format we can directly determine the target without looking up the part + //Also here we can encounter different types of barcodes, like storage location barcodes, which are not resolvable to a part if($barcodeScan instanceof LocalBarcodeScanResult) { return $this->getURLLocalBarcode($barcodeScan); } - if ($barcodeScan instanceof EIGP114BarcodeScanResult) { - return $this->getURLVendorBarcode($barcodeScan); + //For other barcodes try to resolve the part first and then redirect to the part page + $localPart = $this->resolvePart($barcodeScan); + if ($localPart !== null) { + return $this->urlGenerator->generate('app_part_show', ['id' => $localPart->getID()]); } - if ($barcodeScan instanceof GTINBarcodeScanResult) { - return $this->getURLGTINBarcode($barcodeScan); - } - - if ($barcodeScan instanceof LCSCBarcodeScanResult) { - return $this->getURLLCSCBarcode($barcodeScan); - } - - throw new InvalidArgumentException('Unknown $barcodeScan type: '.get_class($barcodeScan)); + throw new EntityNotFoundException('Could not resolve a local part for the given barcode scan result'); } private function getURLLocalBarcode(LocalBarcodeScanResult $barcodeScan): string @@ -112,119 +110,31 @@ final class BarcodeRedirector } /** - * Gets the URL to a part from a scan of the LCSC Barcode + * Tries to resolve a Part from the given barcode scan result. Returns null if no part could be found for the given barcode, + * or the barcode doesn't contain information allowing to resolve to a local part. + * @param BarcodeScanResultInterface $barcodeScan + * @return Part|null + * @throws \InvalidArgumentException if the barcode scan result type is unknown and cannot be handled this function */ - private function getURLLCSCBarcode(LCSCBarcodeScanResult $barcodeScan): string + public function resolvePart(BarcodeScanResultInterface $barcodeScan): ?Part { - $part = $this->getPartFromLCSC($barcodeScan); - return $this->urlGenerator->generate('app_part_show', ['id' => $part->getID()]); - } - - /** - * Resolve LCSC barcode -> Part. - * Strategy: - * 1) Try providerReference.provider_id == pc (LCSC "Cxxxxxx") if you store it there - * 2) Fallback to manufacturer_product_number == pm (MPN) - * Returns first match (consistent with EIGP114 logic) - */ - private function getPartFromLCSC(LCSCBarcodeScanResult $barcodeScan): Part - { - // Try LCSC code (pc) as provider id if available - $pc = $barcodeScan->lcscCode; // e.g. C138033 - if ($pc) { - $part = $this->em->getRepository(Part::class)->getPartByProviderInfo($pc); - if ($part !== null) { - return $part; - } - } - - // Fallback to MPN (pm) - $pm = $barcodeScan->mpn; // e.g. RC0402FR-071ML - if (!$pm) { - throw new EntityNotFoundException(); - } - - $part = $this->em->getRepository(Part::class)->getPartByMPN($pm); - if ($part !== null) { - return $part; + if ($barcodeScan instanceof LocalBarcodeScanResult) { + return $this->resolvePartFromLocal($barcodeScan); } - throw new EntityNotFoundException(); - } - - /** - * 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()]); - } - - private function getURLGTINBarcode(GTINBarcodeScanResult $barcodeScan): string - { - $part = $this->em->getRepository(Part::class)->findOneBy(['gtin' => $barcodeScan->gtin]); - if (!$part instanceof Part) { - throw new EntityNotFoundException(); + if ($barcodeScan instanceof EIGP114BarcodeScanResult) { + return $this->resolvePartFromVendor($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) { - - $part = $this->em->getRepository(Part::class)->getPartByProviderInfo($barcodeScan->digikeyPartNumber); - if ($part !== null) { - return $part; - } + if ($barcodeScan instanceof GTINBarcodeScanResult) { + return $this->resolvePartFromGTIN($barcodeScan); } - if (!$barcodeScan->supplierPartNumber){ - throw new EntityNotFoundException(); + if ($barcodeScan instanceof LCSCBarcodeScanResult) { + return $this->resolvePartFromLCSC($barcodeScan); } - //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 - - $part = $this->em->getRepository(Part::class)->getPartByMPN($barcodeScan->supplierPartNumber, $barcodeScan->mouserManufacturer); - if($part !== null) { - return $part; - } - - throw new EntityNotFoundException(); - } - - public function resolvePartOrNull(BarcodeScanResultInterface $barcodeScan): ?Part - { - try { - if ($barcodeScan instanceof LocalBarcodeScanResult) { - return $this->resolvePartFromLocal($barcodeScan); - } - - if ($barcodeScan instanceof EIGP114BarcodeScanResult) { - return $this->getPartFromVendor($barcodeScan); - } - - if ($barcodeScan instanceof LCSCBarcodeScanResult) { - return $this->getPartFromLCSC($barcodeScan); - } - - return null; - } catch (EntityNotFoundException) { - return null; - } + throw new \InvalidArgumentException("Unknown barcode scan result type: ".get_class($barcodeScan)); } private function resolvePartFromLocal(LocalBarcodeScanResult $barcodeScan): ?Part @@ -247,4 +157,68 @@ final class BarcodeRedirector } } + /** + * 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 resolvePartFromVendor(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) { + + $part = $this->em->getRepository(Part::class)->getPartByProviderInfo($barcodeScan->digikeyPartNumber); + if ($part !== null) { + return $part; + } + } + + if (!$barcodeScan->supplierPartNumber){ + return null; + } + + //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 + + return $this->em->getRepository(Part::class)->getPartByMPN($barcodeScan->supplierPartNumber, $barcodeScan->mouserManufacturer); + } + + /** + * Resolve LCSC barcode -> Part. + * Strategy: + * 1) Try providerReference.provider_id == pc (LCSC "Cxxxxxx") if you store it there + * 2) Fallback to manufacturer_product_number == pm (MPN) + * Returns first match (consistent with EIGP114 logic) + */ + private function resolvePartFromLCSC(LCSCBarcodeScanResult $barcodeScan): ?Part + { + // Try LCSC code (pc) as provider id if available + $pc = $barcodeScan->lcscCode; // e.g. C138033 + if ($pc) { + $part = $this->em->getRepository(Part::class)->getPartByProviderInfo($pc); + if ($part !== null) { + return $part; + } + } + + // Fallback to MPN (pm) + $pm = $barcodeScan->mpn; // e.g. RC0402FR-071ML + if (!$pm) { + return null; + } + + return $this->em->getRepository(Part::class)->getPartByMPN($pm); + } + + private function resolvePartFromGTIN(GTINBarcodeScanResult $barcodeScan): ?Part + { + return $this->em->getRepository(Part::class)->findOneBy(['gtin' => $barcodeScan->gtin]); + } + + + } diff --git a/tests/Services/LabelSystem/BarcodeScanner/BarcodeRedirectorTest.php b/tests/Services/LabelSystem/BarcodeScanner/BarcodeScanResultHandlerTest.php similarity index 95% rename from tests/Services/LabelSystem/BarcodeScanner/BarcodeRedirectorTest.php rename to tests/Services/LabelSystem/BarcodeScanner/BarcodeScanResultHandlerTest.php index 8a2810ba..27c13f98 100644 --- a/tests/Services/LabelSystem/BarcodeScanner/BarcodeRedirectorTest.php +++ b/tests/Services/LabelSystem/BarcodeScanner/BarcodeScanResultHandlerTest.php @@ -41,10 +41,10 @@ declare(strict_types=1); namespace App\Tests\Services\LabelSystem\BarcodeScanner; +use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultHandler; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use App\Entity\LabelSystem\LabelSupportedElement; -use App\Services\LabelSystem\BarcodeScanner\BarcodeRedirector; use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType; use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult; use Doctrine\ORM\EntityNotFoundException; @@ -55,14 +55,14 @@ use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultInterface; use InvalidArgumentException; -final class BarcodeRedirectorTest extends KernelTestCase +final class BarcodeScanResultHandlerTest extends KernelTestCase { - private ?BarcodeRedirector $service = null; + private ?BarcodeScanResultHandler $service = null; protected function setUp(): void { self::bootKernel(); - $this->service = self::getContainer()->get(BarcodeRedirector::class); + $this->service = self::getContainer()->get(BarcodeScanResultHandler::class); } public static function urlDataProvider(): \Iterator