const express = require('express'); const multer = require('multer'); const XLSX = require('xlsx'); const CodeGenerationService = require('../services/CodeGenerationService'); const PrintableLayoutService = require('../services/PrintableLayoutService'); const Product = require('../models/Product'); const Inventory = require('../models/Inventory'); const router = express.Router(); // Configure multer for file uploads (for export functionality) const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 5 * 1024 * 1024, // 5MB limit } }); /** * GET /api/codes/formats * Get supported barcode formats */ router.get('/formats', (req, res) => { try { const codeGenService = new CodeGenerationService(); const supportedFormats = codeGenService.getSupportedFormats(); res.json({ success: true, data: { barcodeFormats: supportedFormats, qrCodeSupported: true } }); } catch (error) { res.status(500).json({ success: false, error: 'Failed to retrieve supported formats', message: error.message }); } }); /** * POST /api/codes/barcode * Generate barcode for a product code */ router.post('/barcode', async (req, res) => { try { const { productCode, format = 'CODE128', options = {} } = req.body; if (!productCode) { return res.status(400).json({ success: false, error: 'Missing product code', message: 'Product code is required' }); } const codeGenService = new CodeGenerationService(); const result = await codeGenService.generateBarcode(productCode, format, options); if (!result.success) { return res.status(400).json({ success: false, error: 'Barcode generation failed', message: result.error }); } res.json({ success: true, data: result, message: 'Barcode generated successfully' }); } catch (error) { res.status(500).json({ success: false, error: 'Failed to generate barcode', message: error.message }); } }); /** * POST /api/codes/qrcode * Generate QR code for product data */ router.post('/qrcode', async (req, res) => { try { const { productData, options = {} } = req.body; if (!productData || !productData.product_code) { return res.status(400).json({ success: false, error: 'Invalid product data', message: 'Product data with product_code is required' }); } const codeGenService = new CodeGenerationService(); const result = await codeGenService.generateQRCode(productData, options); if (!result.success) { return res.status(400).json({ success: false, error: 'QR code generation failed', message: result.error }); } res.json({ success: true, data: result, message: 'QR code generated successfully' }); } catch (error) { res.status(500).json({ success: false, error: 'Failed to generate QR code', message: error.message }); } }); /** * POST /api/codes/both * Generate both barcode and QR code for product data */ router.post('/both', async (req, res) => { try { const { productData, options = {} } = req.body; if (!productData || !productData.product_code) { return res.status(400).json({ success: false, error: 'Invalid product data', message: 'Product data with product_code is required' }); } const codeGenService = new CodeGenerationService(); const result = await codeGenService.generateBothCodes(productData, options); res.json({ success: true, data: result, message: 'Codes generated successfully' }); } catch (error) { res.status(500).json({ success: false, error: 'Failed to generate codes', message: error.message }); } }); /** * POST /api/codes/product/:productId * Generate codes for a specific product by ID */ router.post('/product/:productId', async (req, res) => { try { const productId = parseInt(req.params.productId); if (isNaN(productId)) { return res.status(400).json({ success: false, error: 'Invalid product ID', message: 'Product ID must be a valid number' }); } // Get product data const product = await Product.findById(productId); if (!product) { return res.status(404).json({ success: false, error: 'Product not found', message: `Product with ID ${productId} does not exist` }); } const { codeType = 'both', format = 'CODE128', options = {} } = req.body; const codeGenService = new CodeGenerationService(); let result; const productData = product.toJSON(); switch (codeType.toLowerCase()) { case 'barcode': result = await codeGenService.generateBarcode(productData.name, format, options); break; case 'qrcode': result = await codeGenService.generateQRCode({ product_code: productData.name, description: productData.description, category: productData.category, unit_of_measure: productData.unit }, options); break; case 'both': default: result = await codeGenService.generateBothCodes({ product_code: productData.name, description: productData.description, category: productData.category, unit_of_measure: productData.unit }, options); break; } res.json({ success: true, data: { product: productData, codes: result }, message: 'Codes generated successfully for product' }); } catch (error) { res.status(500).json({ success: false, error: 'Failed to generate codes for product', message: error.message }); } }); /** * GET /api/codes/layouts/sizes * Get available label sizes for printable layouts */ router.get('/layouts/sizes', (req, res) => { try { const printService = new PrintableLayoutService(); const labelSizes = printService.getAvailableLabelSizes(); res.json({ success: true, data: labelSizes }); } catch (error) { res.status(500).json({ success: false, error: 'Failed to retrieve label sizes', message: error.message }); } }); /** * POST /api/codes/layouts/preview * Generate preview of printable layout */ router.post('/layouts/preview', async (req, res) => { try { const { productIds = [], options = {} } = req.body; if (!Array.isArray(productIds) || productIds.length === 0) { return res.status(400).json({ success: false, error: 'Invalid product IDs', message: 'Product IDs array is required and cannot be empty' }); } // Get sample products (limit to first 5 for preview) const sampleIds = productIds.slice(0, 5); const sampleProducts = []; for (const productId of sampleIds) { const product = await Product.findById(productId); if (product) { sampleProducts.push({ product_code: product.name, description: product.description, category: product.category, unit_of_measure: product.unit }); } } if (sampleProducts.length === 0) { return res.status(404).json({ success: false, error: 'No valid products found', message: 'None of the provided product IDs exist' }); } const printService = new PrintableLayoutService(); const preview = await printService.generateLayoutPreview(sampleProducts, options); if (!preview.success) { return res.status(400).json({ success: false, error: 'Preview generation failed', message: preview.error }); } res.json({ success: true, data: { ...preview.preview, sampleProducts: sampleProducts, totalRequestedProducts: productIds.length }, message: 'Layout preview generated successfully' }); } catch (error) { res.status(500).json({ success: false, error: 'Failed to generate layout preview', message: error.message }); } }); /** * POST /api/codes/layouts/generate * Generate printable PDF layout with codes */ router.post('/layouts/generate', async (req, res) => { try { const { productIds = [], options = {} } = req.body; if (!Array.isArray(productIds) || productIds.length === 0) { return res.status(400).json({ success: false, error: 'Invalid product IDs', message: 'Product IDs array is required and cannot be empty' }); } // Limit to reasonable number of products if (productIds.length > 1000) { return res.status(400).json({ success: false, error: 'Too many products', message: 'Maximum 1000 products allowed per layout' }); } // Get products data const products = []; for (const productId of productIds) { const product = await Product.findById(productId); if (product) { products.push({ product_code: product.name, description: product.description, category: product.category, unit_of_measure: product.unit }); } } if (products.length === 0) { return res.status(404).json({ success: false, error: 'No valid products found', message: 'None of the provided product IDs exist' }); } const printService = new PrintableLayoutService(); const result = await printService.generatePrintableLayout(products, options); if (!result.success) { return res.status(400).json({ success: false, error: 'Layout generation failed', message: result.error }); } // Set response headers for PDF download res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', 'attachment; filename="product-labels.pdf"'); res.setHeader('Content-Length', result.data.length); res.send(result.data); } catch (error) { res.status(500).json({ success: false, error: 'Failed to generate printable layout', message: error.message }); } }); /** * GET /api/codes/export/excel * Export inventory data to Excel with updated levels */ router.get('/export/excel', async (req, res) => { try { const filters = {}; // Extract query parameters for filtering if (req.query.category) { filters.category = req.query.category; } if (req.query.lowStock === 'true') { filters.lowStock = true; } // Get inventory summary const inventoryData = await Inventory.getInventorySummary(filters); if (inventoryData.length === 0) { return res.status(404).json({ success: false, error: 'No data to export', message: 'No inventory data found matching the specified filters' }); } // Prepare data for Excel export const excelData = inventoryData.map(item => ({ 'Product Code': item.product_code || '', 'Description': item.description || '', 'Category': item.category || '', 'Current Level': item.current_level || 0, 'Minimum Level': item.minimum_level || 0, 'Maximum Level': item.maximum_level || '', 'Stock Status': item.stock_status || 'unknown', 'Last Updated': item.last_updated || '', 'Updated By': item.updated_by || '' })); // Create workbook and worksheet const workbook = XLSX.utils.book_new(); const worksheet = XLSX.utils.json_to_sheet(excelData); // Add worksheet to workbook XLSX.utils.book_append_sheet(workbook, worksheet, 'Inventory'); // Add metadata sheet const metadata = [ { Field: 'Export Date', Value: new Date().toISOString() }, { Field: 'Total Records', Value: inventoryData.length }, { Field: 'Filters Applied', Value: Object.keys(filters).length > 0 ? JSON.stringify(filters) : 'None' } ]; const metadataSheet = XLSX.utils.json_to_sheet(metadata); XLSX.utils.book_append_sheet(workbook, metadataSheet, 'Export Info'); // Generate Excel buffer const excelBuffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }); // Set response headers for Excel download const filename = `inventory-export-${new Date().toISOString().split('T')[0]}.xlsx`; res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); res.setHeader('Content-Length', excelBuffer.length); res.send(excelBuffer); } catch (error) { res.status(500).json({ success: false, error: 'Failed to export Excel file', message: error.message }); } }); /** * POST /api/codes/export/excel/custom * Export custom inventory data to Excel with specified columns */ router.post('/export/excel/custom', async (req, res) => { try { const { productIds = [], columns = ['product_code', 'description', 'current_level'], includeHistory = false, filename } = req.body; if (!Array.isArray(productIds) || productIds.length === 0) { return res.status(400).json({ success: false, error: 'Invalid product IDs', message: 'Product IDs array is required and cannot be empty' }); } // Get products and their inventory data const exportData = []; for (const productId of productIds) { const product = await Product.findById(productId); if (!product) continue; const inventory = await Inventory.getByProductId(productId); const rowData = {}; // Add requested columns if (columns.includes('product_code')) rowData['Product Code'] = product.name; if (columns.includes('description')) rowData['Description'] = product.description; if (columns.includes('category')) rowData['Category'] = product.category; if (columns.includes('current_level')) rowData['Current Level'] = inventory?.current_level || 0; if (columns.includes('minimum_level')) rowData['Minimum Level'] = inventory?.minimum_level || 0; if (columns.includes('maximum_level')) rowData['Maximum Level'] = inventory?.maximum_level || ''; if (columns.includes('last_updated')) rowData['Last Updated'] = inventory?.last_updated || ''; if (columns.includes('updated_by')) rowData['Updated By'] = inventory?.updated_by || ''; exportData.push(rowData); } if (exportData.length === 0) { return res.status(404).json({ success: false, error: 'No data to export', message: 'No valid products found for export' }); } // Create workbook const workbook = XLSX.utils.book_new(); const worksheet = XLSX.utils.json_to_sheet(exportData); XLSX.utils.book_append_sheet(workbook, worksheet, 'Custom Export'); // Add history sheet if requested if (includeHistory) { const historyData = []; for (const productId of productIds.slice(0, 10)) { // Limit history to first 10 products const history = await Inventory.getInventoryHistory(productId, { limit: 50 }); historyData.push(...history.map(h => ({ 'Product Code': h.product_code, 'Old Level': h.old_level, 'New Level': h.new_level, 'Change Reason': h.change_reason, 'Updated By': h.updated_by, 'Updated At': h.updated_at }))); } if (historyData.length > 0) { const historySheet = XLSX.utils.json_to_sheet(historyData); XLSX.utils.book_append_sheet(workbook, historySheet, 'History'); } } // Generate Excel buffer const excelBuffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }); // Set response headers const exportFilename = filename || `custom-export-${new Date().toISOString().split('T')[0]}.xlsx`; res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); res.setHeader('Content-Disposition', `attachment; filename="${exportFilename}"`); res.setHeader('Content-Length', excelBuffer.length); res.send(excelBuffer); } catch (error) { res.status(500).json({ success: false, error: 'Failed to export custom Excel file', message: error.message }); } }); /** * POST /api/codes/qr/parse * Parse QR code data back to product information */ router.post('/qr/parse', (req, res) => { try { const { qrData } = req.body; if (!qrData) { return res.status(400).json({ success: false, error: 'Missing QR data', message: 'QR code data is required' }); } const codeGenService = new CodeGenerationService(); const result = codeGenService.parseQRCodeData(qrData); if (!result.success) { return res.status(400).json({ success: false, error: 'QR code parsing failed', message: result.error }); } res.json({ success: true, data: result, message: 'QR code parsed successfully' }); } catch (error) { res.status(500).json({ success: false, error: 'Failed to parse QR code', message: error.message }); } }); module.exports = router;