Files
inventory-barcode-system/services/CodeGenerationService.js

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;