mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-28 20:39:35 +00:00
Refactored BarcodeRedirector logic to be more universal
This commit is contained in:
parent
e0345076c1
commit
f45960e4be
3 changed files with 101 additions and 127 deletions
|
|
@ -43,7 +43,7 @@ namespace App\Controller;
|
||||||
|
|
||||||
use App\Form\LabelSystem\ScanDialogType;
|
use App\Form\LabelSystem\ScanDialogType;
|
||||||
use App\Services\InfoProviderSystem\Providers\LCSCProvider;
|
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\BarcodeScanHelper;
|
||||||
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultInterface;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultInterface;
|
||||||
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
|
||||||
|
|
@ -71,7 +71,7 @@ use \App\Entity\Parts\StorageLocation;
|
||||||
class ScanController extends AbstractController
|
class ScanController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected BarcodeRedirector $barcodeParser,
|
protected BarcodeScanResultHandler $barcodeParser,
|
||||||
protected BarcodeScanHelper $barcodeNormalizer,
|
protected BarcodeScanHelper $barcodeNormalizer,
|
||||||
private readonly ProviderRegistry $providerRegistry,
|
private readonly ProviderRegistry $providerRegistry,
|
||||||
private readonly PartInfoRetriever $infoRetriever,
|
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 not in info mode, mimic “normal scan” behavior: redirect if possible.
|
||||||
if (!$infoMode) {
|
if (!$infoMode) {
|
||||||
try {
|
try {
|
||||||
$url = $this->barcodeParser->getRedirectURL($scan);
|
$url = $this->barcodeParser->getInfoURL($scan);
|
||||||
return $this->redirect($url);
|
return $this->redirect($url);
|
||||||
} catch (EntityNotFoundException) {
|
} catch (EntityNotFoundException) {
|
||||||
// Decoded OK, but no part is found. If it’s a vendor code, redirect to create.
|
// 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
|
source_type: BarcodeSourceType::INTERNAL
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this->redirect($this->barcodeParser->getRedirectURL($scan_result));
|
return $this->redirect($this->barcodeParser->getInfoURL($scan_result));
|
||||||
} catch (EntityNotFoundException) {
|
} catch (EntityNotFoundException) {
|
||||||
$this->addFlash('success', 'scan.qr_not_found');
|
$this->addFlash('success', 'scan.qr_not_found');
|
||||||
|
|
||||||
|
|
@ -338,7 +338,7 @@ class ScanController extends AbstractController
|
||||||
$targetFound = false;
|
$targetFound = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$redirectUrl = $this->barcodeParser->getRedirectURL($scan);
|
$redirectUrl = $this->barcodeParser->getInfoURL($scan);
|
||||||
$targetFound = true;
|
$targetFound = true;
|
||||||
} catch (EntityNotFoundException) {
|
} catch (EntityNotFoundException) {
|
||||||
}
|
}
|
||||||
|
|
@ -350,7 +350,7 @@ class ScanController extends AbstractController
|
||||||
$locations = [];
|
$locations = [];
|
||||||
|
|
||||||
if ($targetFound) {
|
if ($targetFound) {
|
||||||
$part = $this->barcodeParser->resolvePartOrNull($scan);
|
$part = $this->barcodeParser->resolvePart($scan);
|
||||||
|
|
||||||
if ($part instanceof Part) {
|
if ($part instanceof Part) {
|
||||||
$partName = $part->getName();
|
$partName = $part->getName();
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,13 @@ use InvalidArgumentException;
|
||||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
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
|
* @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
|
* @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) {
|
if($barcodeScan instanceof LocalBarcodeScanResult) {
|
||||||
return $this->getURLLocalBarcode($barcodeScan);
|
return $this->getURLLocalBarcode($barcodeScan);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($barcodeScan instanceof EIGP114BarcodeScanResult) {
|
//For other barcodes try to resolve the part first and then redirect to the part page
|
||||||
return $this->getURLVendorBarcode($barcodeScan);
|
$localPart = $this->resolvePart($barcodeScan);
|
||||||
|
if ($localPart !== null) {
|
||||||
|
return $this->urlGenerator->generate('app_part_show', ['id' => $localPart->getID()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($barcodeScan instanceof GTINBarcodeScanResult) {
|
throw new EntityNotFoundException('Could not resolve a local part for the given barcode scan result');
|
||||||
return $this->getURLGTINBarcode($barcodeScan);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($barcodeScan instanceof LCSCBarcodeScanResult) {
|
|
||||||
return $this->getURLLCSCBarcode($barcodeScan);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidArgumentException('Unknown $barcodeScan type: '.get_class($barcodeScan));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getURLLocalBarcode(LocalBarcodeScanResult $barcodeScan): string
|
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);
|
if ($barcodeScan instanceof LocalBarcodeScanResult) {
|
||||||
return $this->urlGenerator->generate('app_part_show', ['id' => $part->getID()]);
|
return $this->resolvePartFromLocal($barcodeScan);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new EntityNotFoundException();
|
if ($barcodeScan instanceof EIGP114BarcodeScanResult) {
|
||||||
}
|
return $this->resolvePartFromVendor($barcodeScan);
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->urlGenerator->generate('app_part_show', ['id' => $part->getID()]);
|
if ($barcodeScan instanceof GTINBarcodeScanResult) {
|
||||||
}
|
return $this->resolvePartFromGTIN($barcodeScan);
|
||||||
|
|
||||||
/**
|
|
||||||
* 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->supplierPartNumber){
|
if ($barcodeScan instanceof LCSCBarcodeScanResult) {
|
||||||
throw new EntityNotFoundException();
|
return $this->resolvePartFromLCSC($barcodeScan);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Fallback to the manufacturer part number. This may return false positives, since it is common for
|
throw new \InvalidArgumentException("Unknown barcode scan result type: ".get_class($barcodeScan));
|
||||||
//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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolvePartFromLocal(LocalBarcodeScanResult $barcodeScan): ?Part
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -41,10 +41,10 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Tests\Services\LabelSystem\BarcodeScanner;
|
namespace App\Tests\Services\LabelSystem\BarcodeScanner;
|
||||||
|
|
||||||
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultHandler;
|
||||||
use PHPUnit\Framework\Attributes\DataProvider;
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
use PHPUnit\Framework\Attributes\Group;
|
use PHPUnit\Framework\Attributes\Group;
|
||||||
use App\Entity\LabelSystem\LabelSupportedElement;
|
use App\Entity\LabelSystem\LabelSupportedElement;
|
||||||
use App\Services\LabelSystem\BarcodeScanner\BarcodeRedirector;
|
|
||||||
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
|
||||||
use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult;
|
use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult;
|
||||||
use Doctrine\ORM\EntityNotFoundException;
|
use Doctrine\ORM\EntityNotFoundException;
|
||||||
|
|
@ -55,14 +55,14 @@ use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultInterface;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
|
||||||
final class BarcodeRedirectorTest extends KernelTestCase
|
final class BarcodeScanResultHandlerTest extends KernelTestCase
|
||||||
{
|
{
|
||||||
private ?BarcodeRedirector $service = null;
|
private ?BarcodeScanResultHandler $service = null;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
self::bootKernel();
|
self::bootKernel();
|
||||||
$this->service = self::getContainer()->get(BarcodeRedirector::class);
|
$this->service = self::getContainer()->get(BarcodeScanResultHandler::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function urlDataProvider(): \Iterator
|
public static function urlDataProvider(): \Iterator
|
||||||
Loading…
Add table
Add a link
Reference in a new issue