mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-03-01 04:49:36 +00:00
Moved barcode to info provider logic from Controller to BarcodeScanResultHandler service
This commit is contained in:
parent
caa71bbdda
commit
8dd972f1ad
4 changed files with 171 additions and 95 deletions
|
|
@ -41,6 +41,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Exceptions\InfoProviderNotActiveException;
|
||||||
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\BarcodeScanResultHandler;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultHandler;
|
||||||
|
|
@ -71,11 +72,11 @@ use \App\Entity\Parts\StorageLocation;
|
||||||
class ScanController extends AbstractController
|
class ScanController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected BarcodeScanResultHandler $barcodeParser,
|
protected BarcodeScanResultHandler $resultHandler,
|
||||||
protected BarcodeScanHelper $barcodeNormalizer,
|
protected BarcodeScanHelper $barcodeNormalizer,
|
||||||
private readonly ProviderRegistry $providerRegistry,
|
private readonly ProviderRegistry $providerRegistry,
|
||||||
private readonly PartInfoRetriever $infoRetriever,
|
private readonly PartInfoRetriever $infoRetriever,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route(path: '', name: 'scan_dialog')]
|
#[Route(path: '', name: 'scan_dialog')]
|
||||||
public function dialog(Request $request, #[MapQueryParameter] ?string $input = null): Response
|
public function dialog(Request $request, #[MapQueryParameter] ?string $input = null): Response
|
||||||
|
|
@ -103,7 +104,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->getInfoURL($scan);
|
$url = $this->resultHandler->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 +154,7 @@ class ScanController extends AbstractController
|
||||||
source_type: BarcodeSourceType::INTERNAL
|
source_type: BarcodeSourceType::INTERNAL
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this->redirect($this->barcodeParser->getInfoURL($scan_result));
|
return $this->redirect($this->resultHandler->getInfoURL($scan_result));
|
||||||
} catch (EntityNotFoundException) {
|
} catch (EntityNotFoundException) {
|
||||||
$this->addFlash('success', 'scan.qr_not_found');
|
$this->addFlash('success', 'scan.qr_not_found');
|
||||||
|
|
||||||
|
|
@ -168,86 +169,13 @@ class ScanController extends AbstractController
|
||||||
*/
|
*/
|
||||||
private function buildCreateUrlForScanResult(BarcodeScanResultInterface $scanResult): ?string
|
private function buildCreateUrlForScanResult(BarcodeScanResultInterface $scanResult): ?string
|
||||||
{
|
{
|
||||||
// LCSC
|
try {
|
||||||
if ($scanResult instanceof LCSCBarcodeScanResult) {
|
return $this->resultHandler->getCreationURL($scanResult);
|
||||||
$lcscCode = $scanResult->lcscCode;
|
} catch (InfoProviderNotActiveException $e) {
|
||||||
if ($lcscCode !== null && $lcscCode !== '') {
|
$this->addFlash('error', $e->getMessage());
|
||||||
return $this->generateUrl('info_providers_create_part', [
|
} catch (\Throwable) {
|
||||||
'providerKey' => 'lcsc',
|
$this->addFlash('error', 'An error occurred while looking up the provider for this barcode. Please try again later.');
|
||||||
'providerId' => $lcscCode,
|
// Don’t break scanning UX if provider lookup fails
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mouser / Digi-Key (EIGP114)
|
|
||||||
if ($scanResult instanceof EIGP114BarcodeScanResult) {
|
|
||||||
$vendor = $scanResult->guessBarcodeVendor();
|
|
||||||
|
|
||||||
// Mouser: use supplierPartNumber -> search provider -> provider_id
|
|
||||||
if ($vendor === 'mouser'
|
|
||||||
&& $scanResult->supplierPartNumber !== null
|
|
||||||
&& $scanResult->supplierPartNumber !== ''
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
$mouserProvider = $this->providerRegistry->getProviderByKey('mouser');
|
|
||||||
|
|
||||||
if (!$mouserProvider->isActive()) {
|
|
||||||
$this->addFlash('warning', 'Mouser provider is disabled / not configured.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Search Mouser using the MPN
|
|
||||||
$dtos = $this->infoRetriever->searchByKeyword(
|
|
||||||
keyword: $scanResult->supplierPartNumber,
|
|
||||||
providers: [$mouserProvider]
|
|
||||||
);
|
|
||||||
|
|
||||||
// If there are results, provider_id is MouserPartNumber (per MouserProvider.php)
|
|
||||||
$best = $dtos[0] ?? null;
|
|
||||||
|
|
||||||
if ($best !== null && $best->provider_id !== '') {
|
|
||||||
|
|
||||||
return $this->generateUrl('info_providers_create_part', [
|
|
||||||
'providerKey' => 'mouser',
|
|
||||||
'providerId' => $best->provider_id,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->addFlash('warning', 'No Mouser match found for this MPN.');
|
|
||||||
return null;
|
|
||||||
} catch (\InvalidArgumentException) {
|
|
||||||
// provider key not found in registry
|
|
||||||
$this->addFlash('warning', 'Mouser provider is not installed/enabled.');
|
|
||||||
return null;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
// Don’t break scanning UX if provider lookup fails
|
|
||||||
$this->addFlash('warning', 'Mouser lookup failed: ' . $e->getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Digi-Key: can use customerPartNumber or supplierPartNumber directly
|
|
||||||
if ($vendor === 'digikey') {
|
|
||||||
try {
|
|
||||||
$provider = $this->providerRegistry->getProviderByKey('digikey');
|
|
||||||
|
|
||||||
if (!$provider->isActive()) {
|
|
||||||
$this->addFlash('warning', 'Digi-Key provider is disabled / not configured (API key missing).');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$id = $scanResult->customerPartNumber ?: $scanResult->supplierPartNumber;
|
|
||||||
|
|
||||||
if (is_string($id) && $id !== '') {
|
|
||||||
return $this->generateUrl('info_providers_create_part', [
|
|
||||||
'providerKey' => 'digikey',
|
|
||||||
'providerId' => $id,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} catch (\InvalidArgumentException) {
|
|
||||||
$this->addFlash('warning', 'Digi-Key provider is not installed/enabled');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -338,7 +266,7 @@ class ScanController extends AbstractController
|
||||||
$targetFound = false;
|
$targetFound = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$redirectUrl = $this->barcodeParser->getInfoURL($scan);
|
$redirectUrl = $this->resultHandler->getInfoURL($scan);
|
||||||
$targetFound = true;
|
$targetFound = true;
|
||||||
} catch (EntityNotFoundException) {
|
} catch (EntityNotFoundException) {
|
||||||
}
|
}
|
||||||
|
|
@ -350,7 +278,7 @@ class ScanController extends AbstractController
|
||||||
$locations = [];
|
$locations = [];
|
||||||
|
|
||||||
if ($targetFound) {
|
if ($targetFound) {
|
||||||
$part = $this->barcodeParser->resolvePart($scan);
|
$part = $this->resultHandler->resolvePart($scan);
|
||||||
|
|
||||||
if ($part instanceof Part) {
|
if ($part instanceof Part) {
|
||||||
$partName = $part->getName();
|
$partName = $part->getName();
|
||||||
|
|
|
||||||
48
src/Exceptions/InfoProviderNotActiveException.php
Normal file
48
src/Exceptions/InfoProviderNotActiveException.php
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2026 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use App\Services\InfoProviderSystem\Providers\InfoProviderInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception denoting that a required info provider is not active. This can be used to display a user-friendly error message,
|
||||||
|
* when a user tries to use an info provider that is not active.
|
||||||
|
*/
|
||||||
|
class InfoProviderNotActiveException extends \RuntimeException
|
||||||
|
{
|
||||||
|
public function __construct(public readonly string $providerKey, public readonly string $friendlyName)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf('The info provider "%s" (%s) is not active.', $this->friendlyName, $this->providerKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of this exception from an info provider instance
|
||||||
|
* @param InfoProviderInterface $provider
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public static function fromProvider(InfoProviderInterface $provider): self
|
||||||
|
{
|
||||||
|
return new self($provider->getProviderKey(), $provider->getProviderInfo()['name'] ?? '???');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||||
namespace App\Services\InfoProviderSystem;
|
namespace App\Services\InfoProviderSystem;
|
||||||
|
|
||||||
use App\Entity\Parts\Part;
|
use App\Entity\Parts\Part;
|
||||||
|
use App\Exceptions\InfoProviderNotActiveException;
|
||||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||||
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
||||||
use App\Services\InfoProviderSystem\Providers\InfoProviderInterface;
|
use App\Services\InfoProviderSystem\Providers\InfoProviderInterface;
|
||||||
|
|
@ -49,6 +50,7 @@ final class PartInfoRetriever
|
||||||
* @param string[]|InfoProviderInterface[] $providers A list of providers to search in, either as provider keys or as provider instances
|
* @param string[]|InfoProviderInterface[] $providers A list of providers to search in, either as provider keys or as provider instances
|
||||||
* @param string $keyword The keyword to search for
|
* @param string $keyword The keyword to search for
|
||||||
* @return SearchResultDTO[] The search results
|
* @return SearchResultDTO[] The search results
|
||||||
|
* @throws InfoProviderNotActiveException if any of the given providers is not active
|
||||||
*/
|
*/
|
||||||
public function searchByKeyword(string $keyword, array $providers): array
|
public function searchByKeyword(string $keyword, array $providers): array
|
||||||
{
|
{
|
||||||
|
|
@ -61,7 +63,7 @@ final class PartInfoRetriever
|
||||||
|
|
||||||
//Ensure that the provider is active
|
//Ensure that the provider is active
|
||||||
if (!$provider->isActive()) {
|
if (!$provider->isActive()) {
|
||||||
throw new \RuntimeException("The provider with key {$provider->getProviderKey()} is not active!");
|
throw InfoProviderNotActiveException::fromProvider($provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$provider instanceof InfoProviderInterface) {
|
if (!$provider instanceof InfoProviderInterface) {
|
||||||
|
|
@ -97,6 +99,7 @@ final class PartInfoRetriever
|
||||||
* @param string $provider_key
|
* @param string $provider_key
|
||||||
* @param string $part_id
|
* @param string $part_id
|
||||||
* @return PartDetailDTO
|
* @return PartDetailDTO
|
||||||
|
* @throws InfoProviderNotActiveException if the the given providers is not active
|
||||||
*/
|
*/
|
||||||
public function getDetails(string $provider_key, string $part_id): PartDetailDTO
|
public function getDetails(string $provider_key, string $part_id): PartDetailDTO
|
||||||
{
|
{
|
||||||
|
|
@ -104,7 +107,7 @@ final class PartInfoRetriever
|
||||||
|
|
||||||
//Ensure that the provider is active
|
//Ensure that the provider is active
|
||||||
if (!$provider->isActive()) {
|
if (!$provider->isActive()) {
|
||||||
throw new \RuntimeException("The provider with key $provider_key is not active!");
|
throw InfoProviderNotActiveException::fromProvider($provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Generate key and escape reserved characters from the provider id
|
//Generate key and escape reserved characters from the provider id
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,10 @@ use App\Entity\Parts\Manufacturer;
|
||||||
use App\Entity\Parts\Part;
|
use App\Entity\Parts\Part;
|
||||||
use App\Entity\Parts\PartLot;
|
use App\Entity\Parts\PartLot;
|
||||||
use App\Entity\Parts\StorageLocation;
|
use App\Entity\Parts\StorageLocation;
|
||||||
|
use App\Exceptions\InfoProviderNotActiveException;
|
||||||
use App\Repository\Parts\PartRepository;
|
use App\Repository\Parts\PartRepository;
|
||||||
|
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
||||||
|
use App\Services\InfoProviderSystem\ProviderRegistry;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityNotFoundException;
|
use Doctrine\ORM\EntityNotFoundException;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
@ -59,7 +62,8 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
*/
|
*/
|
||||||
final readonly class BarcodeScanResultHandler
|
final readonly class BarcodeScanResultHandler
|
||||||
{
|
{
|
||||||
public function __construct(private UrlGeneratorInterface $urlGenerator, private EntityManagerInterface $em)
|
public function __construct(private UrlGeneratorInterface $urlGenerator, private EntityManagerInterface $em, private PartInfoRetriever $infoRetriever,
|
||||||
|
private ProviderRegistry $providerRegistry)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,11 +100,33 @@ final readonly class BarcodeScanResultHandler
|
||||||
throw new \LogicException("Resolved entity is of unknown type: ".get_class($entity));
|
throw new \LogicException("Resolved entity is of unknown type: ".get_class($entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a URL to create a new part based on this barcode scan result, if possible.
|
||||||
|
* @param BarcodeScanResultInterface $scanResult
|
||||||
|
* @return string|null
|
||||||
|
* @throws InfoProviderNotActiveException If the scan result contains information for a provider which is currently not active in the system
|
||||||
|
*/
|
||||||
|
public function getCreationURL(BarcodeScanResultInterface $scanResult): ?string
|
||||||
|
{
|
||||||
|
$infos = $this->getCreateInfos($scanResult);
|
||||||
|
if ($infos === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ensure that the provider is active, otherwise we should not generate a creation URL for it
|
||||||
|
$provider = $this->providerRegistry->getProviderByKey($infos['providerKey']);
|
||||||
|
if (!$provider->isActive()) {
|
||||||
|
throw InfoProviderNotActiveException::fromProvider($provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->urlGenerator->generate('info_providers_create_part', ['providerKey' => $infos['providerKey'], 'providerId' => $infos['providerId']]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to resolve the given barcode scan result to a local entity. This can be a Part, a PartLot or a StorageLocation, depending on the type of the barcode and the information contained in it.
|
* Tries to resolve the given barcode scan result to a local entity. This can be a Part, a PartLot or a StorageLocation, depending on the type of the barcode and the information contained in it.
|
||||||
* Returns null if no matching entity could be found.
|
* Returns null if no matching entity could be found.
|
||||||
* @param BarcodeScanResultInterface $barcodeScan
|
* @param BarcodeScanResultInterface $barcodeScan
|
||||||
* @return Part|PartLot|StorageLocation
|
* @return Part|PartLot|StorageLocation|null
|
||||||
*/
|
*/
|
||||||
public function resolveEntity(BarcodeScanResultInterface $barcodeScan): Part|PartLot|StorageLocation|null
|
public function resolveEntity(BarcodeScanResultInterface $barcodeScan): Part|PartLot|StorageLocation|null
|
||||||
{
|
{
|
||||||
|
|
@ -113,7 +139,7 @@ final readonly class BarcodeScanResultHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($barcodeScan instanceof GTINBarcodeScanResult) {
|
if ($barcodeScan instanceof GTINBarcodeScanResult) {
|
||||||
return $this->resolvePartFromGTIN($barcodeScan);
|
return $this->em->getRepository(Part::class)->findOneBy(['gtin' => $barcodeScan->gtin]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($barcodeScan instanceof LCSCBarcodeScanResult) {
|
if ($barcodeScan instanceof LCSCBarcodeScanResult) {
|
||||||
|
|
@ -210,11 +236,82 @@ final readonly class BarcodeScanResultHandler
|
||||||
return $this->em->getRepository(Part::class)->getPartByMPN($pm);
|
return $this->em->getRepository(Part::class)->getPartByMPN($pm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolvePartFromGTIN(GTINBarcodeScanResult $barcodeScan): ?Part
|
|
||||||
|
/**
|
||||||
|
* Tries to extract creation information for a part from the given barcode scan result. This can be used to
|
||||||
|
* automatically fill in the info provider reference of a part, when creating a new part based on the scan result.
|
||||||
|
* Returns null if no provider information could be extracted from the scan result, or if the scan result type is unknown and cannot be handled by this function.
|
||||||
|
* It is not necessarily checked that the provider is active, or that the result actually exists on the provider side.
|
||||||
|
* @param BarcodeScanResultInterface $scanResult
|
||||||
|
* @return array{providerKey: string, providerId: string}|null
|
||||||
|
* @throws InfoProviderNotActiveException If the scan result contains information for a provider which is currently not active in the system
|
||||||
|
*/
|
||||||
|
public function getCreateInfos(BarcodeScanResultInterface $scanResult): ?array
|
||||||
{
|
{
|
||||||
return $this->em->getRepository(Part::class)->findOneBy(['gtin' => $barcodeScan->gtin]);
|
// LCSC
|
||||||
|
if ($scanResult instanceof LCSCBarcodeScanResult) {
|
||||||
|
return [
|
||||||
|
'providerKey' => 'lcsc',
|
||||||
|
'providerId' => $scanResult->lcscCode,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scanResult instanceof EIGP114BarcodeScanResult) {
|
||||||
|
return $this->getCreationInfoForEIGP114($scanResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param EIGP114BarcodeScanResult $scanResult
|
||||||
|
* @return array{providerKey: string, providerId: string}|null
|
||||||
|
*/
|
||||||
|
private function getCreationInfoForEIGP114(EIGP114BarcodeScanResult $scanResult): ?array
|
||||||
|
{
|
||||||
|
$vendor = $scanResult->guessBarcodeVendor();
|
||||||
|
|
||||||
|
// Mouser: use supplierPartNumber -> search provider -> provider_id
|
||||||
|
if ($vendor === 'mouser' && $scanResult->supplierPartNumber !== null
|
||||||
|
) {
|
||||||
|
// Search Mouser using the MPN
|
||||||
|
$dtos = $this->infoRetriever->searchByKeyword(
|
||||||
|
keyword: $scanResult->supplierPartNumber,
|
||||||
|
providers: ["mouser"]
|
||||||
|
);
|
||||||
|
|
||||||
|
// If there are results, provider_id is MouserPartNumber (per MouserProvider.php)
|
||||||
|
$best = $dtos[0] ?? null;
|
||||||
|
|
||||||
|
if ($best !== null) {
|
||||||
|
return [
|
||||||
|
'providerKey' => 'mouser',
|
||||||
|
'providerId' => $best->provider_id,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Digi-Key: can use customerPartNumber or supplierPartNumber directly
|
||||||
|
if ($vendor === 'digikey') {
|
||||||
|
return [
|
||||||
|
'providerKey' => 'digikey',
|
||||||
|
'providerId' => $scanResult->customerPartNumber ?? $scanResult->supplierPartNumber,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element14: can use supplierPartNumber directly
|
||||||
|
if ($vendor === 'element14') {
|
||||||
|
return [
|
||||||
|
'providerKey' => 'element14',
|
||||||
|
'providerId' => $scanResult->supplierPartNumber,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue