- 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
1450 lines
55 KiB
HTML
1450 lines
55 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}FG Scan - Quality App{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/fg_scan.css') }}">
|
||
<script src="https://cdn.jsdelivr.net/npm/qz-tray@2.1.0/qz-tray.js"></script>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="scan-container">
|
||
<!-- Form Card -->
|
||
<div class="scan-form-card">
|
||
<h3 class="form-title">FG Scan Entry</h3>
|
||
<form id="scanForm" method="POST" class="scan-form">
|
||
<label for="operator_code">Operator Code:</label>
|
||
<input type="text" id="operator_code" name="operator_code" required autocomplete="off">
|
||
<div class="error-message" id="error_operator_code"></div>
|
||
|
||
<label for="cp_code">CP Code:</label>
|
||
<input type="text" id="cp_code" name="cp_code" required autocomplete="off">
|
||
<div class="error-message" id="error_cp_code"></div>
|
||
|
||
<label for="oc1_code">OC1 Code:</label>
|
||
<input type="text" id="oc1_code" name="oc1_code" autocomplete="off">
|
||
<div class="error-message" id="error_oc1_code"></div>
|
||
|
||
<label for="oc2_code">OC2 Code:</label>
|
||
<input type="text" id="oc2_code" name="oc2_code" autocomplete="off">
|
||
<div class="error-message" id="error_oc2_code"></div>
|
||
|
||
<label for="defect_code">Defect Code:</label>
|
||
<input type="text" id="defect_code" name="defect_code" maxlength="3" autocomplete="off">
|
||
<div class="error-message" id="error_defect_code"></div>
|
||
|
||
<label for="date_time">Date/Time:</label>
|
||
<input type="text" id="date_time" name="date_time" readonly>
|
||
|
||
<!-- Hidden fields for actual date/time submission -->
|
||
<input type="hidden" id="date" name="date">
|
||
<input type="hidden" id="time" name="time">
|
||
|
||
<div class="form-buttons">
|
||
<button type="submit" class="btn-submit">Submit Scan</button>
|
||
<button type="button" class="btn-clear" id="clearOperator">Clear Quality Operator</button>
|
||
</div>
|
||
|
||
<div class="form-options">
|
||
<label class="checkbox-label">
|
||
<input type="checkbox" id="scanToBoxes" name="scan_to_boxes">
|
||
Scan To Boxes
|
||
</label>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Latest Scans Table -->
|
||
<div class="scan-table-card">
|
||
<h3 class="table-title">Latest Scans</h3>
|
||
<div class="table-wrapper">
|
||
<table class="scan-table">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Op Code</th>
|
||
<th>CP Code</th>
|
||
<th>OC1</th>
|
||
<th>OC2</th>
|
||
<th>Defect Code</th>
|
||
<th>Date</th>
|
||
<th>Time</th>
|
||
<th>Approved Qty</th>
|
||
<th>Rejected Qty</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="scansTableBody">
|
||
{% if scan_groups %}
|
||
{% for scan_group in scan_groups %}
|
||
<tr>
|
||
<td>{{ scan_group.id }}</td>
|
||
<td>{{ scan_group.operator_code }}</td>
|
||
<td>{{ scan_group.cp_code }}</td>
|
||
<td>{{ scan_group.oc1_code or '-' }}</td>
|
||
<td>{{ scan_group.oc2_code or '-' }}</td>
|
||
<td>{{ scan_group.defect_code or '-' }}</td>
|
||
<td>{{ scan_group.date }}</td>
|
||
<td>{{ scan_group.time }}</td>
|
||
<td>{{ scan_group.approved_qty }}</td>
|
||
<td>{{ scan_group.rejected_qty }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
{% else %}
|
||
<tr>
|
||
<td colspan="10" style="text-align: center; padding: 20px; color: var(--text-secondary);">
|
||
<i class="fas fa-inbox"></i> No scans recorded yet. Submit a scan to see results here.
|
||
</td>
|
||
</tr>
|
||
{% endif %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Box Assignment Modal -->
|
||
<div id="boxAssignmentModal" class="box-modal" style="display: none;">
|
||
<div class="box-modal-content">
|
||
<div class="modal-header">
|
||
<h2>Assign to Box</h2>
|
||
<button type="button" class="modal-close" id="closeModal">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<!-- Display CP Code -->
|
||
<p style="margin-bottom: 20px; font-size: 0.95em; font-weight: 500;">
|
||
CP Code: <strong id="modal-cp-code" style="color: #007bff;">-</strong>
|
||
</p>
|
||
|
||
<!-- OPTION 1: Quick Box Creation -->
|
||
<div style="margin: 20px 0; padding: 15px; background: #f0f8ff; border: 1px solid #cce7ff; border-radius: 5px;">
|
||
<button type="button" id="quickBoxLabel" class="btn"
|
||
style="width: 100%; background: #28a745; color: white; padding: 10px; font-size: 1em; border: none; border-radius: 4px; cursor: pointer; font-weight: 600;">
|
||
📦 Quick Box Label Creation
|
||
</button>
|
||
<p style="font-size: 0.85em; color: #666; margin-top: 8px; text-align: center;">
|
||
Creates new box and prints label immediately
|
||
</p>
|
||
</div>
|
||
|
||
<!-- SEPARATOR -->
|
||
<div style="text-align: center; margin: 20px 0; color: #999; font-size: 0.9em; letter-spacing: 1px;">
|
||
━━━━━━━ OR ━━━━━━━
|
||
</div>
|
||
|
||
<!-- OPTION 2: Scan Existing Box -->
|
||
<div style="margin: 20px 0;">
|
||
<label for="boxNumber" style="font-weight: 600; display: block; margin-bottom: 8px; color: #333;">Scan Box Number:</label>
|
||
<input type="text" id="boxNumber" placeholder="Scan or enter box number"
|
||
style="width: 100%; padding: 8px; font-size: 1em; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; font-size: 1.1em;">
|
||
<p style="font-size: 0.85em; color: #666; margin-top: 5px;">
|
||
Scan an existing box label or enter the box number manually
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer" style="padding: 15px 20px; border-top: 1px solid #eee; display: flex; justify-content: flex-end; gap: 10px;">
|
||
<button type="button" class="btn-secondary" id="cancelModal" style="padding: 8px 16px;">Skip</button>
|
||
<button type="button" class="btn-submit" id="assignToBox" style="padding: 8px 16px;">Assign to Box</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Global variables
|
||
let scanToBoxesEnabled = false;
|
||
let currentCpCode = '';
|
||
let currentScanId = null;
|
||
let qzTrayReady = false;
|
||
let cpCodeLastInputTime = null;
|
||
|
||
// ===== FORM VALIDATION FUNCTION =====
|
||
function validateForm() {
|
||
const operatorCode = document.getElementById('operator_code').value.trim();
|
||
const cpCode = document.getElementById('cp_code').value.trim();
|
||
const oc1Code = document.getElementById('oc1_code').value.trim();
|
||
const oc2Code = document.getElementById('oc2_code').value.trim();
|
||
const defectCode = document.getElementById('defect_code').value.trim();
|
||
|
||
// Check required fields
|
||
if (!operatorCode || !operatorCode.startsWith('OP') || operatorCode.length !== 4) {
|
||
console.log('❌ Operator code validation failed');
|
||
return false;
|
||
}
|
||
|
||
if (!cpCode || !cpCode.startsWith('CP') || cpCode.length !== 15) {
|
||
console.log('❌ CP code validation failed');
|
||
return false;
|
||
}
|
||
|
||
if (!oc1Code || !oc1Code.startsWith('OC') || oc1Code.length !== 4) {
|
||
console.log('❌ OC1 code validation failed');
|
||
return false;
|
||
}
|
||
|
||
if (!oc2Code || !oc2Code.startsWith('OC') || oc2Code.length !== 4) {
|
||
console.log('❌ OC2 code validation failed');
|
||
return false;
|
||
}
|
||
|
||
if (!defectCode || !/^\d{3}$/.test(defectCode)) {
|
||
console.log('❌ Defect code validation failed');
|
||
return false;
|
||
}
|
||
|
||
console.log('✅ All validations passed');
|
||
return true;
|
||
}
|
||
|
||
// ===== RESTORE CHECKBOX STATE FROM LOCALSTORAGE =====
|
||
const savedCheckboxState = localStorage.getItem('scan_to_boxes_enabled');
|
||
if (savedCheckboxState === 'true') {
|
||
scanToBoxesEnabled = true;
|
||
}
|
||
|
||
// Get form input references FIRST (before using them) - with null safety check
|
||
const operatorCodeInput = document.getElementById('operator_code');
|
||
const cpCodeInput = document.getElementById('cp_code');
|
||
const oc1CodeInput = document.getElementById('oc1_code');
|
||
const oc2CodeInput = document.getElementById('oc2_code');
|
||
const defectCodeInput = document.getElementById('defect_code');
|
||
|
||
// Restore checkbox visual state (must be done after DOM is ready)
|
||
// Use a small delay to ensure the checkbox element exists
|
||
setTimeout(function() {
|
||
const scanToBoxesCheckbox = document.getElementById('scanToBoxes');
|
||
if (scanToBoxesCheckbox) {
|
||
scanToBoxesCheckbox.checked = scanToBoxesEnabled;
|
||
// Also update the display of quickBoxSection if it exists
|
||
const quickBoxSection = document.getElementById('quickBoxSection');
|
||
if (quickBoxSection) {
|
||
quickBoxSection.style.display = scanToBoxesEnabled ? 'block' : 'none';
|
||
}
|
||
console.log('✅ Checkbox state restored:', scanToBoxesEnabled);
|
||
}
|
||
}, 100);
|
||
|
||
// Safety check - ensure all form inputs are available
|
||
if (!operatorCodeInput || !cpCodeInput || !oc1CodeInput || !oc2CodeInput || !defectCodeInput) {
|
||
console.error('❌ Error: Required form inputs not found in DOM');
|
||
console.log('operatorCodeInput:', operatorCodeInput);
|
||
console.log('cpCodeInput:', cpCodeInput);
|
||
console.log('oc1CodeInput:', oc1CodeInput);
|
||
console.log('oc2CodeInput:', oc2CodeInput);
|
||
console.log('defectCodeInput:', defectCodeInput);
|
||
}
|
||
|
||
// Initialize QZ Tray only when needed (lazy loading)
|
||
// Don't connect on page load - only connect when user enables "Scan To Boxes"
|
||
function initializeQzTray() {
|
||
if (typeof qz === 'undefined') {
|
||
console.log('ℹ️ QZ Tray library not loaded');
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
qz.security.setSignaturePromise(function(toSign) {
|
||
return new Promise(function(resolve, reject) {
|
||
// For development, we'll allow unsigned requests
|
||
resolve();
|
||
});
|
||
});
|
||
|
||
qz.websocket.connect().then(function() {
|
||
qzTrayReady = true;
|
||
console.log('✅ QZ Tray connected successfully');
|
||
}).catch(function(err) {
|
||
console.log('ℹ️ QZ Tray connection failed:', err);
|
||
qzTrayReady = false;
|
||
});
|
||
return true;
|
||
} catch(err) {
|
||
console.log('ℹ️ QZ Tray initialization error:', err);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Update date/time display
|
||
function updateDateTime() {
|
||
const now = new Date();
|
||
const dateStr = now.toLocaleDateString('en-US');
|
||
const timeStr = now.toLocaleTimeString('en-US', { hour12: true });
|
||
const dateTimeInput = document.getElementById('date_time');
|
||
if (dateTimeInput) {
|
||
dateTimeInput.value = dateStr + ' ' + timeStr;
|
||
}
|
||
}
|
||
|
||
updateDateTime();
|
||
setInterval(updateDateTime, 1000);
|
||
|
||
// Load operator code from localStorage
|
||
function loadOperatorCode() {
|
||
if (!operatorCodeInput) return;
|
||
const saved = localStorage.getItem('quality_operator_code');
|
||
if (saved) {
|
||
operatorCodeInput.value = saved;
|
||
}
|
||
}
|
||
|
||
// Check if we need to clear fields after a successful submission
|
||
const shouldClearAfterSubmit = localStorage.getItem('fg_scan_clear_after_submit');
|
||
if (shouldClearAfterSubmit === 'true' && cpCodeInput && oc1CodeInput && oc2CodeInput && defectCodeInput) {
|
||
// Clear the flag
|
||
localStorage.removeItem('fg_scan_clear_after_submit');
|
||
localStorage.removeItem('fg_scan_last_cp');
|
||
localStorage.removeItem('fg_scan_last_defect');
|
||
|
||
// Clear CP code, OC1, OC2, and defect code for next scan (NOT operator code)
|
||
cpCodeInput.value = '';
|
||
oc1CodeInput.value = '';
|
||
oc2CodeInput.value = '';
|
||
defectCodeInput.value = '';
|
||
|
||
// Show success indicator
|
||
setTimeout(function() {
|
||
// Focus on CP code field for next scan
|
||
if (cpCodeInput) {
|
||
cpCodeInput.focus();
|
||
}
|
||
|
||
// Add visual feedback - small notification on right side
|
||
const successIndicator = document.createElement('div');
|
||
successIndicator.style.cssText = `
|
||
position: fixed;
|
||
top: 30px;
|
||
right: 30px;
|
||
background: #4CAF50;
|
||
color: white;
|
||
padding: 12px 20px;
|
||
border-radius: 6px;
|
||
z-index: 9999;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.25);
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
white-space: nowrap;
|
||
animation: slideInRight 0.3s ease-out, slideOutRight 0.3s ease-in 2.7s forwards;
|
||
`;
|
||
successIndicator.textContent = '✅ Scan recorded! Ready for next scan';
|
||
document.body.appendChild(successIndicator);
|
||
|
||
// Add keyframe animation if not already added
|
||
if (!document.getElementById('fg-scan-notification-styles')) {
|
||
const style = document.createElement('style');
|
||
style.id = 'fg-scan-notification-styles';
|
||
style.textContent = `
|
||
@keyframes slideInRight {
|
||
from {
|
||
transform: translateX(400px);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
@keyframes slideOutRight {
|
||
from {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
to {
|
||
transform: translateX(400px);
|
||
opacity: 0;
|
||
}
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
}
|
||
|
||
// Remove success indicator after 3 seconds
|
||
setTimeout(function() {
|
||
if (successIndicator.parentNode) {
|
||
successIndicator.parentNode.removeChild(successIndicator);
|
||
}
|
||
}, 3000);
|
||
}, 100);
|
||
}
|
||
|
||
// Focus on the first empty required field (only if not clearing after submit)
|
||
if (shouldClearAfterSubmit !== 'true' && operatorCodeInput && cpCodeInput && oc1CodeInput && oc2CodeInput && defectCodeInput) {
|
||
if (!operatorCodeInput.value) {
|
||
operatorCodeInput.focus();
|
||
} else if (!cpCodeInput.value) {
|
||
cpCodeInput.focus();
|
||
} else if (!oc1CodeInput.value) {
|
||
oc1CodeInput.focus();
|
||
} else if (!oc2CodeInput.value) {
|
||
oc2CodeInput.focus();
|
||
} else {
|
||
defectCodeInput.focus();
|
||
}
|
||
}
|
||
|
||
loadOperatorCode();
|
||
|
||
// Create error message elements with null safety checks
|
||
let operatorErrorMessage, cpErrorMessage, oc1ErrorMessage, oc2ErrorMessage, defectErrorMessage;
|
||
|
||
if (operatorCodeInput && operatorCodeInput.parentNode) {
|
||
operatorErrorMessage = document.createElement('div');
|
||
operatorErrorMessage.className = 'error-message';
|
||
operatorErrorMessage.id = 'operator-error';
|
||
operatorErrorMessage.textContent = 'Operator code format: OP + 2 digits (example: OP01, OP99)';
|
||
operatorCodeInput.parentNode.insertBefore(operatorErrorMessage, operatorCodeInput.nextSibling);
|
||
}
|
||
|
||
if (cpCodeInput && cpCodeInput.parentNode) {
|
||
cpErrorMessage = document.createElement('div');
|
||
cpErrorMessage.className = 'error-message';
|
||
cpErrorMessage.id = 'cp-error';
|
||
cpErrorMessage.textContent = 'CP code format: CP + 8 digits + hyphen + 4 digits (example: CP00000000-0001)';
|
||
cpCodeInput.parentNode.insertBefore(cpErrorMessage, cpCodeInput.nextSibling);
|
||
}
|
||
|
||
if (oc1CodeInput && oc1CodeInput.parentNode) {
|
||
oc1ErrorMessage = document.createElement('div');
|
||
oc1ErrorMessage.className = 'error-message';
|
||
oc1ErrorMessage.id = 'oc1-error';
|
||
oc1ErrorMessage.textContent = 'OC1 code format: OC + 2 digits (example: OC01, OC99)';
|
||
oc1CodeInput.parentNode.insertBefore(oc1ErrorMessage, oc1CodeInput.nextSibling);
|
||
}
|
||
|
||
if (oc2CodeInput && oc2CodeInput.parentNode) {
|
||
oc2ErrorMessage = document.createElement('div');
|
||
oc2ErrorMessage.className = 'error-message';
|
||
oc2ErrorMessage.id = 'oc2-error';
|
||
oc2ErrorMessage.textContent = 'OC2 code format: OC + 2 digits (example: OC01, OC99)';
|
||
oc2CodeInput.parentNode.insertBefore(oc2ErrorMessage, oc2CodeInput.nextSibling);
|
||
}
|
||
|
||
if (defectCodeInput && defectCodeInput.parentNode) {
|
||
defectErrorMessage = document.createElement('div');
|
||
defectErrorMessage.className = 'error-message';
|
||
defectErrorMessage.id = 'defect-error';
|
||
defectErrorMessage.textContent = 'Defect code must be a 3-digit number (e.g., 000, 001, 123)';
|
||
defectCodeInput.parentNode.insertBefore(defectErrorMessage, defectCodeInput.nextSibling);
|
||
}
|
||
|
||
// Helper function to safely add/remove error classes
|
||
function showErrorMessage(element, message) {
|
||
if (element) {
|
||
element.classList.add('show');
|
||
}
|
||
}
|
||
|
||
function hideErrorMessage(element) {
|
||
if (element) {
|
||
element.classList.remove('show');
|
||
}
|
||
}
|
||
|
||
// ===== CP CODE AUTO-COMPLETE LOGIC =====
|
||
let cpCodeAutoCompleteTimeout = null;
|
||
|
||
function autoCompleteCpCode() {
|
||
const value = cpCodeInput.value.trim().toUpperCase();
|
||
|
||
// Only process if it starts with "CP" but is not 15 characters
|
||
if (value.startsWith('CP') && value.length < 15 && value.length > 2) {
|
||
console.log('Auto-completing CP code:', value);
|
||
|
||
// Check if there's a hyphen in the value
|
||
if (value.includes('-')) {
|
||
// Split by hyphen: CP[base]-[suffix]
|
||
const parts = value.split('-');
|
||
if (parts.length === 2) {
|
||
const cpPrefix = parts[0]; // e.g., "CP00002042"
|
||
const suffix = parts[1]; // e.g., "1" or "12" or "123" or "3"
|
||
|
||
console.log('CP prefix:', cpPrefix, 'Suffix:', suffix);
|
||
|
||
// Always pad the suffix to exactly 4 digits
|
||
const paddedSuffix = suffix.padStart(4, '0');
|
||
|
||
// Construct the complete CP code
|
||
const completedCpCode = `${cpPrefix}-${paddedSuffix}`;
|
||
|
||
console.log('Completed CP code length:', completedCpCode.length, 'Code:', completedCpCode);
|
||
|
||
// Ensure it's exactly 15 characters
|
||
if (completedCpCode.length === 15) {
|
||
console.log('✅ Completed CP code:', completedCpCode);
|
||
cpCodeInput.value = completedCpCode;
|
||
|
||
// Show visual feedback
|
||
cpCodeInput.style.backgroundColor = '#e8f5e9';
|
||
setTimeout(() => {
|
||
cpCodeInput.style.backgroundColor = '';
|
||
}, 500);
|
||
|
||
// Move focus to next field (OC1 code)
|
||
setTimeout(() => {
|
||
oc1CodeInput.focus();
|
||
console.log('✅ Auto-completed CP Code and advanced to OC1');
|
||
}, 50);
|
||
|
||
// Show completion notification
|
||
showNotification(`✅ CP Code auto-completed: ${completedCpCode}`, 'success');
|
||
} else {
|
||
console.log('⚠️ Completed code length is not 15:', completedCpCode.length);
|
||
}
|
||
}
|
||
} else {
|
||
console.log('⏳ Waiting for hyphen to be entered before auto-completing');
|
||
}
|
||
} else {
|
||
if (value.length >= 15) {
|
||
console.log('ℹ️ CP code is already complete (15 characters)');
|
||
}
|
||
}
|
||
}
|
||
|
||
if (cpCodeInput) {
|
||
cpCodeInput.addEventListener('input', function() {
|
||
cpCodeLastInputTime = Date.now();
|
||
const currentValue = this.value.trim().toUpperCase();
|
||
this.value = currentValue; // Convert to uppercase
|
||
|
||
// Clear existing timeout
|
||
if (cpCodeAutoCompleteTimeout) {
|
||
clearTimeout(cpCodeAutoCompleteTimeout);
|
||
}
|
||
|
||
console.log('CP Code input changed:', currentValue);
|
||
|
||
// Validate CP code format: CP + 8 digits + hyphen + 4 digits = 15 chars
|
||
if (currentValue.length >= 2 && !currentValue.startsWith('CP')) {
|
||
showErrorMessage(cpErrorMessage);
|
||
this.setCustomValidity('Must start with CP');
|
||
} else {
|
||
hideErrorMessage(cpErrorMessage);
|
||
this.setCustomValidity('');
|
||
|
||
// Auto-advance when field is complete and valid (exactly 15 chars: CP[8]-[4])
|
||
if (currentValue.length === 15 && currentValue.startsWith('CP')) {
|
||
setTimeout(() => {
|
||
if (oc1CodeInput) oc1CodeInput.focus();
|
||
console.log('✅ Auto-advanced from CP Code to OC1 Code');
|
||
}, 50);
|
||
}
|
||
}
|
||
|
||
// If hyphen is present and value is less than 15 chars, process immediately for auto-complete
|
||
if (currentValue.includes('-') && currentValue.length < 15) {
|
||
console.log('Hyphen detected, checking for auto-complete');
|
||
cpCodeAutoCompleteTimeout = setTimeout(() => {
|
||
console.log('Processing auto-complete after hyphen');
|
||
autoCompleteCpCode();
|
||
}, 500);
|
||
} else if (currentValue.length < 15 && currentValue.startsWith('CP')) {
|
||
// Set normal 2-second timeout only when no hyphen yet
|
||
cpCodeAutoCompleteTimeout = setTimeout(() => {
|
||
console.log('2-second timeout triggered for CP code');
|
||
autoCompleteCpCode();
|
||
}, 2000);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Also trigger auto-complete when focus leaves the field (blur event)
|
||
if (cpCodeInput) {
|
||
cpCodeInput.addEventListener('blur', function() {
|
||
console.log('CP Code blur event triggered with value:', this.value);
|
||
if (cpCodeAutoCompleteTimeout) {
|
||
clearTimeout(cpCodeAutoCompleteTimeout);
|
||
}
|
||
autoCompleteCpCode();
|
||
});
|
||
}
|
||
|
||
// Prevent leaving CP code field if invalid
|
||
if (cpCodeInput) {
|
||
cpCodeInput.addEventListener('blur', function(e) {
|
||
if (this.value.length > 0 && !this.value.startsWith('CP')) {
|
||
showErrorMessage(cpErrorMessage);
|
||
this.setCustomValidity('Must start with CP');
|
||
// Return focus to this field
|
||
setTimeout(() => {
|
||
this.focus();
|
||
this.select();
|
||
}, 0);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Prevent Tab/Enter from moving to next field if CP code is invalid
|
||
if (cpCodeInput) {
|
||
cpCodeInput.addEventListener('keydown', function(e) {
|
||
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('CP')) {
|
||
e.preventDefault();
|
||
showErrorMessage(cpErrorMessage);
|
||
this.setCustomValidity('Must start with CP');
|
||
this.select();
|
||
}
|
||
});
|
||
}
|
||
|
||
// Prevent focusing on CP code if operator code is invalid
|
||
if (cpCodeInput && operatorCodeInput) {
|
||
cpCodeInput.addEventListener('focus', function(e) {
|
||
if (operatorCodeInput.value.length > 0 && !operatorCodeInput.value.startsWith('OP')) {
|
||
e.preventDefault();
|
||
showErrorMessage(operatorErrorMessage);
|
||
operatorCodeInput.focus();
|
||
operatorCodeInput.select();
|
||
}
|
||
});
|
||
}
|
||
|
||
// ===== OPERATOR CODE VALIDATION =====
|
||
if (operatorCodeInput) {
|
||
operatorCodeInput.addEventListener('input', function() {
|
||
const value = this.value.toUpperCase();
|
||
this.value = value; // Convert to uppercase
|
||
|
||
if (value.length >= 2 && !value.startsWith('OP')) {
|
||
showErrorMessage(operatorErrorMessage);
|
||
this.setCustomValidity('Must start with OP');
|
||
} else {
|
||
hideErrorMessage(operatorErrorMessage);
|
||
this.setCustomValidity('');
|
||
|
||
// Auto-advance when field is complete and valid
|
||
if (value.length === 4 && value.startsWith('OP')) {
|
||
setTimeout(() => {
|
||
if (cpCodeInput) cpCodeInput.focus();
|
||
console.log('✅ Auto-advanced from Operator Code to CP Code');
|
||
}, 50);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
if (operatorCodeInput) {
|
||
operatorCodeInput.addEventListener('blur', function(e) {
|
||
if (this.value.length > 0 && !this.value.startsWith('OP')) {
|
||
showErrorMessage(operatorErrorMessage);
|
||
this.setCustomValidity('Must start with OP');
|
||
setTimeout(() => {
|
||
this.focus();
|
||
this.select();
|
||
}, 0);
|
||
}
|
||
});
|
||
}
|
||
|
||
if (operatorCodeInput) {
|
||
operatorCodeInput.addEventListener('keydown', function(e) {
|
||
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OP')) {
|
||
e.preventDefault();
|
||
showErrorMessage(operatorErrorMessage);
|
||
this.setCustomValidity('Must start with OP');
|
||
this.select();
|
||
}
|
||
});
|
||
}
|
||
|
||
// ===== OC1 CODE VALIDATION =====
|
||
if (oc1CodeInput) {
|
||
oc1CodeInput.addEventListener('input', function() {
|
||
const value = this.value.toUpperCase();
|
||
this.value = value; // Convert to uppercase
|
||
|
||
if (value.length >= 2 && !value.startsWith('OC')) {
|
||
showErrorMessage(oc1ErrorMessage);
|
||
this.setCustomValidity('Must start with OC');
|
||
} else {
|
||
hideErrorMessage(oc1ErrorMessage);
|
||
this.setCustomValidity('');
|
||
|
||
// Auto-advance when field is complete and valid
|
||
if (value.length === 4 && value.startsWith('OC')) {
|
||
setTimeout(() => {
|
||
if (oc2CodeInput) oc2CodeInput.focus();
|
||
console.log('✅ Auto-advanced from OC1 Code to OC2 Code');
|
||
}, 50);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
if (oc1CodeInput) {
|
||
oc1CodeInput.addEventListener('blur', function(e) {
|
||
if (this.value.length > 0 && !this.value.startsWith('OC')) {
|
||
showErrorMessage(oc1ErrorMessage);
|
||
this.setCustomValidity('Must start with OC');
|
||
setTimeout(() => {
|
||
this.focus();
|
||
this.select();
|
||
}, 0);
|
||
}
|
||
});
|
||
}
|
||
|
||
if (oc1CodeInput) {
|
||
oc1CodeInput.addEventListener('keydown', function(e) {
|
||
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OC')) {
|
||
e.preventDefault();
|
||
showErrorMessage(oc1ErrorMessage);
|
||
this.setCustomValidity('Must start with OC');
|
||
this.select();
|
||
}
|
||
});
|
||
}
|
||
|
||
if (oc1CodeInput && cpCodeInput) {
|
||
oc1CodeInput.addEventListener('focus', function(e) {
|
||
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
|
||
e.preventDefault();
|
||
showErrorMessage(cpErrorMessage);
|
||
cpCodeInput.focus();
|
||
cpCodeInput.select();
|
||
}
|
||
});
|
||
}
|
||
|
||
// ===== OC2 CODE VALIDATION =====
|
||
if (oc2CodeInput) {
|
||
oc2CodeInput.addEventListener('input', function() {
|
||
const value = this.value.toUpperCase();
|
||
this.value = value; // Convert to uppercase
|
||
|
||
if (value.length >= 2 && !value.startsWith('OC')) {
|
||
showErrorMessage(oc2ErrorMessage);
|
||
this.setCustomValidity('Must start with OC');
|
||
} else {
|
||
hideErrorMessage(oc2ErrorMessage);
|
||
this.setCustomValidity('');
|
||
|
||
// Auto-advance when field is complete and valid
|
||
if (value.length === 4 && value.startsWith('OC')) {
|
||
setTimeout(() => {
|
||
if (defectCodeInput) defectCodeInput.focus();
|
||
console.log('✅ Auto-advanced from OC2 Code to Defect Code');
|
||
}, 50);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
if (oc2CodeInput) {
|
||
oc2CodeInput.addEventListener('blur', function(e) {
|
||
if (this.value.length > 0 && !this.value.startsWith('OC')) {
|
||
showErrorMessage(oc2ErrorMessage);
|
||
this.setCustomValidity('Must start with OC');
|
||
setTimeout(() => {
|
||
this.focus();
|
||
this.select();
|
||
}, 0);
|
||
}
|
||
});
|
||
}
|
||
|
||
if (oc2CodeInput) {
|
||
oc2CodeInput.addEventListener('keydown', function(e) {
|
||
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OC')) {
|
||
e.preventDefault();
|
||
showErrorMessage(oc2ErrorMessage);
|
||
this.setCustomValidity('Must start with OC');
|
||
this.select();
|
||
}
|
||
});
|
||
}
|
||
|
||
if (oc2CodeInput && cpCodeInput && oc1CodeInput) {
|
||
oc2CodeInput.addEventListener('focus', function(e) {
|
||
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
|
||
e.preventDefault();
|
||
showErrorMessage(cpErrorMessage);
|
||
cpCodeInput.focus();
|
||
cpCodeInput.select();
|
||
}
|
||
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
|
||
e.preventDefault();
|
||
showErrorMessage(oc1ErrorMessage);
|
||
oc1CodeInput.focus();
|
||
oc1CodeInput.select();
|
||
}
|
||
});
|
||
}
|
||
|
||
// ===== DEFECT CODE VALIDATION =====
|
||
if (defectCodeInput) {
|
||
defectCodeInput.addEventListener('input', function() {
|
||
// Remove any non-digit characters
|
||
this.value = this.value.replace(/\D/g, '');
|
||
|
||
// Validate if it's a valid 3-digit number when length is 3
|
||
if (this.value.length === 3) {
|
||
const isValid = /^\d{3}$/.test(this.value);
|
||
if (!isValid) {
|
||
showErrorMessage(defectErrorMessage);
|
||
this.setCustomValidity('Must be a 3-digit number');
|
||
} else {
|
||
hideErrorMessage(defectErrorMessage);
|
||
this.setCustomValidity('');
|
||
}
|
||
} else {
|
||
hideErrorMessage(defectErrorMessage);
|
||
this.setCustomValidity('');
|
||
}
|
||
|
||
// Auto-submit when 3 characters are entered and all validations pass
|
||
if (this.value.length === 3) {
|
||
// Validate operator code before submitting
|
||
if (operatorCodeInput && !operatorCodeInput.value.startsWith('OP')) {
|
||
showErrorMessage(operatorErrorMessage);
|
||
operatorCodeInput.focus();
|
||
operatorCodeInput.setCustomValidity('Must start with OP');
|
||
return;
|
||
}
|
||
|
||
// Validate CP code before submitting
|
||
if (cpCodeInput && (!cpCodeInput.value.startsWith('CP') || cpCodeInput.value.length !== 15)) {
|
||
showErrorMessage(cpErrorMessage);
|
||
cpCodeInput.focus();
|
||
cpCodeInput.setCustomValidity('Must start with CP and be complete');
|
||
return;
|
||
}
|
||
|
||
// Validate OC1 code before submitting
|
||
if (oc1CodeInput && !oc1CodeInput.value.startsWith('OC')) {
|
||
showErrorMessage(oc1ErrorMessage);
|
||
oc1CodeInput.focus();
|
||
oc1CodeInput.setCustomValidity('Must start with OC');
|
||
return;
|
||
}
|
||
|
||
// Validate OC2 code before submitting
|
||
if (oc2CodeInput && !oc2CodeInput.value.startsWith('OC')) {
|
||
showErrorMessage(oc2ErrorMessage);
|
||
oc2CodeInput.focus();
|
||
oc2CodeInput.setCustomValidity('Must start with OC');
|
||
return;
|
||
}
|
||
|
||
// Validate defect code is a valid 3-digit number
|
||
const isValidDefectCode = /^\d{3}$/.test(this.value);
|
||
if (!isValidDefectCode) {
|
||
showErrorMessage(defectErrorMessage);
|
||
this.focus();
|
||
this.setCustomValidity('Must be a 3-digit number');
|
||
return;
|
||
}
|
||
|
||
// Clear all custom validity states before submitting
|
||
if (operatorCodeInput) operatorCodeInput.setCustomValidity('');
|
||
if (cpCodeInput) cpCodeInput.setCustomValidity('');
|
||
if (oc1CodeInput) oc1CodeInput.setCustomValidity('');
|
||
if (oc2CodeInput) oc2CodeInput.setCustomValidity('');
|
||
this.setCustomValidity('');
|
||
|
||
// ===== TIME FIELD AUTO-UPDATE (CRITICAL) =====
|
||
// Update time field to current time before submitting
|
||
const timeInput = document.getElementById('date_time');
|
||
const now = new Date();
|
||
const hours = String(now.getHours()).padStart(2, '0');
|
||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||
const timeValue = `${hours}:${minutes}:${seconds}`;
|
||
|
||
// Format date as YYYY-MM-DD for database
|
||
const year = now.getFullYear();
|
||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||
const day = String(now.getDate()).padStart(2, '0');
|
||
const dateValue = `${year}-${month}-${day}`;
|
||
|
||
// Parse the current datetime display and update just the time part
|
||
const dateStr = timeInput.value.split(' ').slice(0, -1).join(' '); // Get date part
|
||
timeInput.value = dateStr + ' ' + timeValue;
|
||
|
||
// Populate hidden date/time fields for form submission
|
||
document.getElementById('date').value = dateValue;
|
||
document.getElementById('time').value = timeValue;
|
||
|
||
console.log('✅ Time field updated to:', timeValue);
|
||
console.log('✅ Date field set to:', dateValue);
|
||
|
||
// Save current scan data to localStorage for clearing after reload
|
||
localStorage.setItem('fg_scan_clear_after_submit', 'true');
|
||
localStorage.setItem('fg_scan_last_cp', cpCodeInput.value);
|
||
localStorage.setItem('fg_scan_last_defect', defectCodeInput.value);
|
||
|
||
// Auto-submit the form - directly trigger submission logic
|
||
console.log('Auto-submitting form on 3-digit defect code');
|
||
const scanForm = document.getElementById('scanForm');
|
||
|
||
// Validate the form first
|
||
if (!validateForm()) {
|
||
console.log('Form validation failed');
|
||
return;
|
||
}
|
||
|
||
// Save operator code
|
||
localStorage.setItem('quality_operator_code', document.getElementById('operator_code').value.trim());
|
||
|
||
const formData = new FormData(scanForm);
|
||
|
||
// If AJAX is needed (scan to boxes)
|
||
if (scanToBoxesEnabled) {
|
||
console.log('Using AJAX submission (scanToBoxesEnabled = true)');
|
||
fetch('{{ url_for("quality.fg_scan") }}', {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-Requested-With': 'XMLHttpRequest'
|
||
},
|
||
body: formData
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
showNotification('✅ Scan saved successfully!', 'success');
|
||
|
||
// Store CP code and scan ID for modal
|
||
const cpCodeElement = document.getElementById('cp_code');
|
||
if (cpCodeElement) {
|
||
currentCpCode = cpCodeElement.value.trim();
|
||
}
|
||
currentScanId = data.scan_id;
|
||
console.log('📌 Scan ID stored:', currentScanId);
|
||
|
||
// Reset form
|
||
resetForm();
|
||
|
||
// Show box assignment modal with CP code - with null checks
|
||
const modalCpCodeEl = document.getElementById('modal-cp-code');
|
||
const boxNumberEl = document.getElementById('boxNumber');
|
||
const boxAssignmentModalEl = document.getElementById('boxAssignmentModal');
|
||
|
||
if (modalCpCodeEl) {
|
||
modalCpCodeEl.textContent = currentCpCode;
|
||
}
|
||
if (boxNumberEl) {
|
||
boxNumberEl.value = ''; // Clear previous entry
|
||
}
|
||
if (boxAssignmentModalEl) {
|
||
boxAssignmentModalEl.style.display = 'flex';
|
||
}
|
||
|
||
// Focus on box number input
|
||
setTimeout(() => {
|
||
const boxInput = document.getElementById('boxNumber');
|
||
if (boxInput) {
|
||
boxInput.focus();
|
||
}
|
||
}, 100);
|
||
} else {
|
||
showNotification('❌ Error saving scan', 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
showNotification('❌ Error saving scan: ' + error.message, 'error');
|
||
});
|
||
} else {
|
||
console.log('Using regular form submission (scanToBoxesEnabled = false)');
|
||
// For non-AJAX submission, use normal form submit which will reload page
|
||
scanForm.submit();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
if (defectCodeInput && cpCodeInput && oc1CodeInput && oc2CodeInput) {
|
||
defectCodeInput.addEventListener('focus', function(e) {
|
||
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
|
||
e.preventDefault();
|
||
showErrorMessage(cpErrorMessage);
|
||
cpCodeInput.focus();
|
||
cpCodeInput.select();
|
||
}
|
||
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
|
||
e.preventDefault();
|
||
showErrorMessage(oc1ErrorMessage);
|
||
oc1CodeInput.focus();
|
||
oc1CodeInput.select();
|
||
}
|
||
if (oc2CodeInput.value.length > 0 && !oc2CodeInput.value.startsWith('OC')) {
|
||
e.preventDefault();
|
||
showErrorMessage(oc2ErrorMessage);
|
||
oc2CodeInput.focus();
|
||
oc2CodeInput.select();
|
||
}
|
||
});
|
||
}
|
||
|
||
// ===== CLEAR OPERATOR CODE BUTTON =====
|
||
const clearOperatorBtn = document.getElementById('clearOperator');
|
||
if (clearOperatorBtn && operatorCodeInput) {
|
||
clearOperatorBtn.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
operatorCodeInput.value = '';
|
||
localStorage.removeItem('quality_operator_code');
|
||
operatorCodeInput.focus();
|
||
showNotification('Quality Operator Code cleared', 'info');
|
||
});
|
||
}
|
||
|
||
// ===== SAVE OPERATOR CODE ON INPUT =====
|
||
if (operatorCodeInput) {
|
||
operatorCodeInput.addEventListener('input', function() {
|
||
if (this.value.startsWith('OP') && this.value.length >= 3) {
|
||
localStorage.setItem('quality_operator_code', this.value);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Form submission
|
||
document.getElementById('scanForm').addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
|
||
if (!validateForm()) {
|
||
return;
|
||
}
|
||
|
||
// Save operator code
|
||
localStorage.setItem('quality_operator_code', document.getElementById('operator_code').value.trim());
|
||
|
||
const formData = new FormData(this);
|
||
|
||
// If AJAX is needed (scan to boxes)
|
||
if (scanToBoxesEnabled) {
|
||
fetch('{{ url_for("quality.fg_scan") }}', {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-Requested-With': 'XMLHttpRequest'
|
||
},
|
||
body: formData
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
showNotification('✅ Scan saved successfully!', 'success');
|
||
|
||
// Store CP code for modal
|
||
currentCpCode = document.getElementById('cp_code').value.trim();
|
||
|
||
// Reset form
|
||
resetForm();
|
||
|
||
// Show box assignment modal with CP code
|
||
document.getElementById('modal-cp-code').textContent = currentCpCode;
|
||
document.getElementById('boxNumber').value = ''; // Clear previous entry
|
||
document.getElementById('boxAssignmentModal').style.display = 'flex';
|
||
|
||
// Focus on box number input
|
||
setTimeout(() => {
|
||
document.getElementById('boxNumber').focus();
|
||
}, 100);
|
||
} else {
|
||
showNotification('❌ Error saving scan', 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
showNotification('❌ Error saving scan: ' + error.message, 'error');
|
||
});
|
||
} else {
|
||
// Regular form submission (non-AJAX)
|
||
this.submit();
|
||
}
|
||
});
|
||
|
||
// Scan to boxes functionality
|
||
document.getElementById('scanToBoxes').addEventListener('change', function() {
|
||
scanToBoxesEnabled = this.checked;
|
||
// Save checkbox state to localStorage for persistence across page refreshes
|
||
localStorage.setItem('scan_to_boxes_enabled', this.checked);
|
||
document.getElementById('quickBoxSection').style.display = this.checked ? 'block' : 'none';
|
||
|
||
if (this.checked) {
|
||
// Initialize QZ Tray when user enables the feature
|
||
console.log('✅ Scan To Boxes enabled - initializing QZ Tray...');
|
||
initializeQzTray();
|
||
} else {
|
||
console.log('ℹ️ Scan To Boxes disabled');
|
||
}
|
||
});
|
||
|
||
// 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;
|
||
}
|
||
|
||
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({})
|
||
}).catch(err => {
|
||
console.error('Create box endpoint not found, trying alternative URL');
|
||
throw err;
|
||
});
|
||
|
||
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 successfully');
|
||
showNotification(`✅ Box ${boxNumber} created and label printed!`, 'success');
|
||
|
||
// Step 4: Show box number (already shown in modal) - with null checks
|
||
console.log('📝 Step 4: Box number display...');
|
||
const boxNumberInput = document.getElementById('boxNumber');
|
||
if (boxNumberInput) {
|
||
boxNumberInput.value = boxNumber;
|
||
}
|
||
|
||
} catch (printError) {
|
||
console.warn('⚠️ QZ Tray print failed, using fallback:', printError);
|
||
showNotification(
|
||
`⚠️ Box ${boxNumber} created but QZ Tray print not available.\n` +
|
||
`Please check if QZ Tray is running.`,
|
||
'warning'
|
||
);
|
||
|
||
// Still populate box number for manual entry - with null checks
|
||
const boxNumberInput = document.getElementById('boxNumber');
|
||
const boxQtyInput = document.getElementById('boxQty');
|
||
if (boxNumberInput) {
|
||
boxNumberInput.value = boxNumber;
|
||
}
|
||
if (boxQtyInput) {
|
||
boxQtyInput.value = '1';
|
||
}
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error:', error);
|
||
showNotification(`❌ Error: ${error.message}`, 'error');
|
||
} finally {
|
||
// Re-enable button
|
||
this.disabled = false;
|
||
this.textContent = '📦 Quick Box Label Creation';
|
||
}
|
||
});
|
||
|
||
// Modal functionality
|
||
const closeModalBtn = document.getElementById('closeModal');
|
||
if (closeModalBtn) {
|
||
closeModalBtn.addEventListener('click', function() {
|
||
document.getElementById('boxAssignmentModal').style.display = 'none';
|
||
// Reload page to show updated scans table
|
||
setTimeout(() => {
|
||
location.reload();
|
||
}, 500);
|
||
});
|
||
}
|
||
|
||
const cancelModalBtn = document.getElementById('cancelModal');
|
||
if (cancelModalBtn) {
|
||
cancelModalBtn.addEventListener('click', function() {
|
||
showNotification('✅ Scan recorded without box assignment', 'success');
|
||
document.getElementById('boxAssignmentModal').style.display = 'none';
|
||
// Clear stored values
|
||
currentCpCode = '';
|
||
currentScanId = null;
|
||
// Reload page to show updated scans table
|
||
setTimeout(() => {
|
||
location.reload();
|
||
}, 500);
|
||
});
|
||
}
|
||
|
||
const assignToBoxBtn = document.getElementById('assignToBox');
|
||
if (assignToBoxBtn) {
|
||
assignToBoxBtn.addEventListener('click', async function() {
|
||
console.log('=== Assign to Box button clicked ===');
|
||
|
||
// Add null checks for DOM elements
|
||
const boxNumberInput = document.getElementById('boxNumber');
|
||
|
||
if (!boxNumberInput) {
|
||
console.error('❌ Modal elements not found');
|
||
showNotification('⚠️ Modal elements not properly loaded. Please refresh the page.', 'error');
|
||
return;
|
||
}
|
||
|
||
const boxNumber = boxNumberInput.value.trim();
|
||
const boxQty = 1; // Always 1 - one scanned item per scan record
|
||
|
||
console.log('Box Number:', boxNumber);
|
||
console.log('Box Quantity:', boxQty);
|
||
console.log('Current Scan ID:', currentScanId);
|
||
console.log('Current CP Code:', currentCpCode);
|
||
|
||
if (!boxNumber) {
|
||
console.warn('Box number is empty');
|
||
showNotification('⚠️ Please enter a box number', 'warning');
|
||
return;
|
||
}
|
||
|
||
if (!currentScanId) {
|
||
console.error('❌ No scan ID found');
|
||
showNotification('⚠️ Error: No scan ID stored. Please submit a scan first.', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
this.disabled = true;
|
||
this.textContent = '⏳ Assigning...';
|
||
|
||
console.log('Sending assignment request to backend...');
|
||
|
||
// Submit box assignment using scan ID
|
||
// The backend will retrieve the CP code from the scan record using the scan ID
|
||
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({
|
||
scan_id: currentScanId,
|
||
box_number: boxNumber,
|
||
quantity: parseInt(boxQty)
|
||
})
|
||
});
|
||
|
||
console.log('Response status:', response.status);
|
||
console.log('Response OK:', response.ok);
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text();
|
||
console.error('Error response body:', errorText);
|
||
throw new Error(`Server error: ${response.status} ${response.statusText}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
console.log('Response data:', data);
|
||
|
||
if (data.success) {
|
||
console.log('✅ Assignment successful');
|
||
|
||
// Get CP code from response if available, otherwise use stored value
|
||
const cpCodeForMessage = data.cp_code || currentCpCode || 'N/A';
|
||
|
||
showNotification(
|
||
`✅ CP ${cpCodeForMessage} assigned to box ${boxNumber}!`,
|
||
'success'
|
||
);
|
||
|
||
// Close modal
|
||
const modalElement = document.getElementById('boxAssignmentModal');
|
||
if (modalElement) {
|
||
modalElement.style.display = 'none';
|
||
}
|
||
|
||
// Clear box inputs with null checks
|
||
const boxNumInput = document.getElementById('boxNumber');
|
||
if (boxNumInput) {
|
||
boxNumInput.value = '';
|
||
}
|
||
|
||
// Clear stored scan ID
|
||
currentScanId = null;
|
||
currentCpCode = '';
|
||
|
||
// Reload page to show updated scans table after a brief delay
|
||
setTimeout(() => {
|
||
location.reload();
|
||
}, 1000);
|
||
|
||
} else {
|
||
console.error('Server returned success: false');
|
||
throw new Error(data.error || 'Unknown error from server');
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error in assignToBox:', error);
|
||
showNotification(`❌ Error: ${error.message}`, 'error');
|
||
} finally {
|
||
this.disabled = false;
|
||
this.textContent = 'Assign to Box'; // ← Fixed: was just 'Assign'
|
||
}
|
||
});
|
||
}
|
||
|
||
// Utility functions
|
||
function resetForm() {
|
||
document.getElementById('cp_code').value = '';
|
||
document.getElementById('oc1_code').value = '';
|
||
document.getElementById('oc2_code').value = '';
|
||
document.getElementById('defect_code').value = '';
|
||
currentCpCode = '';
|
||
document.getElementById('cp_code').focus();
|
||
}
|
||
|
||
function showNotification(message, type) {
|
||
const notification = document.createElement('div');
|
||
notification.className = `notification notification-${type}`;
|
||
notification.textContent = message;
|
||
// Apply inline styles to ensure proper positioning and appearance
|
||
notification.style.cssText = `
|
||
position: fixed;
|
||
top: 30px;
|
||
right: 30px;
|
||
padding: 12px 20px;
|
||
border-radius: 6px;
|
||
z-index: 9999;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.25);
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
white-space: nowrap;
|
||
opacity: 1;
|
||
animation: slideInRight 0.3s ease-out, slideOutRight 0.3s ease-in 2.7s forwards;
|
||
`;
|
||
document.body.appendChild(notification);
|
||
|
||
// Ensure animations exist
|
||
if (!document.getElementById('notification-styles')) {
|
||
const style = document.createElement('style');
|
||
style.id = 'notification-styles';
|
||
style.textContent = `
|
||
@keyframes slideInRight {
|
||
from {
|
||
transform: translateX(400px);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
@keyframes slideOutRight {
|
||
from {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
to {
|
||
transform: translateX(400px);
|
||
opacity: 0;
|
||
}
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
}
|
||
|
||
// Remove notification after animation completes
|
||
setTimeout(() => {
|
||
if (notification.parentNode) {
|
||
notification.parentNode.removeChild(notification);
|
||
}
|
||
}, 3000);
|
||
}
|
||
|
||
// Dark mode support
|
||
function applyDarkModeStyles() {
|
||
const isDarkMode = document.body.classList.contains('dark-mode');
|
||
if (isDarkMode) {
|
||
document.documentElement.style.setProperty('--bg-color', '#1e1e1e');
|
||
document.documentElement.style.setProperty('--text-color', '#e0e0e0');
|
||
}
|
||
}
|
||
|
||
// Check dark mode on page load
|
||
if (document.body.classList.contains('dark-mode')) {
|
||
applyDarkModeStyles();
|
||
}
|
||
|
||
// Listen for dark mode changes
|
||
const observer = new MutationObserver((mutations) => {
|
||
mutations.forEach((mutation) => {
|
||
if (mutation.attributeName === 'class') {
|
||
applyDarkModeStyles();
|
||
}
|
||
});
|
||
});
|
||
|
||
observer.observe(document.body, { attributes: true });
|
||
</script>
|
||
{% endblock %}
|