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} 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} 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 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} 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;