mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-01-20 09:09:33 +00:00
Generalized interpretation of format06 barcodes, added ids for mouser
This commit is contained in:
parent
67bf365b4a
commit
03ec4a9f84
3 changed files with 116 additions and 46 deletions
|
|
@ -42,6 +42,7 @@ declare(strict_types=1);
|
||||||
namespace App\Services\LabelSystem\Barcodes;
|
namespace App\Services\LabelSystem\Barcodes;
|
||||||
|
|
||||||
use App\Entity\LabelSystem\LabelSupportedElement;
|
use App\Entity\LabelSystem\LabelSupportedElement;
|
||||||
|
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 Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
@ -109,14 +110,15 @@ final class BarcodeRedirector
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a part from a scan of a Vendor Barcode by filtering for parts
|
* 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 the
|
* with the same Info Provider Id or, if that fails, by looking for parts with a
|
||||||
* same manufacturer product number. Only returns the first matching part.
|
* matching manufacturer product number. Only returns the first matching part.
|
||||||
*/
|
*/
|
||||||
private function getPartFromVendor(VendorBarcodeScanResult $barcodeScan) : Part
|
private function getPartFromVendor(VendorBarcodeScanResult $barcodeScan) : Part
|
||||||
{
|
{
|
||||||
// first check via the info provider ID (e.g. Vendor ID). This might fail if the part was not added via
|
// 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
|
// the info provider system or if the part was bought from a different vendor than the data was retrieved
|
||||||
// from.
|
// from.
|
||||||
|
if($barcodeScan->vendor_part_number) {
|
||||||
$qb = $this->em->getRepository(Part::class)->createQueryBuilder('part');
|
$qb = $this->em->getRepository(Part::class)->createQueryBuilder('part');
|
||||||
//Lower() to be case insensitive
|
//Lower() to be case insensitive
|
||||||
$qb->where($qb->expr()->like('LOWER(part.providerReference.provider_id)', 'LOWER(:vendor_id)'));
|
$qb->where($qb->expr()->like('LOWER(part.providerReference.provider_id)', 'LOWER(:vendor_id)'));
|
||||||
|
|
@ -125,15 +127,34 @@ final class BarcodeRedirector
|
||||||
if ($results) {
|
if ($results) {
|
||||||
return $results[0];
|
return $results[0];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$barcodeScan->manufacturer_part_number){
|
||||||
|
throw new EntityNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
//Fallback to the manufacturer part number. This may return false positives, since it is common for
|
//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
|
//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
|
//We assume the user is able to realize when this returns the wrong part
|
||||||
//If there's barcodes that contain the vendor we could make this more robust, but at least Digikey doesn't.
|
//If the barcode specifies the manufacturer we try to use that as well
|
||||||
$qb = $this->em->getRepository(Part::class)->createQueryBuilder('part');
|
$mpnQb = $this->em->getRepository(Part::class)->createQueryBuilder('part');
|
||||||
$qb->where($qb->expr()->like('LOWER(part.manufacturer_product_number)', 'LOWER(:mpn)'));
|
$mpnQb->where($mpnQb->expr()->like('LOWER(part.manufacturer_product_number)', 'LOWER(:mpn)'));
|
||||||
$qb->setParameter('mpn', $barcodeScan->manufacturer_part_number);
|
$mpnQb->setParameter('mpn', $barcodeScan->manufacturer_part_number);
|
||||||
$results = $qb->getQuery()->getResult();
|
|
||||||
|
if($barcodeScan->manufacturer){
|
||||||
|
$manufacturerQb = $this->em->getRepository(Manufacturer::class)->createQueryBuilder("manufacturer");
|
||||||
|
$manufacturerQb->where($manufacturerQb->expr()->like("LOWER(manufacturer.name)", "LOWER(:manufacturer_name)"));
|
||||||
|
$manufacturerQb->setParameter("manufacturer_name", $barcodeScan->manufacturer);
|
||||||
|
$manufacturers = $manufacturerQb->getQuery()->getResult();
|
||||||
|
|
||||||
|
if($manufacturers) {
|
||||||
|
$mpnQb->andWhere($mpnQb->expr()->eq("part.manufacturer", ":manufacturer"));
|
||||||
|
$mpnQb->setParameter("manufacturer", $manufacturers);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = $mpnQb->getQuery()->getResult();
|
||||||
if($results){
|
if($results){
|
||||||
return $results[0];
|
return $results[0];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ final class BarcodeScanHelper
|
||||||
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::VENDOR) {
|
if ($type === BarcodeSourceType::VENDOR) {
|
||||||
return $this->parseDigikeyBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode');
|
return $this->parseFormat06Barcode($input) ?? throw new InvalidArgumentException('Could not parse barcode');
|
||||||
}
|
}
|
||||||
|
|
||||||
//Null means auto and we try the different formats
|
//Null means auto and we try the different formats
|
||||||
|
|
@ -112,7 +112,7 @@ final class BarcodeScanHelper
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->parseDigikeyBarcode($input);
|
$result = $this->parseFormat06Barcode($input);
|
||||||
if ($result !== null) {
|
if ($result !== null) {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
@ -120,51 +120,99 @@ final class BarcodeScanHelper
|
||||||
throw new InvalidArgumentException('Unknown barcode');
|
throw new InvalidArgumentException('Unknown barcode');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function parseDigikeyBarcode(string $input): ?VendorBarcodeScanResult
|
|
||||||
|
/**
|
||||||
|
* Parses Format 06 Barcodes according to ISO/IEC 15434. That standard calls on ASC MH10 to specify
|
||||||
|
* the data identifiers, but these are way too many to incorporate here. EIGP 114.2018 is yet another standard
|
||||||
|
* based on Format 06 which specifies identifiers for the electronics industry. I've included the identifiers
|
||||||
|
* from that standard, plus the extra ones I found on Digikey and Mouser Bags.
|
||||||
|
* @param string $input what was read from the barcode
|
||||||
|
* @return ?array Array of the form ["Meaning" => "Value"]
|
||||||
|
*/
|
||||||
|
private function decodeFormat06Barcode(string $input): ?array
|
||||||
{
|
{
|
||||||
if(!str_starts_with($input, "[)>\u{1E}06\u{1D}")){
|
if(!str_starts_with($input, "[)>\u{1E}06\u{1D}")){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if(str_ends_with($input, "\u{04}")){
|
||||||
|
$input = substr($input, 0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
$barcodeParts = explode("\u{1D}",$input);
|
$barcodeParts = explode("\u{1D}",$input);
|
||||||
if (count($barcodeParts) !== 16){
|
//get rid of the Format 06 identifier
|
||||||
|
array_shift($barcodeParts);
|
||||||
|
if (count($barcodeParts) < 2){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$fieldIds = [
|
$fieldIds = [
|
||||||
['id' => '', 'name' => ''],
|
//IDs per EIGP 114.2018
|
||||||
['id' => 'P', 'name' => 'Customer Part Number'],
|
'6D' => 'Ship Date',
|
||||||
['id' => '1P', 'name' => 'Supplier Part Number'],
|
'P' => 'Customer Part Number',
|
||||||
['id' => '30P','name' => 'Digikey Part Number'],
|
'1P' => 'Supplier Part Number',
|
||||||
['id' => 'K', 'name' => 'Purchase Order Part Number'],
|
'Q' => 'Quantity',
|
||||||
['id' => '1K', 'name' => 'Digikey Sales Order Number'],
|
'K' => 'Purchase Order Part Number',
|
||||||
['id' => '10K','name' => 'Digikey Invoice Number'],
|
'4K' => 'Purchase Order Line Number',
|
||||||
['id' => '9D', 'name' => 'Date Code'],
|
'9D' => 'Date Code',
|
||||||
['id' => '1T', 'name' => 'Lot Code'],
|
'10D' => 'Alternative Date Code',
|
||||||
['id' => '11K','name' => 'Packing List Number'],
|
'1T' => 'Lot Code',
|
||||||
['id' => '4L', 'name' => 'Country of Origin'],
|
'4L' => 'Country of Origin',
|
||||||
['id' => 'Q', 'name' => 'Quantity'],
|
'3S' => 'Package ID 1',
|
||||||
['id' => '11Z','name' => 'Label Type'],
|
'4S' => 'Package ID 2',
|
||||||
['id' => '12Z','name' => 'Part ID'],
|
'5S' => 'Package ID 3',
|
||||||
['id' => '13Z','name' => 'NA'],
|
'11K' => 'Packing List Number',
|
||||||
['id' => '20Z','name' => 'Padding']
|
'S' => 'Serial Number',
|
||||||
|
'33P' => 'BIN Code',
|
||||||
|
'13Q' => 'Package Count',
|
||||||
|
'2P' => 'Revision Number',
|
||||||
|
//IDs used by Digikey
|
||||||
|
'30P' => 'Digikey Part Number',
|
||||||
|
'1K' => 'Sales Order Number',
|
||||||
|
'10K' => 'Invoice Number',
|
||||||
|
'11Z' => 'Label Type',
|
||||||
|
'12Z' => 'Part ID',
|
||||||
|
'13Z' => 'NA',
|
||||||
|
'20Z' => 'Padding',
|
||||||
|
//IDs used by Mouser
|
||||||
|
'14K' => 'Position in Order',
|
||||||
|
'1V' => 'Manufacturer',
|
||||||
];
|
];
|
||||||
|
|
||||||
$results = [];
|
$results = [];
|
||||||
|
|
||||||
foreach ($barcodeParts as $index => $part) {
|
foreach($barcodeParts as $part) {
|
||||||
$fieldProps = $fieldIds[$index];
|
//^ 0* ([1-9]? \d* [A-Z])
|
||||||
if(!str_starts_with($part, $fieldProps['id'])){
|
//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)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$results[$fieldProps['name']] = substr($part, strlen($fieldProps['id']));
|
$meaning = $fieldIds[$matches[0]];
|
||||||
|
$fieldValue = substr($part, strlen($matches[0]));
|
||||||
|
$results[$meaning] = $fieldValue;
|
||||||
|
|
||||||
|
}
|
||||||
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a Format06 Barcode and puts it into a VendorBarcodeScanResult
|
||||||
|
* See decodeFormat06Barcode for details
|
||||||
|
*/
|
||||||
|
private function parseFormat06Barcode(string $input): ?VendorBarcodeScanResult{
|
||||||
|
$results = $this->decodeFormat06Barcode($input);
|
||||||
|
|
||||||
|
if($results === null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return new VendorBarcodeScanResult(
|
return new VendorBarcodeScanResult(
|
||||||
'Digikey',
|
manufacturer_part_number: $results['Supplier Part Number'] ?? null,
|
||||||
$results['Supplier Part Number'],
|
vendor_part_number: $results['Digikey Part Number'] ?? null,
|
||||||
$results['Digikey Part Number'],
|
date_code: $results['Date Code'] ?? null,
|
||||||
$results['Date Code'],
|
quantity: $results['Quantity'] ?? null,
|
||||||
$results['Quantity']
|
manufacturer: $results['Manufacturer'] ?? null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,12 @@ namespace App\Services\LabelSystem\Barcodes;
|
||||||
class VendorBarcodeScanResult
|
class VendorBarcodeScanResult
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly string $vendor,
|
public readonly ?string $vendor = null,
|
||||||
public readonly ?string $manufacturer_part_number = null,
|
public readonly ?string $manufacturer_part_number = null,
|
||||||
public readonly ?string $vendor_part_number = null,
|
public readonly ?string $vendor_part_number = null,
|
||||||
public readonly ?string $date_code = null,
|
public readonly ?string $date_code = null,
|
||||||
public readonly ?string $quantity = null
|
public readonly ?string $quantity = null,
|
||||||
|
public readonly ?string $manufacturer = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue