Initial commit: Inventory Barcode System
This commit is contained in:
253
services/CodeGenerationService.js
Normal file
253
services/CodeGenerationService.js
Normal file
@ -0,0 +1,253 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user