- 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
606 lines
20 KiB
Markdown
606 lines
20 KiB
Markdown
# Quick Box Creation & Label Printing - Code Snippets Reference
|
|
|
|
## Quick Reference - Key Code Files and Functions
|
|
|
|
### 1. Database Table Creation Code
|
|
|
|
**File:** [app/warehouse.py](warehouse.py#L32-L62)
|
|
|
|
**boxes_crates table:**
|
|
```python
|
|
def ensure_boxes_crates_table():
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
cursor.execute("SHOW TABLES LIKE 'boxes_crates'")
|
|
result = cursor.fetchone()
|
|
if not result:
|
|
cursor.execute('''
|
|
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
|
|
)
|
|
''')
|
|
conn.commit()
|
|
conn.close()
|
|
except Exception as e:
|
|
print(f"Error ensuring boxes_crates table: {e}")
|
|
```
|
|
|
|
**box_contents table:**
|
|
```python
|
|
def ensure_box_contents_table():
|
|
"""Ensure box_contents table exists for tracking CP codes in boxes"""
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
cursor.execute("SHOW TABLES LIKE 'box_contents'")
|
|
result = cursor.fetchone()
|
|
if not result:
|
|
cursor.execute('''
|
|
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)
|
|
)
|
|
''')
|
|
conn.commit()
|
|
print("box_contents table created successfully")
|
|
conn.close()
|
|
except Exception as e:
|
|
print(f"Error ensuring box_contents table: {e}")
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Frontend JavaScript Implementation
|
|
|
|
**File:** [templates/fg_scan.html](templates/fg_scan.html#L10-L200)
|
|
|
|
**Global Variables:**
|
|
```javascript
|
|
// Global variables for scan-to-boxes feature
|
|
let scanToBoxesEnabled = false;
|
|
let currentCpCode = null;
|
|
```
|
|
|
|
**Main Submit Function:**
|
|
```javascript
|
|
async function submitScanWithBoxAssignment() {
|
|
const form = document.getElementById('fg-scan-form');
|
|
const formData = new FormData(form);
|
|
|
|
console.log('=== submitScanWithBoxAssignment called ===');
|
|
|
|
try {
|
|
const response = await fetch(window.location.href, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: formData
|
|
});
|
|
|
|
console.log('Response status:', response.status);
|
|
|
|
if (response.ok) {
|
|
currentCpCode = formData.get('cp_code');
|
|
const defectCode = formData.get('defect_code') || '000';
|
|
|
|
console.log('CP Code:', currentCpCode);
|
|
console.log('Defect Code:', defectCode);
|
|
|
|
showNotification('✅ Scan recorded successfully!', 'success');
|
|
|
|
// Only show box modal if quality code is 000
|
|
if (defectCode === '000' || defectCode === '0') {
|
|
console.log('Should show box modal');
|
|
showBoxModal(currentCpCode);
|
|
} else {
|
|
console.log('Defect code not 000, reloading page');
|
|
setTimeout(() => window.location.reload(), 1000);
|
|
}
|
|
|
|
// Clear form fields (except operator code)
|
|
document.getElementById('cp_code').value = '';
|
|
document.getElementById('oc1_code').value = '';
|
|
document.getElementById('oc2_code').value = '';
|
|
document.getElementById('defect_code').value = '';
|
|
} else {
|
|
console.error('Response not OK');
|
|
showNotification('❌ Scan submission failed', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in submitScanWithBoxAssignment:', error);
|
|
showNotification('❌ Error: ' + error.message, 'error');
|
|
}
|
|
}
|
|
```
|
|
|
|
**Show Modal Function:**
|
|
```javascript
|
|
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();
|
|
}
|
|
```
|
|
|
|
**Assign CP to Box Function:**
|
|
```javascript
|
|
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();
|
|
}
|
|
```
|
|
|
|
**Notification Helper:**
|
|
```javascript
|
|
function showNotification(message, type = 'info') {
|
|
const notification = document.createElement('div');
|
|
notification.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: ${type === 'success' ? '#4CAF50' : type === 'error' ? '#f44336' : type === 'warning' ? '#ff9800' : '#2196F3'};
|
|
color: white;
|
|
padding: 15px 20px;
|
|
border-radius: 5px;
|
|
z-index: 10001;
|
|
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
|
|
font-weight: bold;
|
|
`;
|
|
notification.textContent = message;
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
if (notification.parentNode) {
|
|
notification.parentNode.removeChild(notification);
|
|
}
|
|
}, 4000);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Backend Routes
|
|
|
|
**File:** [app/routes.py](routes.py#L1020-L1090)
|
|
|
|
**FG Scan Route:**
|
|
```python
|
|
@bp.route('/fg_scan', methods=['GET', 'POST'])
|
|
@requires_quality_module
|
|
def fg_scan():
|
|
# Ensure scanfg_orders table exists
|
|
ensure_scanfg_orders_table()
|
|
|
|
if request.method == 'POST':
|
|
# Handle form submission
|
|
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')
|
|
|
|
try:
|
|
# Connect to the database
|
|
with db_connection_context() as conn:
|
|
cursor = conn.cursor()
|
|
|
|
# Always insert a new entry
|
|
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()
|
|
|
|
# Get the quantities from the newly inserted row
|
|
cp_base_code = cp_code[:10]
|
|
cursor.execute("""
|
|
SELECT approved_quantity, rejected_quantity
|
|
FROM scanfg_orders
|
|
WHERE CP_full_code = %s
|
|
""", (cp_code,))
|
|
result = cursor.fetchone()
|
|
approved_count = result[0] if result else 0
|
|
rejected_count = result[1] if result else 0
|
|
|
|
# Flash appropriate message
|
|
if int(defect_code) == 0:
|
|
flash(f'✅ APPROVED scan recorded for {cp_code}. Total approved: {approved_count}')
|
|
else:
|
|
flash(f'❌ REJECTED scan recorded for {cp_code} (defect: {defect_code}). Total rejected: {rejected_count}')
|
|
|
|
except mariadb.Error as e:
|
|
print(f"Error saving finish goods scan data: {e}")
|
|
flash(f"Error saving scan data: {e}")
|
|
|
|
# Check if this is an AJAX request (for scan-to-boxes feature)
|
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest' or \
|
|
request.accept_mimetypes.best == 'application/json':
|
|
# For AJAX requests, return JSON response without redirect
|
|
return jsonify({'success': True, 'message': 'Scan recorded successfully'})
|
|
|
|
# For normal form submissions, redirect to prevent form resubmission
|
|
return redirect(url_for('main.fg_scan'))
|
|
|
|
# Fetch the latest scan data for display
|
|
scan_data = []
|
|
try:
|
|
with db_connection_context() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("""
|
|
SELECT Id, operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time,
|
|
approved_quantity, rejected_quantity
|
|
FROM scanfg_orders
|
|
ORDER BY Id DESC
|
|
LIMIT 15
|
|
""")
|
|
raw_scan_data = cursor.fetchall()
|
|
# Apply formatting to scan data
|
|
scan_data = [[format_cell_data(cell) for cell in row] for row in raw_scan_data]
|
|
except mariadb.Error as e:
|
|
print(f"Error fetching finish goods scan data: {e}")
|
|
flash(f"Error fetching scan data: {e}")
|
|
|
|
return render_template('fg_scan.html', scan_data=scan_data)
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Box Management Functions
|
|
|
|
**File:** [app/warehouse.py](warehouse.py#L155-L210)
|
|
|
|
**Generate Box Number:**
|
|
```python
|
|
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:**
|
|
```python
|
|
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}"
|
|
```
|
|
|
|
---
|
|
|
|
### 5. CP to Box Assignment
|
|
|
|
**File:** [app/warehouse.py](warehouse.py#L619-L658)
|
|
|
|
**Assign CP to Box Handler:**
|
|
```python
|
|
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 exists
|
|
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 the 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
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
print(f"Error in assign_cp_to_box_handler: {e}")
|
|
print(traceback.format_exc())
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
```
|
|
|
|
---
|
|
|
|
### 6. Box Search and Management
|
|
|
|
**File:** [app/warehouse.py](warehouse.py#L740-L830)
|
|
|
|
**Search Box by Number:**
|
|
```python
|
|
def search_box_by_number(box_number):
|
|
"""
|
|
Search for a box by box number and return its details including location
|
|
|
|
Returns:
|
|
tuple: (success: bool, data: dict, status_code: int)
|
|
"""
|
|
try:
|
|
if not box_number:
|
|
return False, {'message': 'Box number is required'}, 400
|
|
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
# Search for the box and get its location info
|
|
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
|
|
|
|
except Exception as e:
|
|
return False, {'message': f'Error searching for box: {str(e)}'}, 500
|
|
```
|
|
|
|
**Assign Box to Location:**
|
|
```python
|
|
def assign_box_to_location(box_id, location_code):
|
|
"""Assign a box to a warehouse location"""
|
|
try:
|
|
if not box_id or not location_code:
|
|
return False, {'message': 'Box ID and location code are required'}, 400
|
|
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
# Check if location exists
|
|
cursor.execute("SELECT id FROM warehouse_locations WHERE location_code = %s", (location_code,))
|
|
location_result = cursor.fetchone()
|
|
|
|
if not location_result:
|
|
conn.close()
|
|
return False, {'message': f'Location "{location_code}" not found in the system'}, 404
|
|
|
|
location_id = location_result[0]
|
|
|
|
# Update box location
|
|
cursor.execute("""
|
|
UPDATE boxes_crates
|
|
SET location_id = %s, updated_at = NOW()
|
|
WHERE id = %s
|
|
""", (location_id, box_id))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return True, {'message': f'Box successfully assigned to location "{location_code}"'}, 200
|
|
|
|
except Exception as e:
|
|
return False, {'message': f'Error assigning box to location: {str(e)}'}, 500
|
|
```
|
|
|
|
**Change Box Status:**
|
|
```python
|
|
def change_box_status(box_id, new_status):
|
|
"""Change the status of a box (open/closed)"""
|
|
try:
|
|
if not box_id:
|
|
return False, {'message': 'Box ID is required'}, 400
|
|
|
|
if new_status not in ['open', 'closed']:
|
|
return False, {'message': 'Invalid status. Must be "open" or "closed"'}, 400
|
|
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
# Get box number for response message
|
|
cursor.execute("SELECT box_number FROM boxes_crates WHERE id = %s", (box_id,))
|
|
box_result = cursor.fetchone()
|
|
|
|
if not box_result:
|
|
conn.close()
|
|
return False, {'message': 'Box not found'}, 404
|
|
|
|
box_number = box_result[0]
|
|
|
|
# Update box status
|
|
cursor.execute("""
|
|
UPDATE boxes_crates
|
|
SET status = %s, updated_at = NOW()
|
|
WHERE id = %s
|
|
""", (new_status, box_id))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return True, {'message': f'Box "{box_number}" status changed to "{new_status}"'}, 200
|
|
|
|
except Exception as e:
|
|
return False, {'message': f'Error changing box status: {str(e)}'}, 500
|
|
```
|
|
|
|
---
|
|
|
|
### 7. API Routes
|
|
|
|
**File:** [app/routes.py](routes.py#L5657-L5717)
|
|
|
|
**Warehouse Box API Routes:**
|
|
```python
|
|
@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 (only if box is closed)"""
|
|
data = request.get_json()
|
|
box_id = data.get('box_id')
|
|
location_code = data.get('location_code', '').strip()
|
|
|
|
# Additional check: verify box is closed before assigning
|
|
if box_id:
|
|
try:
|
|
with db_connection_context() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT status FROM boxes_crates WHERE id = %s", (box_id,))
|
|
result = cursor.fetchone()
|
|
|
|
if result and result[0] == 'open':
|
|
return jsonify({
|
|
'success': False,
|
|
'message': 'Cannot assign an open box to a location. Please close the box first.'
|
|
}), 400
|
|
except Exception as e:
|
|
pass # Continue to the main function
|
|
|
|
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
|
|
```
|
|
|
|
---
|
|
|
|
## Summary of Key Code Locations
|
|
|
|
| Feature | File | Lines | Function |
|
|
|---------|------|-------|----------|
|
|
| Create boxes_crates table | warehouse.py | 32-45 | `ensure_boxes_crates_table()` |
|
|
| Create box_contents table | warehouse.py | 47-62 | `ensure_box_contents_table()` |
|
|
| Generate box numbers | warehouse.py | 155-165 | `generate_box_number()` |
|
|
| Add new box | warehouse.py | 167-183 | `add_box()` |
|
|
| Assign CP to box | warehouse.py | 619-658 | `assign_cp_to_box_handler()` |
|
|
| Search box | warehouse.py | 740-785 | `search_box_by_number()` |
|
|
| Assign box to location | warehouse.py | 787-830 | `assign_box_to_location()` |
|
|
| Change box status | warehouse.py | 906-965 | `change_box_status()` |
|
|
| FG Scan route | routes.py | 1020-1090 | `fg_scan()` |
|
|
| Warehouse API routes | routes.py | 5657-5717 | Multiple API endpoints |
|
|
| Frontend JS | fg_scan.html | 10-200 | Multiple JS functions |
|
|
|
|
All code snippets are from [/srv/quality_app/py_app/app/](../py_app/app/) directory.
|