Files
quality_app-v2/documentation/BOXES_IMPLEMENTATION_DETAILS.md
Quality App Developer b15cc93b9d FG Scan form validation improvements with warehouse module updates
- Fixed 3 JavaScript syntax errors in fg_scan.html (lines 951, 840-950, 1175-1215)
- Restored form field validation with proper null safety checks
- Re-enabled auto-advance between form fields
- Re-enabled CP code auto-complete with hyphen detection
- Updated validation error messages with clear format specifications and examples
- Added autocomplete='off' to all input fields
- Removed auto-prefix correction feature
- Updated warehouse routes and modules for box assignment workflow
- Added/improved database initialization scripts
- Updated requirements.txt dependencies

Format specifications implemented:
- Operator Code: OP + 2 digits (example: OP01, OP99)
- CP Code: CP + 8 digits + hyphen + 4 digits (example: CP00000000-0001)
- OC1/OC2 Codes: OC + 2 digits (example: OC01, OC99)
- Defect Code: 3 digits only
2026-01-30 10:50:06 +02:00

17 KiB

Quick Box Creation & Printing Implementation Details

Overview

The "Quick Box Creation" feature allows users to quickly create boxes and assign finish goods (FG) CP codes to them directly from the FG scan page, with automatic box label printing support.


1. Database Tables

boxes_crates Table

Location: warehouse.py (lines 32-45)

CREATE TABLE IF NOT EXISTS boxes_crates (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    box_number VARCHAR(8) 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 VARCHAR(100),
    FOREIGN KEY (location_id) REFERENCES warehouse_locations(id) ON DELETE SET NULL
)

Fields:

  • box_number: 8-digit unique identifier (00000001, 00000002, etc.)
  • status: 'open' (receiving items) or 'closed' (ready for warehouse)
  • location_id: References warehouse location (nullable)
  • created_by: Username of operator who created the box
  • created_at/updated_at: Timestamps

box_contents Table

Location: warehouse.py (lines 47-62)

CREATE TABLE IF NOT EXISTS box_contents (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    box_id BIGINT NOT NULL,
    cp_code VARCHAR(15) NOT NULL,
    scan_id BIGINT,
    scanned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    scanned_by VARCHAR(100),
    FOREIGN KEY (box_id) REFERENCES boxes_crates(id) ON DELETE CASCADE,
    INDEX idx_box_id (box_id),
    INDEX idx_cp_code (cp_code)
)

Fields:

  • box_id: References the box this CP code is in
  • cp_code: The finish good CP code being scanned
  • scanned_by: Username of operator who scanned
  • scanned_at: When the CP code was added to the box

2. Frontend Implementation

FG Scan Page (fg_scan.html)

Location: /srv/quality_app/py_app/app/templates/fg_scan.html

Key Global Variables (Lines 10-14)

let scanToBoxesEnabled = false;
let currentCpCode = null;

// Functions defined at global scope for accessibility
async function submitScanWithBoxAssignment() { ... }
function showBoxModal(cpCode) { ... }
async function assignCpToBox(boxNumber) { ... }
function showNotification(message, type = 'info') { ... }

Toggle Control

  • Checkbox ID: scan-to-boxes-toggle
  • Persists state in localStorage: scan_to_boxes_enabled
  • When enabled: Allows QZ Tray connection for direct label printing

Complete Workflow (Lines 19-84)

Step 1: Form Submission with Box Assignment

async function submitScanWithBoxAssignment() {
    const form = document.getElementById('fg-scan-form');
    const formData = new FormData(form);
    
    // Submit scan to server
    const response = await fetch(window.location.href, {
        method: 'POST',
        headers: {'X-Requested-With': 'XMLHttpRequest'},
        body: formData
    });
    
    if (response.ok) {
        currentCpCode = formData.get('cp_code');
        const defectCode = formData.get('defect_code') || '000';
        
        // Only show modal for approved items (defect code 000 or 0)
        if (defectCode === '000' || defectCode === '0') {
            showBoxModal(currentCpCode);
        } else {
            // Reload page for defective items
            setTimeout(() => window.location.reload(), 1000);
        }
        
        // Clear form for next scan
        document.getElementById('cp_code').value = '';
        // ... clear other fields
    }
}

Step 2: Show Box Modal

function showBoxModal(cpCode) {
    document.getElementById('modal-cp-code').textContent = cpCode;
    document.getElementById('box-assignment-modal').style.display = 'block';
    document.getElementById('scan-box-input').value = '';
    document.getElementById('scan-box-input').focus();
}

Step 3: Assign CP to Box via API

async function assignCpToBox(boxNumber) {
    const response = await fetch('/warehouse/assign_cp_to_box', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            box_number: boxNumber,
            cp_code: currentCpCode
        })
    });
    
    if (!response.ok) {
        const error = await response.json();
        throw new Error(error.error || 'Failed to assign CP to box');
    }
    
    return await response.json();
}

3. Backend Implementation

Routes

FG Scan Route

Location: /srv/quality_app/py_app/app/routes.py (lines 1020-1090)

@bp.route('/fg_scan', methods=['GET', 'POST'])
@requires_quality_module
def fg_scan():
    ensure_scanfg_orders_table()
    
    if request.method == 'POST':
        operator_code = request.form.get('operator_code')
        cp_code = request.form.get('cp_code')
        oc1_code = request.form.get('oc1_code')
        oc2_code = request.form.get('oc2_code')
        defect_code = request.form.get('defect_code')
        date = request.form.get('date')
        time = request.form.get('time')
        
        # Insert scan record
        with db_connection_context() as conn:
            cursor = conn.cursor()
            insert_query = """
                INSERT INTO scanfg_orders 
                (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time)
                VALUES (%s, %s, %s, %s, %s, %s, %s)
            """
            cursor.execute(insert_query, 
                          (operator_code, cp_code, oc1_code, oc2_code, defect_code, date, time))
            conn.commit()
        
        # Handle AJAX requests for scan-to-boxes feature
        if request.headers.get('X-Requested-With') == 'XMLHttpRequest' or \
           request.accept_mimetypes.best == 'application/json':
            return jsonify({'success': True, 'message': 'Scan recorded successfully'})
        
        # Standard form submission
        return redirect(url_for('main.fg_scan'))

Key Points:

  • Accepts AJAX requests for scan-to-boxes feature
  • Returns JSON for AJAX, redirects for normal form submission
  • Only quality_module permission required

Assign CP to Box Route

Location: /srv/quality_app/py_app/app/warehouse.py (lines 619-658)

def assign_cp_to_box_handler():
    """Handle assigning CP code to a box"""
    from flask import request, jsonify, session
    import json
    
    try:
        ensure_box_contents_table()
        
        data = json.loads(request.data)
        box_number = data.get('box_number')
        cp_code = data.get('cp_code')
        scanned_by = session.get('user', 'Unknown')
        
        if not box_number or not cp_code:
            return jsonify({'success': False, 'error': 'Missing box_number or cp_code'}), 400
        
        conn = get_db_connection()
        cursor = conn.cursor()
        
        # Find box by number
        cursor.execute("SELECT id FROM boxes_crates WHERE box_number = %s", (box_number,))
        box = cursor.fetchone()
        
        if not box:
            conn.close()
            return jsonify({'success': False, 'error': f'Box {box_number} not found'}), 404
        
        box_id = box[0]
        
        # Insert into box_contents
        cursor.execute("""
            INSERT INTO box_contents (box_id, cp_code, scanned_by)
            VALUES (%s, %s, %s)
        """, (box_id, cp_code, scanned_by))
        
        conn.commit()
        conn.close()
        
        return jsonify({
            'success': True,
            'message': f'CP {cp_code} assigned to box {box_number}'
        }), 200

Box Management Functions

Generate Box Number

Location: warehouse.py (lines 155-165)

def generate_box_number():
    """Generate next box number with 8 digits (00000001, 00000002, etc.)"""
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT MAX(CAST(box_number AS UNSIGNED)) FROM boxes_crates")
    result = cursor.fetchone()
    conn.close()
    
    if result and result[0]:
        next_number = int(result[0]) + 1
    else:
        next_number = 1
    
    return str(next_number).zfill(8)

Add Box

Location: warehouse.py (lines 167-183)

def add_box(location_id=None, created_by=None):
    """Add a new box/crate"""
    conn = get_db_connection()
    cursor = conn.cursor()
    
    box_number = generate_box_number()
    
    try:
        cursor.execute(
            "INSERT INTO boxes_crates (box_number, status, location_id, created_by) VALUES (%s, %s, %s, %s)",
            (box_number, 'open', location_id if location_id else None, created_by)
        )
        conn.commit()
        conn.close()
        return f"Box {box_number} created successfully"
    except Exception as e:
        conn.close()
        return f"Error creating box: {e}"

Search Box by Number

Location: warehouse.py (lines 740-785)

def search_box_by_number(box_number):
    """Search for a box by box number and return its details including location"""
    try:
        if not box_number:
            return False, {'message': 'Box number is required'}, 400
        
        conn = get_db_connection()
        cursor = conn.cursor()
        
        cursor.execute("""
            SELECT 
                b.id,
                b.box_number,
                b.status,
                b.location_id,
                w.location_code
            FROM boxes_crates b
            LEFT JOIN warehouse_locations w ON b.location_id = w.id
            WHERE b.box_number = %s
        """, (box_number,))
        
        result = cursor.fetchone()
        conn.close()
        
        if result:
            return True, {
                'box': {
                    'id': result[0],
                    'box_number': result[1],
                    'status': result[2],
                    'location_id': result[3],
                    'location_code': result[4]
                }
            }, 200
        else:
            return False, {'message': f'Box "{box_number}" not found in the system'}, 404

4. Complete Workflow

From FG Scan to Box Label Printing

1. USER SCANS CP CODE
   ↓
2. FG SCAN FORM SUBMISSION (AJAX)
   - POST /fg_scan with scan data
   - Quality code must be 000 (approved)
   ↓
3. SCAN RECORDED IN DATABASE
   - Inserted into scanfg_orders table
   - Quantities calculated by trigger
   ↓
4. BOX MODAL DISPLAYED
   - Shows CP code that was just scanned
   - Focuses on box number input
   ↓
5. USER ENTERS BOX NUMBER
   - Can scan or type existing box number
   - Or create new box (if enabled)
   ↓
6. CP ASSIGNED TO BOX
   - POST /warehouse/assign_cp_to_box
   - Data inserted into box_contents table
   - Records: box_id, cp_code, scanned_by, timestamp
   ↓
7. BOX LABEL PRINTED (if enabled)
   - QZ Tray connects to printer
   - Box label PDF generated
   - Sent to printer
   ↓
8. READY FOR NEXT SCAN
   - Modal closes
   - Form cleared
   - Focus returns to CP code input

5. Data Inserted into Boxes Table

When Creating a Box

boxes_crates table:

INSERT INTO boxes_crates (box_number, status, location_id, created_by)
VALUES ('00000001', 'open', NULL, 'operator_username');

Data Details:

  • box_number: Auto-generated (00000001, 00000002, etc.)
  • status: Always starts as 'open' (can scan items into it)
  • location_id: NULL initially (assigned later when moved to warehouse location)
  • created_at: CURRENT_TIMESTAMP (automatic)
  • updated_at: CURRENT_TIMESTAMP (automatic)
  • created_by: Session username

When Assigning CP Code to Box

box_contents table:

INSERT INTO box_contents (box_id, cp_code, scanned_by)
VALUES (1, 'CP12345678-0001', 'operator_username');

Data Details:

  • box_id: Foreign key to boxes_crates.id
  • cp_code: The CP code from the scan
  • scan_id: NULL (optional, could link to scanfg_orders.id)
  • scanned_by: Session username
  • scanned_at: CURRENT_TIMESTAMP (automatic)

6. Box Label Printing Solution

QZ Tray Integration

Location: /srv/quality_app/py_app/app/templates/fg_scan.html (lines 7-9)

<!-- QZ Tray for printing - using local patched version for pairing-key authentication -->
<script src="{{ url_for('static', filename='qz-tray.js') }}"></script>

Features:

  • Local patched version for pairing-key authentication
  • Connects when scan-to-boxes toggle is enabled
  • Handles direct printer communication
  • Supports page-by-page printing

Label Generation

Box Label PDF Structure:

  • Location: warehouse.py (lines 220-400)
  • Page Size: 8cm x 5cm landscape
  • Content: Box number as text + barcode
  • Uses ReportLab for PDF generation
from reportlab.lib.pagesizes import landscape
from reportlab.pdfgen import canvas
from reportlab.graphics.barcode import code128
from reportlab.lib.units import mm

# 8cm x 5cm landscape label
page_width = 80 * mm
page_height = 50 * mm

# Barcode generation
barcode = code128.Code128(
    box_number,
    barWidth=0.4*mm,
    barHeight=barcode_height,
    humanReadable=True,
    fontSize=10
)

7. API Endpoints

Warehouse Module Routes

Location: /srv/quality_app/py_app/app/routes.py (lines 5657-5717)

@bp.route('/api/warehouse/box/search', methods=['POST'])
@requires_warehouse_module
def api_search_box():
    """Search for a box by box number"""
    data = request.get_json()
    box_number = data.get('box_number', '').strip()
    success, response_data, status_code = search_box_by_number(box_number)
    return jsonify({'success': success, **response_data}), status_code

@bp.route('/api/warehouse/box/assign-location', methods=['POST'])
@requires_warehouse_module
def api_assign_box_to_location():
    """Assign a box to a warehouse location"""
    data = request.get_json()
    box_id = data.get('box_id')
    location_code = data.get('location_code', '').strip()
    success, response_data, status_code = assign_box_to_location(box_id, location_code)
    return jsonify({'success': success, **response_data}), status_code

@bp.route('/api/warehouse/box/change-status', methods=['POST'])
@requires_warehouse_module
def api_change_box_status():
    """Change the status of a box (open/closed)"""
    data = request.get_json()
    box_id = data.get('box_id')
    new_status = data.get('new_status', '').strip()
    success, response_data, status_code = change_box_status(box_id, new_status)
    return jsonify({'success': success, **response_data}), status_code

@bp.route('/api/warehouse/location/search', methods=['POST'])
@requires_warehouse_module
def api_search_location():
    """Search for a location and get all boxes in it"""
    data = request.get_json()
    location_code = data.get('location_code', '').strip()
    success, response_data, status_code = search_location_with_boxes(location_code)
    return jsonify({'success': success, **response_data}), status_code

@bp.route('/api/warehouse/box/move-location', methods=['POST'])
@requires_warehouse_module
def api_move_box_to_location():
    """Move a box from one location to another"""
    data = request.get_json()
    box_id = data.get('box_id')
    new_location_code = data.get('new_location_code', '').strip()
    success, response_data, status_code = move_box_to_new_location(box_id, new_location_code)
    return jsonify({'success': success, **response_data}), status_code

8. File Locations Summary

Component File Path Lines
Database Tables warehouse.py 32-62
Box Functions warehouse.py 155-183, 185-210, 212-232, 234-264, 266-284
Assign CP to Box warehouse.py 619-658
Search/Assign/Move Functions warehouse.py 740-980
FG Scan Route routes.py 1020-1090
Warehouse API Routes routes.py 5657-5717
Frontend JS fg_scan.html 10-200
QZ Tray Script fg_scan.html 7-9

9. Key Implementation Notes

Quality Control

  • Only approved items (quality_code = 000) trigger box modal
  • Rejected items reload page instead
  • Prevents mixing defective items in boxes

Auto-increment Box Numbers

  • 8-digit zero-padded format (00000001, 00000002)
  • Automatic generation on box creation
  • Ensures unique, scannable identifiers

Session Management

  • Operator username tracked in created_by and scanned_by fields
  • Enables full audit trail of who created and modified boxes

Toggle for Feature

  • localStorage persistence for scan-to-boxes setting
  • Separate from checkbox state on page refresh
  • QZ Tray only connects when enabled

Error Handling

  • AJAX error notifications to user
  • Graceful fallbacks for printer failures
  • Database transaction rollback on errors

10. Integration with New App

To implement in the new app (/srv/quality_app-v2):

  1. Copy database table schemas from warehouse.py
  2. Implement warehouse module in models
  3. Add FG scan route with AJAX support
  4. Create box assignment API endpoint
  5. Add QZ Tray integration to frontend
  6. Implement box label generation for PDFs
  7. Set up permissions for quality and warehouse modules

The implementation is modular and can be adapted to the new Flask structure.