Files
quality_app-v2/app/templates/modules/quality/fg_scan.html
Quality App Developer 59e82c0209 Fix: Resolve 'Column date cannot be null' error in FG scan
- Added default date/time handling in save_fg_scan() function
- Backend now uses current date/time if values not provided
- Added hidden date/time form fields in frontend
- Updated JavaScript to populate hidden fields before submission
- Prevents null database errors when scanning orders
2026-01-27 17:52:57 +02:00

926 lines
33 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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>
<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>
<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">
<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">
<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">
<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>
<div id="quickBoxSection" style="display: none;" class="quick-box-section">
<button type="button" class="btn-secondary" id="quickBoxLabel">Quick Box Label Creation</button>
</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">&times;</button>
</div>
<div class="modal-body">
<label for="boxNumber">Box Number:</label>
<input type="text" id="boxNumber" placeholder="Enter box number">
<label for="boxQty">Quantity:</label>
<input type="number" id="boxQty" placeholder="Enter quantity" min="1">
</div>
<div class="modal-footer">
<button type="button" class="btn-secondary" id="cancelModal">Cancel</button>
<button type="button" class="btn-submit" id="assignToBox">Assign</button>
</div>
</div>
</div>
<script>
// Global variables
let scanToBoxesEnabled = false;
let currentCpCode = '';
let qzTrayReady = false;
let cpCodeLastInputTime = null;
// 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');
// 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
const successIndicator = document.createElement('div');
successIndicator.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #4CAF50;
color: white;
padding: 10px 20px;
border-radius: 5px;
z-index: 1000;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
font-weight: bold;
`;
successIndicator.textContent = '✅ Scan recorded! Ready for next scan';
document.body.appendChild(successIndicator);
// 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
const operatorErrorMessage = document.createElement('div');
operatorErrorMessage.className = 'error-message';
operatorErrorMessage.id = 'operator-error';
operatorErrorMessage.textContent = 'Operator code must start with OP and be 4 characters';
operatorCodeInput.parentNode.insertBefore(operatorErrorMessage, operatorCodeInput.nextSibling);
const cpErrorMessage = document.createElement('div');
cpErrorMessage.className = 'error-message';
cpErrorMessage.id = 'cp-error';
cpErrorMessage.textContent = 'CP code must start with CP and be 15 characters';
cpCodeInput.parentNode.insertBefore(cpErrorMessage, cpCodeInput.nextSibling);
const oc1ErrorMessage = document.createElement('div');
oc1ErrorMessage.className = 'error-message';
oc1ErrorMessage.id = 'oc1-error';
oc1ErrorMessage.textContent = 'OC1 code must start with OC and be 4 characters';
oc1CodeInput.parentNode.insertBefore(oc1ErrorMessage, oc1CodeInput.nextSibling);
const oc2ErrorMessage = document.createElement('div');
oc2ErrorMessage.className = 'error-message';
oc2ErrorMessage.id = 'oc2-error';
oc2ErrorMessage.textContent = 'OC2 code must start with OC and be 4 characters';
oc2CodeInput.parentNode.insertBefore(oc2ErrorMessage, oc2CodeInput.nextSibling);
const 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);
// ===== 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)');
}
}
}
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 prefix
if (currentValue.length >= 2 && !currentValue.startsWith('CP')) {
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
} else {
cpErrorMessage.classList.remove('show');
this.setCustomValidity('');
// Auto-advance when field is complete and valid
if (currentValue.length === 15 && currentValue.startsWith('CP')) {
setTimeout(() => {
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
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)
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
cpCodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('CP')) {
cpErrorMessage.classList.add('show');
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
cpCodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
this.select();
}
});
// Prevent focusing on CP code if operator code is invalid
cpCodeInput.addEventListener('focus', function(e) {
if (operatorCodeInput.value.length > 0 && !operatorCodeInput.value.startsWith('OP')) {
e.preventDefault();
operatorErrorMessage.classList.add('show');
operatorCodeInput.focus();
operatorCodeInput.select();
}
});
// ===== OPERATOR CODE VALIDATION =====
operatorCodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value; // Convert to uppercase
if (value.length >= 2 && !value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
} else {
operatorErrorMessage.classList.remove('show');
this.setCustomValidity('');
// Auto-advance when field is complete and valid
if (value.length === 4 && value.startsWith('OP')) {
setTimeout(() => {
cpCodeInput.focus();
console.log('✅ Auto-advanced from Operator Code to CP Code');
}, 50);
}
}
});
operatorCodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
operatorCodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OP')) {
e.preventDefault();
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
this.select();
}
});
// ===== OC1 CODE VALIDATION =====
oc1CodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value; // Convert to uppercase
if (value.length >= 2 && !value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
} else {
oc1ErrorMessage.classList.remove('show');
this.setCustomValidity('');
// Auto-advance when field is complete and valid
if (value.length === 4 && value.startsWith('OC')) {
setTimeout(() => {
oc2CodeInput.focus();
console.log('✅ Auto-advanced from OC1 Code to OC2 Code');
}, 50);
}
}
});
oc1CodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
oc1CodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
this.select();
}
});
oc1CodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
});
// ===== OC2 CODE VALIDATION =====
oc2CodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value; // Convert to uppercase
if (value.length >= 2 && !value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
} else {
oc2ErrorMessage.classList.remove('show');
this.setCustomValidity('');
// Auto-advance when field is complete and valid
if (value.length === 4 && value.startsWith('OC')) {
setTimeout(() => {
defectCodeInput.focus();
console.log('✅ Auto-advanced from OC2 Code to Defect Code');
}, 50);
}
}
});
oc2CodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
oc2CodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
this.select();
}
});
oc2CodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.select();
}
});
// ===== DEFECT CODE VALIDATION =====
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) {
defectErrorMessage.classList.add('show');
this.setCustomValidity('Must be a 3-digit number');
} else {
defectErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
} else {
defectErrorMessage.classList.remove('show');
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.value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
operatorCodeInput.focus();
operatorCodeInput.setCustomValidity('Must start with OP');
return;
}
// Validate CP code before submitting
if (!cpCodeInput.value.startsWith('CP') || cpCodeInput.value.length !== 15) {
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.setCustomValidity('Must start with CP and be complete');
return;
}
// Validate OC1 code before submitting
if (!oc1CodeInput.value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.setCustomValidity('Must start with OC');
return;
}
// Validate OC2 code before submitting
if (!oc2CodeInput.value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
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) {
defectErrorMessage.classList.add('show');
this.focus();
this.setCustomValidity('Must be a 3-digit number');
return;
}
// Clear all custom validity states before submitting
operatorCodeInput.setCustomValidity('');
cpCodeInput.setCustomValidity('');
oc1CodeInput.setCustomValidity('');
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
console.log('Auto-submitting form on 3-digit defect code');
document.getElementById('scanForm').submit();
}
});
defectCodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.select();
}
if (oc2CodeInput.value.length > 0 && !oc2CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
oc2CodeInput.focus();
oc2CodeInput.select();
}
});
// ===== CLEAR OPERATOR CODE BUTTON =====
document.getElementById('clearOperator').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 =====
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');
resetForm();
document.getElementById('boxAssignmentModal').style.display = 'flex';
} else {
showNotification('Error saving scan', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('Error saving scan', 'error');
});
} else {
// Regular form submission
this.submit();
}
});
// Scan to boxes functionality
document.getElementById('scanToBoxes').addEventListener('change', function() {
scanToBoxesEnabled = 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();
}
});
// Quick box label creation
document.getElementById('quickBoxLabel').addEventListener('click', function() {
if (!qzTrayReady) {
alert('QZ Tray is not connected. Please ensure QZ Tray is running.');
return;
}
const cpCode = document.getElementById('cp_code').value.trim();
if (!cpCode) {
alert('Please enter a CP code first');
return;
}
// Create label configuration for QZ Tray
const label = {
type: 'label',
cpCode: cpCode,
createdAt: new Date().toISOString()
};
// Send to printer via QZ Tray
qz.print({
type: 'label',
format: cpCode
}).catch(function(err) {
console.error('Print error:', err);
alert('Error printing label');
});
});
// Modal functionality
document.getElementById('closeModal').addEventListener('click', function() {
document.getElementById('boxAssignmentModal').style.display = 'none';
});
document.getElementById('cancelModal').addEventListener('click', function() {
document.getElementById('boxAssignmentModal').style.display = 'none';
});
document.getElementById('assignToBox').addEventListener('click', function() {
const boxNumber = document.getElementById('boxNumber').value.trim();
const boxQty = document.getElementById('boxQty').value.trim();
if (!boxNumber) {
alert('Please enter a box number');
return;
}
if (!boxQty || isNaN(boxQty) || parseInt(boxQty) < 1) {
alert('Please enter a valid quantity');
return;
}
// Submit box assignment
const data = {
box_number: boxNumber,
quantity: boxQty,
cp_code: currentCpCode
};
fetch('{{ url_for("quality.fg_scan") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification('Box assigned successfully!', 'success');
document.getElementById('boxAssignmentModal').style.display = 'none';
document.getElementById('boxNumber').value = '';
document.getElementById('boxQty').value = '';
}
})
.catch(error => console.error('Error:', error));
});
// 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;
notification.style.opacity = '1';
document.body.appendChild(notification);
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => notification.remove(), 300);
}, 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 %}