- 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
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 boxcreated_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 incp_code: The finish good CP code being scannedscanned_by: Username of operator who scannedscanned_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.idcp_code: The CP code from the scanscan_id: NULL (optional, could link to scanfg_orders.id)scanned_by: Session usernamescanned_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_byandscanned_byfields - 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):
- Copy database table schemas from warehouse.py
- Implement warehouse module in models
- Add FG scan route with AJAX support
- Create box assignment API endpoint
- Add QZ Tray integration to frontend
- Implement box label generation for PDFs
- Set up permissions for quality and warehouse modules
The implementation is modular and can be adapted to the new Flask structure.