diff --git a/src/Form/LabelSystem/ScanDialogType.php b/src/Form/LabelSystem/ScanDialogType.php
index 13ff8e6f..0a67467f 100644
--- a/src/Form/LabelSystem/ScanDialogType.php
+++ b/src/Form/LabelSystem/ScanDialogType.php
@@ -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',
},
]);
diff --git a/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php
index 2de7c035..1364e6c1 100644
--- a/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php
+++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php
@@ -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
*/
diff --git a/src/Services/LabelSystem/BarcodeScanner/BarcodeScanHelper.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanHelper.php
index e5930b36..c9cce95a 100644
--- a/src/Services/LabelSystem/BarcodeScanner/BarcodeScanHelper.php
+++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanHelper.php
@@ -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);
diff --git a/src/Services/LabelSystem/BarcodeScanner/BarcodeSourceType.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeSourceType.php
index 40f707de..8f1dc72e 100644
--- a/src/Services/LabelSystem/BarcodeScanner/BarcodeSourceType.php
+++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeSourceType.php
@@ -42,4 +42,7 @@ enum BarcodeSourceType
* EIGP114 formatted barcodes like used by digikey, mouser, etc.
*/
case EIGP114;
-}
\ No newline at end of file
+
+ /** For LCSC.com formatted QR codes */
+ case LCSC;
+}
diff --git a/src/Services/LabelSystem/BarcodeScanner/LCSCBarcodeScanResult.php b/src/Services/LabelSystem/BarcodeScanner/LCSCBarcodeScanResult.php
new file mode 100644
index 00000000..9a87951f
--- /dev/null
+++ b/src/Services/LabelSystem/BarcodeScanner/LCSCBarcodeScanResult.php
@@ -0,0 +1,127 @@
+ $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);
+ }
+}
diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf
index f7f10146..0e79a6ed 100644
--- a/translations/messages.en.xlf
+++ b/translations/messages.en.xlf
@@ -12027,6 +12027,12 @@ Please note, that you can not impersonate a disabled user. If you try you will g
EIGP 114 barcode (e.g. the datamatrix codes on digikey and mouser orders)
+
+
+ scan_dialog.mode.lcsc
+ LCSC.com barcode
+
+
scan_dialog.info_mode