mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-01-19 16:49:34 +00:00
added handling of LCSC barcode decoding and part loading on Label Scanner
This commit is contained in:
parent
766ba07105
commit
2e793e89fa
6 changed files with 205 additions and 2 deletions
|
|
@ -75,7 +75,8 @@ class ScanDialogType extends AbstractType
|
|||
BarcodeSourceType::INTERNAL => 'scan_dialog.mode.internal',
|
||||
BarcodeSourceType::IPN => 'scan_dialog.mode.ipn',
|
||||
BarcodeSourceType::USER_DEFINED => 'scan_dialog.mode.user',
|
||||
BarcodeSourceType::EIGP114 => 'scan_dialog.mode.eigp'
|
||||
BarcodeSourceType::EIGP114 => 'scan_dialog.mode.eigp',
|
||||
BarcodeSourceType::LCSC => 'scan_dialog.mode.lcsc',
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ use App\Entity\LabelSystem\LabelSupportedElement;
|
|||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Repository\Parts\PartRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityNotFoundException;
|
||||
use InvalidArgumentException;
|
||||
|
|
@ -77,6 +78,10 @@ final class BarcodeRedirector
|
|||
return $this->getURLVendorBarcode($barcodeScan);
|
||||
}
|
||||
|
||||
if ($barcodeScan instanceof LCSCBarcodeScanResult) {
|
||||
return $this->getURLLCSCBarcode($barcodeScan);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unknown $barcodeScan type: '.get_class($barcodeScan));
|
||||
}
|
||||
|
||||
|
|
@ -102,6 +107,54 @@ final class BarcodeRedirector
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL to a part from a scan of the LCSC Barcode
|
||||
*/
|
||||
private function getURLLCSCBarcode(LCSCBarcodeScanResult $barcodeScan): string
|
||||
{
|
||||
$part = $this->getPartFromLCSC($barcodeScan);
|
||||
return $this->urlGenerator->generate('app_part_show', ['id' => $part->getID()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve LCSC barcode -> Part.
|
||||
* Strategy:
|
||||
* 1) Try providerReference.provider_id == pc (LCSC "Cxxxxxx") if you store it there
|
||||
* 2) Fallback to manufacturer_product_number == pm (MPN)
|
||||
* Returns first match (consistent with EIGP114 logic)
|
||||
*/
|
||||
private function getPartFromLCSC(LCSCBarcodeScanResult $barcodeScan): Part
|
||||
{
|
||||
// Try LCSC code (pc) as provider id if available
|
||||
$pc = $barcodeScan->getPC(); // e.g. C138033
|
||||
if ($pc) {
|
||||
$qb = $this->em->getRepository(Part::class)->createQueryBuilder('part');
|
||||
$qb->where($qb->expr()->like('LOWER(part.providerReference.provider_id)', 'LOWER(:vendor_id)'));
|
||||
$qb->setParameter('vendor_id', $pc);
|
||||
$results = $qb->getQuery()->getResult();
|
||||
if ($results) {
|
||||
return $results[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to MPN (pm)
|
||||
$pm = $barcodeScan->getPM(); // e.g. RC0402FR-071ML
|
||||
if (!$pm) {
|
||||
throw new EntityNotFoundException();
|
||||
}
|
||||
|
||||
$mpnQb = $this->em->getRepository(Part::class)->createQueryBuilder('part');
|
||||
$mpnQb->where($mpnQb->expr()->like('LOWER(part.manufacturer_product_number)', 'LOWER(:mpn)'));
|
||||
$mpnQb->setParameter('mpn', $pm);
|
||||
|
||||
$results = $mpnQb->getQuery()->getResult();
|
||||
if ($results) {
|
||||
return $results[0];
|
||||
}
|
||||
|
||||
throw new EntityNotFoundException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL to a part from a scan of a Vendor Barcode
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -92,6 +92,9 @@ final class BarcodeScanHelper
|
|||
if ($type === BarcodeSourceType::EIGP114) {
|
||||
return $this->parseEIGP114Barcode($input);
|
||||
}
|
||||
if ($type === BarcodeSourceType::LCSC) {
|
||||
return $this->parseLCSCBarcode($input);
|
||||
}
|
||||
|
||||
//Null means auto and we try the different formats
|
||||
$result = $this->parseInternalBarcode($input);
|
||||
|
|
@ -117,6 +120,11 @@ final class BarcodeScanHelper
|
|||
return $result;
|
||||
}
|
||||
|
||||
// Try LCSC barcode
|
||||
if (LCSCBarcodeScanResult::looksLike($input)) {
|
||||
return $this->parseLCSCBarcode($input);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unknown barcode');
|
||||
}
|
||||
|
||||
|
|
@ -125,6 +133,11 @@ final class BarcodeScanHelper
|
|||
return EIGP114BarcodeScanResult::parseFormat06Code($input);
|
||||
}
|
||||
|
||||
private function parseLCSCBarcode(string $input): LCSCBarcodeScanResult
|
||||
{
|
||||
return LCSCBarcodeScanResult::parse($input);
|
||||
}
|
||||
|
||||
private function parseUserDefinedBarcode(string $input): ?LocalBarcodeScanResult
|
||||
{
|
||||
$lot_repo = $this->entityManager->getRepository(PartLot::class);
|
||||
|
|
|
|||
|
|
@ -42,4 +42,7 @@ enum BarcodeSourceType
|
|||
* EIGP114 formatted barcodes like used by digikey, mouser, etc.
|
||||
*/
|
||||
case EIGP114;
|
||||
}
|
||||
|
||||
/** For LCSC.com formatted QR codes */
|
||||
case LCSC;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\LabelSystem\BarcodeScanner;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* This class represents the content of a lcsc.com barcode
|
||||
* Its data structure is represented by {pbn:...,on:...,pc:...,pm:...,qty:...}
|
||||
*/
|
||||
class LCSCBarcodeScanResult implements BarcodeScanResultInterface
|
||||
{
|
||||
/**
|
||||
* @param array<string, string> $fields
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $fields,
|
||||
public readonly string $raw_input,
|
||||
) {}
|
||||
|
||||
public function getSourceType(): BarcodeSourceType
|
||||
{
|
||||
return BarcodeSourceType::LCSC;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null The manufactures part number
|
||||
*/
|
||||
public function getPM(): ?string
|
||||
{
|
||||
$v = $this->fields['pm'] ?? null;
|
||||
$v = $v !== null ? trim($v) : null;
|
||||
return ($v === '') ? null : $v;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null The lcsc.com part number
|
||||
*/
|
||||
public function getPC(): ?string
|
||||
{
|
||||
$v = $this->fields['pc'] ?? null;
|
||||
$v = $v !== null ? trim($v) : null;
|
||||
return ($v === '') ? null : $v;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|float[]|int[]|null[]|string[] An array of fields decoded from the barcode
|
||||
*/
|
||||
public function getDecodedForInfoMode(): array
|
||||
{
|
||||
// Keep it human-friendly
|
||||
return [
|
||||
'Barcode type' => 'LCSC',
|
||||
'MPN (pm)' => $this->getPM() ?? '',
|
||||
'LCSC code (pc)' => $this->getPC() ?? '',
|
||||
'Qty' => $this->fields['qty'] ?? '',
|
||||
'Order No (on)' => $this->fields['on'] ?? '',
|
||||
'Pick Batch (pbn)' => $this->fields['pbn'] ?? '',
|
||||
'Warehouse (wc)' => $this->fields['wc'] ?? '',
|
||||
'Country/Channel (cc)' => $this->fields['cc'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the barcode data to see if the input matches the expected format used by lcsc.com
|
||||
* @param string $input
|
||||
* @return bool
|
||||
*/
|
||||
public static function looksLike(string $input): bool
|
||||
{
|
||||
$s = trim($input);
|
||||
|
||||
// Your example: {pbn:...,on:...,pc:...,pm:...,qty:...}
|
||||
if (!str_starts_with($s, '{') || !str_ends_with($s, '}')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must contain at least pm: and pc: (common for LCSC labels)
|
||||
return (stripos($s, 'pm:') !== false) && (stripos($s, 'pc:') !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the barcode input string into the fields used by lcsc.com
|
||||
* @param string $input
|
||||
* @return self
|
||||
*/
|
||||
public static function parse(string $input): self
|
||||
{
|
||||
$raw = trim($input);
|
||||
|
||||
if (!self::looksLike($raw)) {
|
||||
throw new InvalidArgumentException('Not an LCSC barcode');
|
||||
}
|
||||
|
||||
$inner = trim($raw);
|
||||
$inner = substr($inner, 1, -1); // remove { }
|
||||
|
||||
$fields = [];
|
||||
|
||||
// This format is comma-separated pairs, values do not contain commas in your sample.
|
||||
$pairs = array_filter(array_map('trim', explode(',', $inner)));
|
||||
|
||||
foreach ($pairs as $pair) {
|
||||
$pos = strpos($pair, ':');
|
||||
if ($pos === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$k = trim(substr($pair, 0, $pos));
|
||||
$v = trim(substr($pair, $pos + 1));
|
||||
|
||||
if ($k === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fields[$k] = $v;
|
||||
}
|
||||
|
||||
if (!isset($fields['pm']) || trim($fields['pm']) === '') {
|
||||
throw new InvalidArgumentException('LCSC barcode missing pm field');
|
||||
}
|
||||
|
||||
return new self($fields, $raw);
|
||||
}
|
||||
}
|
||||
|
|
@ -12027,6 +12027,12 @@ Please note, that you can not impersonate a disabled user. If you try you will g
|
|||
<target>EIGP 114 barcode (e.g. the datamatrix codes on digikey and mouser orders)</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="lAzDdr" name="scan_dialog.mode.lcsc">
|
||||
<segment state="translated">
|
||||
<source>scan_dialog.mode.lcsc</source>
|
||||
<target>LCSC.com barcode</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="QSMS_Bd" name="scan_dialog.info_mode">
|
||||
<segment state="translated">
|
||||
<source>scan_dialog.info_mode</source>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue