updated validation and auto submit

This commit is contained in:
2025-10-10 22:49:57 +03:00
parent e257f6be18
commit 05394697a0
7 changed files with 1055 additions and 1 deletions

View File

@@ -0,0 +1,41 @@
import mariadb
# Database connection credentials
# (reuse from create_scan_1db.py or update as needed)
db_config = {
"user": "trasabilitate",
"password": "Initial01!",
"host": "localhost",
"database": "trasabilitate_database"
}
try:
conn = mariadb.connect(**db_config)
cursor = conn.cursor()
print("Connected to the database successfully!")
# Create the scanfg_orders table (same structure as scan1_orders)
create_table_query = """
CREATE TABLE IF NOT EXISTS scanfg_orders (
Id INT AUTO_INCREMENT PRIMARY KEY,
operator_code VARCHAR(4) NOT NULL,
CP_full_code VARCHAR(15) NOT NULL UNIQUE,
OC1_code VARCHAR(4) NOT NULL,
OC2_code VARCHAR(4) NOT NULL,
CP_base_code VARCHAR(10) GENERATED ALWAYS AS (LEFT(CP_full_code, 10)) STORED,
quality_code INT(3) NOT NULL,
date DATE NOT NULL,
time TIME NOT NULL,
approved_quantity INT DEFAULT 0,
rejected_quantity INT DEFAULT 0
);
"""
cursor.execute(create_table_query)
print("Table 'scanfg_orders' created successfully!")
conn.commit()
cursor.close()
conn.close()
except mariadb.Error as e:
print(f"Error connecting to the database: {e}")

View File

@@ -0,0 +1,73 @@
import mariadb
# Database connection credentials
db_config = {
"user": "trasabilitate",
"password": "Initial01!",
"host": "localhost",
"database": "trasabilitate_database"
}
# Connect to the database
try:
conn = mariadb.connect(**db_config)
cursor = conn.cursor()
print("Connected to the database successfully!")
# Delete old triggers if they exist
try:
cursor.execute("DROP TRIGGER IF EXISTS increment_approved_quantity_fg;")
print("Old trigger 'increment_approved_quantity_fg' deleted successfully.")
except mariadb.Error as e:
print(f"Error deleting old trigger 'increment_approved_quantity_fg': {e}")
try:
cursor.execute("DROP TRIGGER IF EXISTS increment_rejected_quantity_fg;")
print("Old trigger 'increment_rejected_quantity_fg' deleted successfully.")
except mariadb.Error as e:
print(f"Error deleting old trigger 'increment_rejected_quantity_fg': {e}")
# Create corrected trigger for approved_quantity in scanfg_orders
create_approved_trigger_fg = """
CREATE TRIGGER increment_approved_quantity_fg
BEFORE INSERT ON scanfg_orders
FOR EACH ROW
BEGIN
IF NEW.quality_code = 000 THEN
SET NEW.approved_quantity = (
SELECT COUNT(*)
FROM scanfg_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code = 000
) + 1;
SET NEW.rejected_quantity = (
SELECT COUNT(*)
FROM scanfg_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code != 000
);
ELSE
SET NEW.approved_quantity = (
SELECT COUNT(*)
FROM scanfg_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code = 000
);
SET NEW.rejected_quantity = (
SELECT COUNT(*)
FROM scanfg_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code != 000
) + 1;
END IF;
END;
"""
cursor.execute(create_approved_trigger_fg)
print("Trigger 'increment_approved_quantity_fg' created successfully for scanfg_orders table!")
# Commit changes and close the connection
conn.commit()
cursor.close()
conn.close()
print("\n✅ All triggers for scanfg_orders table created successfully!")
print("The approved_quantity and rejected_quantity will now be calculated automatically.")
except mariadb.Error as e:
print(f"Error connecting to the database or creating triggers: {e}")

View File

@@ -299,6 +299,79 @@ def logout():
session.pop('role', None)
return redirect(url_for('main.login'))
# Finish Goods Scan Route
@bp.route('/fg_scan', methods=['GET', 'POST'])
def fg_scan():
if 'role' not in session or session['role'] not in ['superadmin', 'administrator', 'admin', 'scan']:
flash('Access denied: Scan users only.')
return redirect(url_for('main.dashboard'))
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
conn = get_db_connection()
cursor = conn.cursor()
# Check if the CP_full_code already exists in scanfg_orders
cursor.execute("SELECT Id FROM scanfg_orders WHERE CP_full_code = ?", (cp_code,))
existing_entry = cursor.fetchone()
if existing_entry:
# Update the existing entry
update_query = """
UPDATE scanfg_orders
SET operator_code = ?, OC1_code = ?, OC2_code = ?, quality_code = ?, date = ?, time = ?
WHERE CP_full_code = ?
"""
cursor.execute(update_query, (operator_code, oc1_code, oc2_code, defect_code, date, time, cp_code))
flash('Existing entry updated successfully.')
else:
# Insert a new entry
insert_query = """
INSERT INTO scanfg_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time)
VALUES (?, ?, ?, ?, ?, ?, ?)
"""
cursor.execute(insert_query, (operator_code, cp_code, oc1_code, oc2_code, defect_code, date, time))
flash('New entry inserted successfully.')
# Commit the transaction
conn.commit()
conn.close()
except mariadb.Error as e:
print(f"Error saving finish goods scan data: {e}")
flash(f"Error saving scan data: {e}")
# Fetch the latest scan data for display from scanfg_orders
scan_data = []
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT Id, operator_code, CP_base_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 for consistent date display
scan_data = [[format_cell_data(cell) for cell in row] for row in raw_scan_data]
conn.close()
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)
@bp.route('/create_user', methods=['POST'])
def create_user():
return create_user_handler()

View File

@@ -0,0 +1,473 @@
{% extends "base.html" %}
{% block title %}Finish Good Scan{% endblock %}
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/scan.css') }}">
<style>
.error-message {
color: #ff4444;
font-size: 0.9em;
margin-top: 5px;
display: none;
}
.error-message.show {
display: block;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
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');
const form = document.getElementById('fg-scan-form');
// Create error message element for operator code
const operatorErrorMessage = document.createElement('div');
operatorErrorMessage.className = 'error-message';
operatorErrorMessage.id = 'operator-error';
operatorErrorMessage.textContent = 'Please scan Quality Operator code (must start with OP)';
operatorCodeInput.parentNode.insertBefore(operatorErrorMessage, operatorCodeInput.nextSibling);
// Create error message element for CP code
const cpErrorMessage = document.createElement('div');
cpErrorMessage.className = 'error-message';
cpErrorMessage.id = 'cp-error';
cpErrorMessage.textContent = 'Please scan a valid CP';
cpCodeInput.parentNode.insertBefore(cpErrorMessage, cpCodeInput.nextSibling);
// Create error message element for OC1 code
const oc1ErrorMessage = document.createElement('div');
oc1ErrorMessage.className = 'error-message';
oc1ErrorMessage.id = 'oc1-error';
oc1ErrorMessage.textContent = 'Please scan a valid OC (must start with OC)';
oc1CodeInput.parentNode.insertBefore(oc1ErrorMessage, oc1CodeInput.nextSibling);
// Create error message element for OC2 code
const oc2ErrorMessage = document.createElement('div');
oc2ErrorMessage.className = 'error-message';
oc2ErrorMessage.id = 'oc2-error';
oc2ErrorMessage.textContent = 'Please scan a valid OC (must start with OC)';
oc2CodeInput.parentNode.insertBefore(oc2ErrorMessage, oc2CodeInput.nextSibling);
// Create error message element for defect code
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);
// Validate operator code on input
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('');
}
});
// Prevent leaving operator code field if invalid
operatorCodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
// Return focus to this field
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
// Prevent Tab/Enter from moving to next field if operator code is invalid
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();
}
});
// Validate CP code on input
cpCodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value; // Convert to uppercase
if (value.length >= 2 && !value.startsWith('CP')) {
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
} else {
cpErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// 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();
}
});
// Prevent focusing on OC1 code if CP code is invalid
oc1CodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
});
// Validate OC1 code on input
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('');
}
});
// Prevent leaving OC1 code field if invalid
oc1CodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
// Return focus to this field
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
// Prevent Tab/Enter from moving to next field if OC1 code is invalid
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();
}
});
// Prevent focusing on OC2 code if CP code is invalid
oc2CodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
// Also check if OC1 is invalid
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.select();
}
});
// Validate OC2 code on input
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('');
}
});
// Prevent leaving OC2 code field if invalid
oc2CodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
// Return focus to this field
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
// Prevent Tab/Enter from moving to next field if OC2 code is invalid
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();
}
});
// Prevent focusing on defect code if CP code is invalid
defectCodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
// Also check if OC1 is invalid
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.select();
}
// Also check if OC2 is invalid
if (oc2CodeInput.value.length > 0 && !oc2CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
oc2CodeInput.focus();
oc2CodeInput.select();
}
});
// Validate defect code on input - only allow digits
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')) {
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.setCustomValidity('Must start with CP');
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;
}
// Update time field before submitting
const timeInput = document.getElementById('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');
timeInput.value = `${hours}:${minutes}:${seconds}`;
// Submit the form
form.submit();
}
});
// Validate form on submit
form.addEventListener('submit', function(e) {
let hasError = false;
if (!operatorCodeInput.value.startsWith('OP')) {
e.preventDefault();
operatorErrorMessage.classList.add('show');
operatorCodeInput.setCustomValidity('Must start with OP');
if (!hasError) {
operatorCodeInput.focus();
hasError = true;
}
}
if (!cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.setCustomValidity('Must start with CP');
if (!hasError) {
cpCodeInput.focus();
hasError = true;
}
}
if (!oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.setCustomValidity('Must start with OC');
if (!hasError) {
oc1CodeInput.focus();
hasError = true;
}
}
if (!oc2CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
oc2CodeInput.setCustomValidity('Must start with OC');
if (!hasError) {
oc2CodeInput.focus();
hasError = true;
}
}
// Validate defect code is a 3-digit number
const isValidDefectCode = /^\d{3}$/.test(defectCodeInput.value);
if (!isValidDefectCode) {
e.preventDefault();
defectErrorMessage.classList.add('show');
defectCodeInput.setCustomValidity('Must be a 3-digit number');
if (!hasError) {
defectCodeInput.focus();
hasError = true;
}
}
});
});
</script>
{% endblock %}
{% block content %}
<div class="scan-container">
<!-- Input Form Card -->
<div class="card scan-form-card">
<h3>Scan Input</h3>
<form method="POST" class="form-centered" id="fg-scan-form">
<label for="operator_code">Quality Operator Code:</label>
<input type="text" id="operator_code" name="operator_code" maxlength="4" required>
<label for="cp_code">CP Code:</label>
<input type="text" id="cp_code" name="cp_code" maxlength="15" required>
<label for="oc1_code">OC1 Code:</label>
<input type="text" id="oc1_code" name="oc1_code" maxlength="4" required>
<label for="oc2_code">OC2 Code:</label>
<input type="text" id="oc2_code" name="oc2_code" maxlength="4" required>
<label for="defect_code">Defect Code:</label>
<input type="text" id="defect_code" name="defect_code" maxlength="4" required>
<label for="date">Date:</label>
<input type="text" id="date" name="date" value="{{ now().strftime('%Y-%m-%d') }}" placeholder="yyyy-mm-dd" pattern="\d{4}-\d{2}-\d{2}" required>
<label for="time">Time:</label>
<input type="text" id="time" name="time" value="{{ now().strftime('%H:%M:%S') }}" readonly>
<button type="submit" class="btn">Submit</button>
</form>
</div>
<!-- Latest Scans Card -->
<div class="card scan-table-card">
<h3>Latest Scans</h3>
<table class="scan-table">
<thead>
<tr>
<th>ID</th>
<th>Op Code</th>
<th>CP Code</th>
<th>OC1 Code</th>
<th>OC2 Code</th>
<th>Defect Code</th>
<th>Date</th>
<th>Time</th>
<th>Apr. Quantity</th>
<th>Rejec. Quantity</th>
</tr>
</thead>
<tbody>
{% for row in scan_data %}
<tr>
<td>{{ row[0] }}</td> <!-- Id -->
<td>{{ row[1] }}</td> <!-- operator_code -->
<td>{{ row[2] }}</td> <!-- CP_full_code -->
<td>{{ row[3] }}</td> <!-- OC1_code -->
<td>{{ row[4] }}</td> <!-- OC2_code -->
<td>{{ row[5] }}</td> <!-- quality_code -->
<td>{{ row[6] }}</td> <!-- date -->
<td>{{ row[7] }}</td> <!-- time -->
<td>{{ row[8] }}</td> <!-- approved quantity -->
<td>{{ row[9] }}</td> <!-- rejected quantity -->
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

View File

@@ -12,7 +12,7 @@
<div class="dashboard-card">
<h3>Finish goods scanning</h3>
<p>Access the scanning module for production orders.</p>
<a href="{{ url_for('main.scan') }}" class="btn">Finish goods scanning</a>
<a href="{{ url_for('main.fg_scan') }}" class="btn">Finish goods scan</a>
</div>
<!-- Card 2: View Scan History -->
<div class="dashboard-card">

View File

@@ -2,6 +2,400 @@
{% block title %}Scan Module{% endblock %}
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/scan.css') }}">
<style>
.error-message {
color: #ff4444;
font-size: 0.9em;
margin-top: 5px;
display: none;
}
.error-message.show {
display: block;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
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');
const form = document.querySelector('.form-centered');
// Create error message element for operator code
const operatorErrorMessage = document.createElement('div');
operatorErrorMessage.className = 'error-message';
operatorErrorMessage.id = 'operator-error';
operatorErrorMessage.textContent = 'Please scan Quality Operator code (must start with OP)';
operatorCodeInput.parentNode.insertBefore(operatorErrorMessage, operatorCodeInput.nextSibling);
// Create error message element for CP code
const cpErrorMessage = document.createElement('div');
cpErrorMessage.className = 'error-message';
cpErrorMessage.id = 'cp-error';
cpErrorMessage.textContent = 'Please scan a valid CP';
cpCodeInput.parentNode.insertBefore(cpErrorMessage, cpCodeInput.nextSibling);
// Create error message element for OC1 code
const oc1ErrorMessage = document.createElement('div');
oc1ErrorMessage.className = 'error-message';
oc1ErrorMessage.id = 'oc1-error';
oc1ErrorMessage.textContent = 'Please scan a valid OC (must start with OC)';
oc1CodeInput.parentNode.insertBefore(oc1ErrorMessage, oc1CodeInput.nextSibling);
// Create error message element for OC2 code
const oc2ErrorMessage = document.createElement('div');
oc2ErrorMessage.className = 'error-message';
oc2ErrorMessage.id = 'oc2-error';
oc2ErrorMessage.textContent = 'Please scan a valid OC (must start with OC)';
oc2CodeInput.parentNode.insertBefore(oc2ErrorMessage, oc2CodeInput.nextSibling);
// Create error message element for defect code
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);
// Validate operator code on input
operatorCodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value;
if (value.length >= 2 && !value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
} else {
operatorErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// Prevent leaving operator code field if invalid
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);
}
});
// Prevent Tab/Enter from moving to next field if operator code is invalid
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();
}
});
// Validate CP code on input
cpCodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value;
if (value.length >= 2 && !value.startsWith('CP')) {
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
} else {
cpErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// 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');
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();
}
});
// Prevent focusing on OC1 code if CP code is invalid
oc1CodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
});
// Validate OC1 code on input
oc1CodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value;
if (value.length >= 2 && !value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
} else {
oc1ErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// Prevent leaving OC1 code field if invalid
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);
}
});
// Prevent Tab/Enter from moving to next field if OC1 code is invalid
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();
}
});
// Prevent focusing on OC2 code if CP code is invalid
oc2CodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
// Also check if OC1 is invalid
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.select();
}
});
// Validate OC2 code on input
oc2CodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value;
if (value.length >= 2 && !value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
} else {
oc2ErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// Prevent leaving OC2 code field if invalid
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);
}
});
// Prevent Tab/Enter from moving to next field if OC2 code is invalid
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();
}
});
// Prevent focusing on defect code if CP code is invalid
defectCodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
// Also check if OC1 is invalid
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.select();
}
// Also check if OC2 is invalid
if (oc2CodeInput.value.length > 0 && !oc2CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
oc2CodeInput.focus();
oc2CodeInput.select();
}
});
// Validate defect code on input - only allow digits
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')) {
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.setCustomValidity('Must start with CP');
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;
}
// Update time field before submitting
const timeInput = document.getElementById('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');
timeInput.value = `${hours}:${minutes}:${seconds}`;
// Submit the form
form.submit();
}
});
// Validate form on submit
form.addEventListener('submit', function(e) {
let hasError = false;
if (!operatorCodeInput.value.startsWith('OP')) {
e.preventDefault();
operatorErrorMessage.classList.add('show');
operatorCodeInput.setCustomValidity('Must start with OP');
if (!hasError) {
operatorCodeInput.focus();
hasError = true;
}
}
if (!cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.setCustomValidity('Must start with CP');
if (!hasError) {
cpCodeInput.focus();
hasError = true;
}
}
if (!oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.setCustomValidity('Must start with OC');
if (!hasError) {
oc1CodeInput.focus();
hasError = true;
}
}
if (!oc2CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
oc2CodeInput.setCustomValidity('Must start with OC');
if (!hasError) {
oc2CodeInput.focus();
hasError = true;
}
}
// Validate defect code is a 3-digit number
const isValidDefectCode = /^\d{3}$/.test(defectCodeInput.value);
if (!isValidDefectCode) {
e.preventDefault();
defectErrorMessage.classList.add('show');
defectCodeInput.setCustomValidity('Must be a 3-digit number');
if (!hasError) {
defectCodeInput.focus();
hasError = true;
}
}
});
});
</script>
{% endblock %}
{% block content %}
<div class="scan-container">