253 lines
7.5 KiB
JavaScript
253 lines
7.5 KiB
JavaScript
const JsBarcode = require('jsbarcode');
|
|
const QRCode = require('qrcode');
|
|
const { createCanvas } = require('canvas');
|
|
|
|
/**
|
|
* Service for generating barcodes and QR codes for inventory products
|
|
*/
|
|
class CodeGenerationService {
|
|
constructor() {
|
|
this.supportedBarcodeFormats = ['CODE128', 'CODE39', 'EAN13', 'EAN8', 'UPC'];
|
|
this.defaultBarcodeOptions = {
|
|
format: 'CODE128',
|
|
width: 2,
|
|
height: 100,
|
|
displayValue: true,
|
|
fontSize: 20,
|
|
textAlign: 'center',
|
|
textPosition: 'bottom',
|
|
textMargin: 2,
|
|
fontOptions: '',
|
|
font: 'monospace',
|
|
background: '#ffffff',
|
|
lineColor: '#000000',
|
|
margin: 10
|
|
};
|
|
|
|
this.defaultQROptions = {
|
|
errorCorrectionLevel: 'M',
|
|
type: 'image/png',
|
|
quality: 0.92,
|
|
margin: 1,
|
|
color: {
|
|
dark: '#000000',
|
|
light: '#FFFFFF'
|
|
},
|
|
width: 200
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate barcode for a product code
|
|
* @param {string} productCode - The product code to encode
|
|
* @param {string} format - Barcode format (CODE128, CODE39, EAN13, etc.)
|
|
* @param {Object} options - Additional barcode options
|
|
* @returns {Promise<string>} Base64 encoded barcode image
|
|
*/
|
|
async generateBarcode(productCode, format = 'CODE128', options = {}) {
|
|
try {
|
|
if (!productCode || typeof productCode !== 'string') {
|
|
throw new Error('Product code must be a non-empty string');
|
|
}
|
|
|
|
if (!this.supportedBarcodeFormats.includes(format.toUpperCase())) {
|
|
throw new Error(`Unsupported barcode format: ${format}. Supported formats: ${this.supportedBarcodeFormats.join(', ')}`);
|
|
}
|
|
|
|
// Validate product code for specific formats
|
|
let processedCode = this._validateProductCodeForFormat(productCode, format);
|
|
|
|
const barcodeOptions = {
|
|
...this.defaultBarcodeOptions,
|
|
...options,
|
|
format: format.toUpperCase()
|
|
};
|
|
|
|
// Create canvas for barcode generation
|
|
const canvas = createCanvas(400, 200);
|
|
|
|
// Generate barcode
|
|
JsBarcode(canvas, processedCode, barcodeOptions);
|
|
|
|
// Convert to base64
|
|
const base64Image = canvas.toDataURL('image/png');
|
|
|
|
return {
|
|
success: true,
|
|
data: base64Image,
|
|
format: format.toUpperCase(),
|
|
productCode: productCode,
|
|
metadata: {
|
|
width: canvas.width,
|
|
height: canvas.height,
|
|
format: 'PNG'
|
|
}
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
productCode: productCode,
|
|
format: format
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate QR code with embedded product data
|
|
* @param {Object} productData - Product information to embed
|
|
* @param {Object} options - QR code generation options
|
|
* @returns {Promise<string>} Base64 encoded QR code image
|
|
*/
|
|
async generateQRCode(productData, options = {}) {
|
|
try {
|
|
if (!productData || typeof productData !== 'object') {
|
|
throw new Error('Product data must be an object');
|
|
}
|
|
|
|
if (!productData.product_code) {
|
|
throw new Error('Product data must include product_code');
|
|
}
|
|
|
|
// Create structured data for QR code
|
|
const qrData = {
|
|
code: productData.product_code,
|
|
desc: productData.description || '',
|
|
cat: productData.category || '',
|
|
uom: productData.unit_of_measure || '',
|
|
ts: new Date().toISOString()
|
|
};
|
|
|
|
const qrOptions = {
|
|
...this.defaultQROptions,
|
|
...options
|
|
};
|
|
|
|
// Generate QR code
|
|
const qrCodeDataURL = await QRCode.toDataURL(JSON.stringify(qrData), qrOptions);
|
|
|
|
return {
|
|
success: true,
|
|
data: qrCodeDataURL,
|
|
productCode: productData.product_code,
|
|
embeddedData: qrData,
|
|
metadata: {
|
|
format: 'PNG',
|
|
errorCorrectionLevel: qrOptions.errorCorrectionLevel,
|
|
width: qrOptions.width
|
|
}
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
productData: productData
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate both barcode and QR code for a product
|
|
* @param {Object} productData - Product information
|
|
* @param {Object} options - Generation options
|
|
* @returns {Promise<Object>} Object containing both codes
|
|
*/
|
|
async generateBothCodes(productData, options = {}) {
|
|
const barcodeOptions = options.barcode || {};
|
|
const qrOptions = options.qr || {};
|
|
const barcodeFormat = options.barcodeFormat || 'CODE128';
|
|
|
|
const [barcodeResult, qrResult] = await Promise.all([
|
|
this.generateBarcode(productData.product_code, barcodeFormat, barcodeOptions),
|
|
this.generateQRCode(productData, qrOptions)
|
|
]);
|
|
|
|
return {
|
|
productCode: productData.product_code,
|
|
barcode: barcodeResult,
|
|
qrCode: qrResult,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get supported barcode formats
|
|
* @returns {Array<string>} List of supported formats
|
|
*/
|
|
getSupportedFormats() {
|
|
return [...this.supportedBarcodeFormats];
|
|
}
|
|
|
|
/**
|
|
* Validate product code for specific barcode format
|
|
* @private
|
|
* @param {string} productCode - Product code to validate
|
|
* @param {string} format - Barcode format
|
|
* @returns {string} Processed product code for barcode generation
|
|
*/
|
|
_validateProductCodeForFormat(productCode, format) {
|
|
const upperFormat = format.toUpperCase();
|
|
|
|
switch (upperFormat) {
|
|
case 'EAN13':
|
|
if (!/^\d{12,13}$/.test(productCode)) {
|
|
throw new Error('EAN13 format requires 12-13 digits');
|
|
}
|
|
// For 13-digit codes, remove the last digit (check digit) for jsbarcode
|
|
// jsbarcode will calculate the check digit automatically
|
|
return productCode.length === 13 ? productCode.substring(0, 12) : productCode;
|
|
case 'EAN8':
|
|
if (!/^\d{7,8}$/.test(productCode)) {
|
|
throw new Error('EAN8 format requires 7-8 digits');
|
|
}
|
|
// For 8-digit codes, remove the last digit (check digit) for jsbarcode
|
|
return productCode.length === 8 ? productCode.substring(0, 7) : productCode;
|
|
case 'UPC':
|
|
if (!/^\d{11,12}$/.test(productCode)) {
|
|
throw new Error('UPC format requires 11-12 digits');
|
|
}
|
|
// For 12-digit codes, remove the last digit (check digit) for jsbarcode
|
|
return productCode.length === 12 ? productCode.substring(0, 11) : productCode;
|
|
case 'CODE39':
|
|
if (!/^[A-Z0-9\-. $\/+%]+$/.test(productCode)) {
|
|
throw new Error('CODE39 format supports only uppercase letters, digits, and specific symbols');
|
|
}
|
|
return productCode;
|
|
case 'CODE128':
|
|
// CODE128 supports most ASCII characters, so minimal validation
|
|
if (productCode.length === 0) {
|
|
throw new Error('Product code cannot be empty');
|
|
}
|
|
return productCode;
|
|
default:
|
|
return productCode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse QR code data back to product information
|
|
* @param {string} qrData - Raw QR code data
|
|
* @returns {Object} Parsed product data
|
|
*/
|
|
parseQRCodeData(qrData) {
|
|
try {
|
|
const parsed = JSON.parse(qrData);
|
|
return {
|
|
success: true,
|
|
productCode: parsed.code,
|
|
description: parsed.desc,
|
|
category: parsed.cat,
|
|
unitOfMeasure: parsed.uom,
|
|
timestamp: parsed.ts
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: 'Invalid QR code data format',
|
|
rawData: qrData
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = CodeGenerationService; |