# Quick Box Checkpoint - Complete Implementation Guide ## Part 1: Backend API Routes Add these routes to `/srv/quality_app-v2/app/modules/quality/routes.py` ### Route 1: Create Quick Box ```python @quality_bp.route('/api/create-quick-box', methods=['POST']) def create_quick_box(): """Create a new box with auto-incremented number""" if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 try: conn = get_db() cursor = conn.cursor() # Get the next box number cursor.execute("SELECT MAX(CAST(SUBSTRING(box_number, 4) AS UNSIGNED)) FROM boxes_crates") result = cursor.fetchone() next_num = (result[0] if result[0] else 0) + 1 box_number = f"BOX{str(next_num).zfill(8)}" # Insert new box user_id = session.get('user_id') cursor.execute(""" INSERT INTO boxes_crates (box_number, status, created_by, created_at) VALUES (%s, %s, %s, NOW()) """, (box_number, 'open', user_id)) conn.commit() box_id = cursor.lastrowid cursor.close() logger.info(f"Quick box created: {box_number} (ID: {box_id})") return jsonify({ 'success': True, 'box_number': box_number, 'box_id': box_id }) except Exception as e: logger.error(f"Error creating quick box: {e}") return jsonify({'error': str(e)}), 500 ``` ### Route 2: Generate Box Label PDF ```python @quality_bp.route('/api/generate-box-label-pdf', methods=['POST']) def generate_box_label_pdf(): """Generate PDF label with barcode for printing via QZ Tray""" if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 try: from io import BytesIO from reportlab.lib.pagesizes import landscape from reportlab.lib import colors from reportlab.lib.units import mm from reportlab.pdfgen import canvas from reportlab.graphics.barcode import code128 import base64 box_number = request.form.get('box_number', 'Unknown') # Create PDF with 8cm x 5cm (landscape) pdf_buffer = BytesIO() page_width = 80 * mm # 8 cm page_height = 50 * mm # 5 cm c = canvas.Canvas(pdf_buffer, pagesize=(page_width, page_height)) c.setPageCompression(1) # Margins margin = 2 * mm usable_width = page_width - (2 * margin) usable_height = page_height - (2 * margin) # Text section at top text_height = 12 * mm barcode_height = usable_height - text_height - (1 * mm) # Draw text text_y = page_height - margin - 8 * mm c.setFont("Helvetica-Bold", 14) c.drawString(margin, text_y, "BOX Nr:") c.setFont("Courier-Bold", 16) c.drawString(margin + 20 * mm, text_y, box_number) # Generate and draw barcode barcode = code128.Code128( box_number, barWidth=0.5 * mm, barHeight=barcode_height - (2 * mm), humanReadable=False ) barcode_x = (page_width - barcode.width) / 2 barcode_y = margin + 2 * mm barcode.drawOn(c, barcode_x, barcode_y) c.save() # Convert to base64 pdf_data = pdf_buffer.getvalue() pdf_base64 = base64.b64encode(pdf_data).decode('utf-8') logger.info(f"Generated PDF label for box: {box_number}") return jsonify({ 'success': True, 'pdf_base64': pdf_base64, 'box_number': box_number }) except Exception as e: logger.error(f"Error generating box label PDF: {e}") return jsonify({'error': str(e)}), 500 ``` ### Route 3: Assign CP to Box ```python @quality_bp.route('/api/assign-cp-to-box', methods=['POST']) def assign_cp_to_box(): """Assign CP code to box""" if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 try: data = request.get_json() box_number = data.get('box_number', '').strip() cp_code = data.get('cp_code', '').strip() quantity = data.get('quantity', 1) if not box_number or not cp_code: return jsonify({'error': 'Missing box_number or cp_code'}), 400 conn = get_db() cursor = conn.cursor() # Get box ID cursor.execute("SELECT id FROM boxes_crates WHERE box_number = %s", (box_number,)) box_result = cursor.fetchone() if not box_result: cursor.close() return jsonify({'error': f'Box {box_number} not found'}), 404 box_id = box_result[0] # Insert into box_contents cursor.execute(""" INSERT INTO box_contents (box_id, cp_code, quantity, created_at) VALUES (%s, %s, %s, NOW()) """, (box_id, cp_code, quantity)) conn.commit() cursor.close() logger.info(f"CP {cp_code} assigned to box {box_number} (qty: {quantity})") return jsonify({ 'success': True, 'message': f'CP {cp_code} assigned to box {box_number}', 'box_id': box_id }) except Exception as e: logger.error(f"Error assigning CP to box: {e}") return jsonify({'error': str(e)}), 500 ``` ## Part 2: Frontend JavaScript Implementation Replace the incomplete `quickBoxLabel` section in `/srv/quality_app-v2/app/templates/modules/quality/fg_scan.html` ### Complete Quick Box Creation Function ```javascript // Quick box label creation - COMPLETE IMPLEMENTATION document.getElementById('quickBoxLabel').addEventListener('click', async function() { // Check if scan-to-boxes is enabled if (!scanToBoxesEnabled) { showNotification('⚠️ Please enable "Scan to Boxes" first', 'warning'); return; } // Get CP code const cpCode = document.getElementById('cp_code').value.trim(); if (!cpCode) { showNotification('⚠️ Please enter a CP code first', 'warning'); return; } try { this.disabled = true; this.textContent = '⏳ Creating...'; // Step 1: Create box in database console.log('📦 Step 1: Creating new box...'); const createResponse = await fetch('{{ url_for("quality.create_quick_box") }}', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, body: JSON.stringify({}) }); if (!createResponse.ok) { throw new Error(`Failed to create box: ${createResponse.statusText}`); } const createResult = await createResponse.json(); if (!createResult.success || !createResult.box_number) { throw new Error(createResult.error || 'Failed to create box'); } const boxNumber = createResult.box_number; console.log('✅ Box created:', boxNumber); showNotification(`✅ Box ${boxNumber} created`, 'success'); // Step 2: Generate PDF label console.log('📄 Step 2: Generating PDF label...'); const pdfFormData = new FormData(); pdfFormData.append('box_number', boxNumber); const pdfResponse = await fetch('{{ url_for("quality.generate_box_label_pdf") }}', { method: 'POST', body: pdfFormData }); if (!pdfResponse.ok) { throw new Error('Failed to generate PDF label'); } const pdfResult = await pdfResponse.json(); if (!pdfResult.success) { throw new Error(pdfResult.error || 'Failed to generate PDF'); } console.log('✅ PDF generated'); // Step 3: Print label via QZ Tray console.log('🖨️ Step 3: Printing label via QZ Tray...'); try { // Check QZ Tray connection if (!window.qz || !window.qz.websocket.isActive()) { console.log('Attempting to connect to QZ Tray...'); await window.qz.websocket.connect(); } // Get printers const printers = await window.qz.printers.find(); if (printers.length === 0) { throw new Error('No printers found'); } // Get default or first available printer let printer; try { printer = await window.qz.printers.getDefault(); } catch (e) { printer = printers[0]; } console.log('Using printer:', printer); // Configure print job const config = window.qz.configs.create(printer, { scaleContent: false, rasterize: false, size: { width: 80, height: 50 }, units: 'mm', margins: { top: 0, right: 0, bottom: 0, left: 0 } }); // Prepare PDF data const printData = [{ type: 'pdf', format: 'base64', data: pdfResult.pdf_base64 }]; // Print await window.qz.print(config, printData); console.log('✅ Label printed'); showNotification(`✅ Box ${boxNumber} created and label printed!`, 'success'); // Step 4: Show modal for CP assignment console.log('📝 Step 4: Opening modal for CP assignment...'); currentCpCode = cpCode; document.getElementById('boxNumber').value = boxNumber; document.getElementById('boxQty').value = '1'; document.getElementById('boxAssignmentModal').style.display = 'flex'; document.getElementById('boxNumber').focus(); } catch (printError) { console.warn('QZ Tray print failed, attempting browser print:', printError); showNotification( `⚠️ Box ${boxNumber} created but QZ Tray print failed.\n` + `Please check if QZ Tray is running.\n\n` + `Box will be ready for manual entry.`, 'warning' ); // Still show modal for manual entry currentCpCode = cpCode; document.getElementById('boxNumber').value = boxNumber; document.getElementById('boxQty').value = '1'; document.getElementById('boxAssignmentModal').style.display = 'flex'; } } catch (error) { console.error('❌ Error:', error); showNotification(`❌ Error: ${error.message}`, 'error'); } finally { // Re-enable button this.disabled = false; this.textContent = '📦 Quick Box Label Creation'; } }); // Assign CP to Box button document.getElementById('assignToBox').addEventListener('click', async function() { const boxNumber = document.getElementById('boxNumber').value.trim(); const boxQty = document.getElementById('boxQty').value.trim(); if (!boxNumber) { showNotification('⚠️ Please enter a box number', 'warning'); return; } if (!boxQty || isNaN(boxQty) || parseInt(boxQty) < 1) { showNotification('⚠️ Please enter a valid quantity', 'warning'); return; } try { this.disabled = true; this.textContent = '⏳ Assigning...'; const response = await fetch('{{ url_for("quality.assign_cp_to_box") }}', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, body: JSON.stringify({ box_number: boxNumber, cp_code: currentCpCode, quantity: boxQty }) }); if (!response.ok) { throw new Error(`Server error: ${response.statusText}`); } const result = await response.json(); if (result.success) { showNotification( `✅ CP ${currentCpCode} assigned to box ${boxNumber}!`, 'success' ); // Close modal document.getElementById('boxAssignmentModal').style.display = 'none'; // Reset form resetForm(); // Clear box inputs document.getElementById('boxNumber').value = ''; document.getElementById('boxQty').value = ''; // Set focus back to operator code for next scan document.getElementById('operator_code').focus(); } else { showNotification(`❌ Error: ${result.error}`, 'error'); } } catch (error) { console.error('Error:', error); showNotification(`❌ Error: ${error.message}`, 'error'); } finally { this.disabled = false; this.textContent = 'Assign'; } }); ``` ### Update Modal Structure (HTML) Update the modal in `/srv/quality_app-v2/app/templates/modules/quality/fg_scan.html`: ```html
``` ### Update QZ Tray Initialization ```javascript // Better QZ Tray initialization function initializeQzTray() { if (typeof qz === 'undefined') { console.log('ℹ️ QZ Tray library not loaded'); return false; } try { // Set signature (required for QZ Tray 2.0+) qz.security.setSignaturePromise(function(toSign) { return new Promise(function(resolve, reject) { // For development/local use, allow unsigned // In production, you would generate a real signature server-side resolve(); }); }); // Try to connect qz.websocket.connect() .then(function() { qzTrayReady = true; console.log('✅ QZ Tray connected'); // Get available printers return qz.printers.find(); }) .then(function(printers) { console.log('Available printers:', printers); if (printers.length > 0) { console.log('✅ Found', printers.length, 'printer(s)'); } else { console.warn('⚠️ No printers found'); } }) .catch(function(err) { console.log('ℹ️ QZ Tray not available:', err); qzTrayReady = false; }); return true; } catch(err) { console.log('ℹ️ QZ Tray initialization error:', err); return false; } } ``` ## Part 3: Database Schema Ensure these tables exist with proper structure: ```sql CREATE TABLE IF NOT EXISTS boxes_crates ( id BIGINT AUTO_INCREMENT PRIMARY KEY, box_number VARCHAR(20) NOT NULL UNIQUE, status ENUM('open', 'closed') DEFAULT 'open', location_id BIGINT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, created_by INT, FOREIGN KEY (location_id) REFERENCES warehouse_locations(id) ON DELETE SET NULL, FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_box_number (box_number), INDEX idx_status (status) ); CREATE TABLE IF NOT EXISTS box_contents ( id BIGINT AUTO_INCREMENT PRIMARY KEY, box_id BIGINT NOT NULL, cp_code VARCHAR(20) NOT NULL, quantity INT DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (box_id) REFERENCES boxes_crates(id) ON DELETE CASCADE, INDEX idx_box_id (box_id), INDEX idx_cp_code (cp_code) ); ``` ## Testing Instructions 1. **Enable Feature**: Check "Scan To Boxes" in FG Scan page 2. **Create Box**: Click "Quick Box Label Creation" 3. **Verify**: - Box created with auto-incremented number - Label printed or shows fallback - Modal appears for CP assignment - Box number populated automatically 4. **Assign CP**: Enter quantity and click "Assign to Box" 5. **Verify Database**: ```sql SELECT * FROM boxes_crates WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR); SELECT * FROM box_contents WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR); ``` ## Error Handling - QZ Tray not connected → Warning message, fallback to browser print - Box creation fails → Error message with details - PDF generation fails → Error message, still show modal - CP assignment fails → Show error, keep modal open for retry