mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 01:25:55 +02:00
Added capability to scan Digikey barcodes and open the local part part page based on the result (#811)
* added capability to scan digikey barcodes and open the local part page based on the digikey part number or manufacturer part number * had replaced one too many doublequotes * Generalized interpretation of format06 barcodes, added ids for mouser * Renamed vendor_barcode to user_barcode in entities * Added a own class to parse EIGP114 barcodes * Added tests to EIGP114Barcode parser * Refactored code * Changed BarcodeRedirector to support the new Barcode EIGP114BarcodeScanResult class * Added possibility to just show all information contained in a barcode * Dont require trailer for EIGP114 barcodes, as digikey does not seem to put them onto their barcodes * Fixed inspection issues --------- Co-authored-by: jona <a@b.c> Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
This commit is contained in:
parent
9c99217dee
commit
9e85b70c17
20 changed files with 868 additions and 177 deletions
|
@ -42,10 +42,10 @@ declare(strict_types=1);
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Form\LabelSystem\ScanDialogType;
|
use App\Form\LabelSystem\ScanDialogType;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeScanHelper;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeRedirector;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeRedirector;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanHelper;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeScanResult;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeSourceType;
|
use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult;
|
||||||
use Doctrine\ORM\EntityNotFoundException;
|
use Doctrine\ORM\EntityNotFoundException;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
@ -77,14 +77,22 @@ class ScanController extends AbstractController
|
||||||
$mode = $form['mode']->getData();
|
$mode = $form['mode']->getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$infoModeData = null;
|
||||||
|
|
||||||
if ($input !== null) {
|
if ($input !== null) {
|
||||||
try {
|
try {
|
||||||
$scan_result = $this->barcodeNormalizer->scanBarcodeContent($input, $mode ?? null);
|
$scan_result = $this->barcodeNormalizer->scanBarcodeContent($input, $mode ?? null);
|
||||||
|
//Perform a redirect if the info mode is not enabled
|
||||||
|
if (!$form['info_mode']->getData()) {
|
||||||
try {
|
try {
|
||||||
return $this->redirect($this->barcodeParser->getRedirectURL($scan_result));
|
return $this->redirect($this->barcodeParser->getRedirectURL($scan_result));
|
||||||
} catch (EntityNotFoundException) {
|
} catch (EntityNotFoundException) {
|
||||||
$this->addFlash('success', 'scan.qr_not_found');
|
$this->addFlash('success', 'scan.qr_not_found');
|
||||||
}
|
}
|
||||||
|
} else { //Otherwise retrieve infoModeData
|
||||||
|
$infoModeData = $scan_result->getDecodedForInfoMode();
|
||||||
|
|
||||||
|
}
|
||||||
} catch (InvalidArgumentException) {
|
} catch (InvalidArgumentException) {
|
||||||
$this->addFlash('error', 'scan.format_unknown');
|
$this->addFlash('error', 'scan.format_unknown');
|
||||||
}
|
}
|
||||||
|
@ -92,6 +100,7 @@ class ScanController extends AbstractController
|
||||||
|
|
||||||
return $this->render('label_system/scanner/scanner.html.twig', [
|
return $this->render('label_system/scanner/scanner.html.twig', [
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
|
'infoModeData' => $infoModeData,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +118,7 @@ class ScanController extends AbstractController
|
||||||
throw new InvalidArgumentException('Unknown type: '.$type);
|
throw new InvalidArgumentException('Unknown type: '.$type);
|
||||||
}
|
}
|
||||||
//Construct the scan result manually, as we don't have a barcode here
|
//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_type: BarcodeScanHelper::QR_TYPE_MAP[$type],
|
||||||
target_id: $id,
|
target_id: $id,
|
||||||
//The routes are only used on the internal generated QR codes
|
//The routes are only used on the internal generated QR codes
|
||||||
|
|
|
@ -106,7 +106,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface
|
||||||
$partLot2->setComment('Test');
|
$partLot2->setComment('Test');
|
||||||
$partLot2->setNeedsRefill(true);
|
$partLot2->setNeedsRefill(true);
|
||||||
$partLot2->setStorageLocation($manager->find(StorageLocation::class, 3));
|
$partLot2->setStorageLocation($manager->find(StorageLocation::class, 3));
|
||||||
$partLot2->setVendorBarcode('lot2_vendor_barcode');
|
$partLot2->setUserBarcode('lot2_vendor_barcode');
|
||||||
$part->addPartLot($partLot2);
|
$part->addPartLot($partLot2);
|
||||||
|
|
||||||
$orderdetail = new Orderdetail();
|
$orderdetail = new Orderdetail();
|
||||||
|
|
|
@ -68,7 +68,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
#[ORM\Index(columns: ['needs_refill'], name: 'part_lots_idx_needs_refill')]
|
#[ORM\Index(columns: ['needs_refill'], name: 'part_lots_idx_needs_refill')]
|
||||||
#[ORM\Index(columns: ['vendor_barcode'], name: 'part_lots_idx_barcode')]
|
#[ORM\Index(columns: ['vendor_barcode'], name: 'part_lots_idx_barcode')]
|
||||||
#[ValidPartLot]
|
#[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(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(security: 'is_granted("read", object)'),
|
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)
|
* @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'])]
|
#[Groups(['part_lot:read', 'part_lot:write'])]
|
||||||
#[Length(max: 255)]
|
#[Length(max: 255)]
|
||||||
protected ?string $vendor_barcode = null;
|
protected ?string $user_barcode = null;
|
||||||
|
|
||||||
public function __clone()
|
public function __clone()
|
||||||
{
|
{
|
||||||
|
@ -375,19 +375,19 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
|
||||||
* null if no barcode is set.
|
* null if no barcode is set.
|
||||||
* @return string|null
|
* @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).
|
* 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
|
* @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;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,9 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\LabelSystem;
|
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\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
@ -55,6 +56,8 @@ class ScanDialogType extends AbstractType
|
||||||
{
|
{
|
||||||
$builder->add('input', TextType::class, [
|
$builder->add('input', TextType::class, [
|
||||||
'label' => 'scan_dialog.input',
|
'label' => 'scan_dialog.input',
|
||||||
|
//Do not trim the input, otherwise this damages Format06 barcodes which end with non-printable characters
|
||||||
|
'trim' => false,
|
||||||
'attr' => [
|
'attr' => [
|
||||||
'autofocus' => true,
|
'autofocus' => true,
|
||||||
'id' => 'scan_dialog_input',
|
'id' => 'scan_dialog_input',
|
||||||
|
@ -71,9 +74,14 @@ class ScanDialogType extends AbstractType
|
||||||
null => 'scan_dialog.mode.auto',
|
null => 'scan_dialog.mode.auto',
|
||||||
BarcodeSourceType::INTERNAL => 'scan_dialog.mode.internal',
|
BarcodeSourceType::INTERNAL => 'scan_dialog.mode.internal',
|
||||||
BarcodeSourceType::IPN => 'scan_dialog.mode.ipn',
|
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, [
|
$builder->add('submit', SubmitType::class, [
|
||||||
|
|
|
@ -103,8 +103,8 @@ class PartLotType extends AbstractType
|
||||||
'help' => 'part_lot.owner.help',
|
'help' => 'part_lot.owner.help',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$builder->add('vendor_barcode', TextType::class, [
|
$builder->add('user_barcode', TextType::class, [
|
||||||
'label' => 'part_lot.edit.vendor_barcode',
|
'label' => 'part_lot.edit.user_barcode',
|
||||||
'help' => 'part_lot.edit.vendor_barcode.help',
|
'help' => 'part_lot.edit.vendor_barcode.help',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -160,9 +160,8 @@ final class DTOtoEntityConverter
|
||||||
|
|
||||||
//Try to map the category to an existing entity (but never create a new one)
|
//Try to map the category to an existing entity (but never create a new one)
|
||||||
if ($dto->category) {
|
if ($dto->category) {
|
||||||
/** @var CategoryRepository<Category> $categoryRepo */
|
//@phpstan-ignore-next-line For some reason php does not recognize the repo returns a category
|
||||||
$categoryRepo = $this->em->getRepository(Category::class);
|
$entity->setCategory($this->em->getRepository(Category::class)->findForInfoProvider($dto->category));
|
||||||
$entity->setCategory($categoryRepo->findForInfoProvider($dto->category));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$entity->setManufacturer($this->getOrCreateEntity(Manufacturer::class, $dto->manufacturer));
|
$entity->setManufacturer($this->getOrCreateEntity(Manufacturer::class, $dto->manufacturer));
|
||||||
|
|
166
src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php
Normal file
166
src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,7 +39,7 @@ declare(strict_types=1);
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Services\LabelSystem\Barcodes;
|
namespace App\Services\LabelSystem\BarcodeScanner;
|
||||||
|
|
||||||
use App\Entity\LabelSystem\LabelSupportedElement;
|
use App\Entity\LabelSystem\LabelSupportedElement;
|
||||||
use App\Entity\Parts\Part;
|
use App\Entity\Parts\Part;
|
||||||
|
@ -75,30 +75,39 @@ final class BarcodeScanHelper
|
||||||
* will try to guess the type.
|
* will try to guess the type.
|
||||||
* @param string $input
|
* @param string $input
|
||||||
* @param BarcodeSourceType|null $type
|
* @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
|
//Do specific parsing
|
||||||
if ($type === BarcodeSourceType::INTERNAL) {
|
if ($type === BarcodeSourceType::INTERNAL) {
|
||||||
return $this->parseInternalBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode');
|
return $this->parseInternalBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode');
|
||||||
}
|
}
|
||||||
if ($type === BarcodeSourceType::VENDOR) {
|
if ($type === BarcodeSourceType::USER_DEFINED) {
|
||||||
return $this->parseVendorBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode');
|
return $this->parseUserDefinedBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode');
|
||||||
}
|
}
|
||||||
if ($type === BarcodeSourceType::IPN) {
|
if ($type === BarcodeSourceType::IPN) {
|
||||||
return $this->parseIPNBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode');
|
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
|
//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);
|
$result = $this->parseInternalBarcode($input);
|
||||||
|
|
||||||
if ($result !== null) {
|
if ($result !== null) {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Try to parse as vendor barcode
|
//Try to parse as User defined barcode
|
||||||
$result = $this->parseVendorBarcode($input);
|
$result = $this->parseUserDefinedBarcode($input);
|
||||||
if ($result !== null) {
|
if ($result !== null) {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
@ -112,11 +121,16 @@ final class BarcodeScanHelper
|
||||||
throw new InvalidArgumentException('Unknown barcode');
|
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);
|
$lot_repo = $this->entityManager->getRepository(PartLot::class);
|
||||||
//Find only the first result
|
//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) {
|
if (count($results) === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -124,14 +138,14 @@ final class BarcodeScanHelper
|
||||||
//We found a part, so use it to create the result
|
//We found a part, so use it to create the result
|
||||||
$lot = $results[0];
|
$lot = $results[0];
|
||||||
|
|
||||||
return new BarcodeScanResult(
|
return new LocalBarcodeScanResult(
|
||||||
target_type: LabelSupportedElement::PART_LOT,
|
target_type: LabelSupportedElement::PART_LOT,
|
||||||
target_id: $lot->getID(),
|
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);
|
$part_repo = $this->entityManager->getRepository(Part::class);
|
||||||
//Find only the first result
|
//Find only the first result
|
||||||
|
@ -143,7 +157,7 @@ final class BarcodeScanHelper
|
||||||
//We found a part, so use it to create the result
|
//We found a part, so use it to create the result
|
||||||
$part = $results[0];
|
$part = $results[0];
|
||||||
|
|
||||||
return new BarcodeScanResult(
|
return new LocalBarcodeScanResult(
|
||||||
target_type: LabelSupportedElement::PART,
|
target_type: LabelSupportedElement::PART,
|
||||||
target_id: $part->getID(),
|
target_id: $part->getID(),
|
||||||
source_type: BarcodeSourceType::IPN
|
source_type: BarcodeSourceType::IPN
|
||||||
|
@ -155,9 +169,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
|
* 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.
|
* not be found in the database, an exception is thrown.
|
||||||
* @param string $input
|
* @param string $input
|
||||||
* @return BarcodeScanResult|null
|
* @return LocalBarcodeScanResult|null
|
||||||
*/
|
*/
|
||||||
private function parseInternalBarcode(string $input): ?BarcodeScanResult
|
private function parseInternalBarcode(string $input): ?LocalBarcodeScanResult
|
||||||
{
|
{
|
||||||
$input = trim($input);
|
$input = trim($input);
|
||||||
$matches = [];
|
$matches = [];
|
||||||
|
@ -167,7 +181,7 @@ final class BarcodeScanHelper
|
||||||
|
|
||||||
//Extract parts from QR code's URL
|
//Extract parts from QR code's URL
|
||||||
if (preg_match('#^https?://.*/scan/(\w+)/(\d+)/?$#', $input, $matches)) {
|
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_type: self::QR_TYPE_MAP[strtolower($matches[1])],
|
||||||
target_id: (int) $matches[2],
|
target_id: (int) $matches[2],
|
||||||
source_type: BarcodeSourceType::INTERNAL
|
source_type: BarcodeSourceType::INTERNAL
|
||||||
|
@ -183,7 +197,7 @@ final class BarcodeScanHelper
|
||||||
throw new InvalidArgumentException('Unknown prefix '.$prefix);
|
throw new InvalidArgumentException('Unknown prefix '.$prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BarcodeScanResult(
|
return new LocalBarcodeScanResult(
|
||||||
target_type: self::PREFIX_TYPE_MAP[$prefix],
|
target_type: self::PREFIX_TYPE_MAP[$prefix],
|
||||||
target_id: $id,
|
target_id: $id,
|
||||||
source_type: BarcodeSourceType::INTERNAL
|
source_type: BarcodeSourceType::INTERNAL
|
||||||
|
@ -199,7 +213,7 @@ final class BarcodeScanHelper
|
||||||
throw new InvalidArgumentException('Unknown prefix '.$prefix);
|
throw new InvalidArgumentException('Unknown prefix '.$prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BarcodeScanResult(
|
return new LocalBarcodeScanResult(
|
||||||
target_type: self::PREFIX_TYPE_MAP[$prefix],
|
target_type: self::PREFIX_TYPE_MAP[$prefix],
|
||||||
target_id: $id,
|
target_id: $id,
|
||||||
source_type: BarcodeSourceType::INTERNAL
|
source_type: BarcodeSourceType::INTERNAL
|
||||||
|
@ -208,7 +222,7 @@ final class BarcodeScanHelper
|
||||||
|
|
||||||
//Legacy Part-DB location labels used $L00336 format
|
//Legacy Part-DB location labels used $L00336 format
|
||||||
if (preg_match('#^\$L(\d{5,})$#', $input, $matches)) {
|
if (preg_match('#^\$L(\d{5,})$#', $input, $matches)) {
|
||||||
return new BarcodeScanResult(
|
return new LocalBarcodeScanResult(
|
||||||
target_type: LabelSupportedElement::STORELOCATION,
|
target_type: LabelSupportedElement::STORELOCATION,
|
||||||
target_id: (int) $matches[1],
|
target_id: (int) $matches[1],
|
||||||
source_type: BarcodeSourceType::INTERNAL
|
source_type: BarcodeSourceType::INTERNAL
|
||||||
|
@ -217,7 +231,7 @@ final class BarcodeScanHelper
|
||||||
|
|
||||||
//Legacy Part-DB used EAN8 barcodes for part labels. Format 0000001(2) (note the optional 8th digit => checksum)
|
//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)) {
|
if (preg_match('#^(\d{7})\d?$#', $input, $matches)) {
|
||||||
return new BarcodeScanResult(
|
return new LocalBarcodeScanResult(
|
||||||
target_type: LabelSupportedElement::PART,
|
target_type: LabelSupportedElement::PART,
|
||||||
target_id: (int) $matches[1],
|
target_id: (int) $matches[1],
|
||||||
source_type: BarcodeSourceType::INTERNAL
|
source_type: BarcodeSourceType::INTERNAL
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* 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\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<string, string|int|float|null>
|
||||||
|
*/
|
||||||
|
public function getDecodedForInfoMode(): array;
|
||||||
|
}
|
|
@ -21,7 +21,7 @@
|
||||||
declare(strict_types=1);
|
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
|
* This enum represents the different types, where a barcode/QR-code can be generated from
|
||||||
|
@ -32,9 +32,14 @@ enum BarcodeSourceType
|
||||||
case INTERNAL;
|
case INTERNAL;
|
||||||
/** This barcode is containing an internal part number (IPN) */
|
/** This barcode is containing an internal part number (IPN) */
|
||||||
case IPN;
|
case IPN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This barcode is a custom barcode from a third party like a vendor, which was set via the vendor_barcode
|
* This barcode is a user defined barcode defined on a part lot
|
||||||
* field of a part lot.
|
|
||||||
*/
|
*/
|
||||||
case VENDOR;
|
case USER_DEFINED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EIGP114 formatted barcodes like used by digikey, mouser, etc.
|
||||||
|
*/
|
||||||
|
case EIGP114;
|
||||||
}
|
}
|
|
@ -0,0 +1,332 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* 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\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<string, string> $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 [)><RS>06<GS>
|
||||||
|
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 <GS> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,14 +21,15 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
namespace App\Services\LabelSystem\Barcodes;
|
namespace App\Services\LabelSystem\BarcodeScanner;
|
||||||
|
|
||||||
use App\Entity\LabelSystem\LabelSupportedElement;
|
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 function __construct(
|
||||||
public readonly LabelSupportedElement $target_type,
|
public readonly LabelSupportedElement $target_type,
|
||||||
|
@ -36,4 +37,13 @@ class BarcodeScanResult
|
||||||
public readonly BarcodeSourceType $source_type,
|
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,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,89 +0,0 @@
|
||||||
<?php
|
|
||||||
/*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,4 +23,22 @@
|
||||||
|
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
|
||||||
|
|
||||||
|
{% if infoModeData %}
|
||||||
|
<hr>
|
||||||
|
<h4>{% trans %}label_scanner.decoded_info.title{% endtrans %}</h4>
|
||||||
|
|
||||||
|
<table class="table table-striped table-hover table-bordered table-sm">
|
||||||
|
<tbody>
|
||||||
|
{% for key, value in infoModeData %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ key }}</td>
|
||||||
|
<td><code>{{ value }}</code></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -108,7 +108,7 @@
|
||||||
<div class="collapse" id="{{ id }}">
|
<div class="collapse" id="{{ id }}">
|
||||||
{{ form_row(form.comment) }}
|
{{ form_row(form.comment) }}
|
||||||
{{ form_row(form.owner) }}
|
{{ form_row(form.owner) }}
|
||||||
{{ form_row(form.vendor_barcode) }}
|
{{ form_row(form.user_barcode) }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -148,7 +148,7 @@ class PartMergerTest extends KernelTestCase
|
||||||
public function testMergeOfPartLots(): void
|
public function testMergeOfPartLots(): void
|
||||||
{
|
{
|
||||||
$lot1 = (new PartLot())->setAmount(2)->setNeedsRefill(true);
|
$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);
|
$lot3 = (new PartLot())->setDescription('lot3')->setAmount(3);
|
||||||
$lot4 = (new PartLot())->setDescription('lot4')->setComment('comment');
|
$lot4 = (new PartLot())->setDescription('lot4')->setComment('comment');
|
||||||
|
|
||||||
|
|
|
@ -39,12 +39,12 @@ declare(strict_types=1);
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Tests\Services\LabelSystem\Barcodes;
|
namespace App\Tests\Services\LabelSystem\BarcodeScanner;
|
||||||
|
|
||||||
use App\Entity\LabelSystem\LabelSupportedElement;
|
use App\Entity\LabelSystem\LabelSupportedElement;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeRedirector;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeRedirector;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeScanResult;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeSourceType;
|
use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult;
|
||||||
use Doctrine\ORM\EntityNotFoundException;
|
use Doctrine\ORM\EntityNotFoundException;
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
|
@ -60,17 +60,17 @@ final class BarcodeRedirectorTest extends KernelTestCase
|
||||||
|
|
||||||
public static function urlDataProvider(): \Iterator
|
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)
|
//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 LocalBarcodeScanResult(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::STORELOCATION, 1, BarcodeSourceType::INTERNAL), '/en/store_location/1/parts'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider urlDataProvider
|
* @dataProvider urlDataProvider
|
||||||
* @group DB
|
* @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));
|
$this->assertSame($url, $this->service->getRedirectURL($scanResult));
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ final class BarcodeRedirectorTest extends KernelTestCase
|
||||||
{
|
{
|
||||||
$this->expectException(EntityNotFoundException::class);
|
$this->expectException(EntityNotFoundException::class);
|
||||||
//If we encounter an invalid lot, we must throw an exception
|
//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));
|
12_345_678, BarcodeSourceType::INTERNAL));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -39,13 +39,12 @@ declare(strict_types=1);
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Tests\Services\LabelSystem\Barcodes;
|
namespace App\Tests\Services\LabelSystem\BarcodeScanner;
|
||||||
|
|
||||||
use App\Entity\LabelSystem\LabelSupportedElement;
|
use App\Entity\LabelSystem\LabelSupportedElement;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeScanHelper;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanHelper;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeScanResult;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeSourceType;
|
use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult;
|
||||||
use Com\Tecnick\Barcode\Barcode;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
|
||||||
class BarcodeScanHelperTest extends WebTestCase
|
class BarcodeScanHelperTest extends WebTestCase
|
||||||
|
@ -61,55 +60,55 @@ class BarcodeScanHelperTest extends WebTestCase
|
||||||
public static function dataProvider(): \Iterator
|
public static function dataProvider(): \Iterator
|
||||||
{
|
{
|
||||||
//QR URL content:
|
//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'];
|
'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'];
|
'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'];
|
'http://foo.bar/part-db/scan/location/4'];
|
||||||
|
|
||||||
//Current Code39 format:
|
//Current Code39 format:
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL),
|
||||||
'L0010'];
|
'L0010'];
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 123, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 123, BarcodeSourceType::INTERNAL),
|
||||||
'L0123'];
|
'L0123'];
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 123456, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 123456, BarcodeSourceType::INTERNAL),
|
||||||
'L123456'];
|
'L123456'];
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::PART, 2, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 2, BarcodeSourceType::INTERNAL),
|
||||||
'P0002'];
|
'P0002'];
|
||||||
|
|
||||||
//Development phase Code39 barcodes:
|
//Development phase Code39 barcodes:
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL),
|
||||||
'L-000010'];
|
'L-000010'];
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL),
|
||||||
'Lß000010'];
|
'Lß000010'];
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
|
||||||
'P-000123'];
|
'P-000123'];
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 123, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 123, BarcodeSourceType::INTERNAL),
|
||||||
'S-000123'];
|
'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'];
|
'L-12345678'];
|
||||||
|
|
||||||
//Legacy storelocation format
|
//Legacy storelocation format
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 336, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 336, BarcodeSourceType::INTERNAL),
|
||||||
'$L00336'];
|
'$L00336'];
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 12_345_678, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 12_345_678, BarcodeSourceType::INTERNAL),
|
||||||
'$L12345678'];
|
'$L12345678'];
|
||||||
|
|
||||||
//Legacy Part format
|
//Legacy Part format
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
|
||||||
'0000123'];
|
'0000123'];
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
|
||||||
'00001236'];
|
'00001236'];
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::PART, 1_234_567, BarcodeSourceType::INTERNAL),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 1_234_567, BarcodeSourceType::INTERNAL),
|
||||||
'12345678'];
|
'12345678'];
|
||||||
|
|
||||||
//Test IPN barcode
|
//Test IPN barcode
|
||||||
yield [new BarcodeScanResult(LabelSupportedElement::PART, 2, BarcodeSourceType::IPN),
|
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 2, BarcodeSourceType::IPN),
|
||||||
'IPN123'];
|
'IPN123'];
|
||||||
|
|
||||||
//Test vendor barcode
|
//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'];
|
'lot2_vendor_barcode'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +130,7 @@ class BarcodeScanHelperTest extends WebTestCase
|
||||||
/**
|
/**
|
||||||
* @dataProvider dataProvider
|
* @dataProvider dataProvider
|
||||||
*/
|
*/
|
||||||
public function testNormalizeBarcodeContent(BarcodeScanResult $expected, string $input): void
|
public function testNormalizeBarcodeContent(LocalBarcodeScanResult $expected, string $input): void
|
||||||
{
|
{
|
||||||
$this->assertEquals($expected, $this->service->scanBarcodeContent($input));
|
$this->assertEquals($expected, $this->service->scanBarcodeContent($input));
|
||||||
}
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12263,5 +12263,35 @@ Please note, that you can not impersonate a disabled user. If you try you will g
|
||||||
<target>Category could not be automatically determined by the info provider. Review the data and select the category manually.</target>
|
<target>Category could not be automatically determined by the info provider. Review the data and select the category manually.</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
|
<unit id="v6oyTac" name="part_lot.edit.user_barcode">
|
||||||
|
<segment>
|
||||||
|
<source>part_lot.edit.user_barcode</source>
|
||||||
|
<target>User barcode</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="dXhegcm" name="scan_dialog.mode.user">
|
||||||
|
<segment>
|
||||||
|
<source>scan_dialog.mode.user</source>
|
||||||
|
<target>User defined barcode (configured at part lot)</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="sSAJDdr" name="scan_dialog.mode.eigp">
|
||||||
|
<segment>
|
||||||
|
<source>scan_dialog.mode.eigp</source>
|
||||||
|
<target>EIGP 114 barcode (e.g. the datamatrix codes on digikey and mouser orders)</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="QSMS_Bd" name="scan_dialog.info_mode">
|
||||||
|
<segment>
|
||||||
|
<source>scan_dialog.info_mode</source>
|
||||||
|
<target>Info mode (Decode barcode and show its contents, but do not redirect to part)</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="k5Gvkgp" name="label_scanner.decoded_info.title">
|
||||||
|
<segment>
|
||||||
|
<source>label_scanner.decoded_info.title</source>
|
||||||
|
<target>Decoded information</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue