feat: Major system improvements and production deployment
✨ New Features: - Added view_orders route with proper table display - Implemented CSV upload with preview workflow and date parsing - Added production WSGI server configuration with Gunicorn - Created comprehensive production management scripts 🔧 Bug Fixes: - Fixed upload_data route column mapping for actual CSV structure - Resolved print module database queries and template rendering - Fixed view orders navigation routing (was pointing to JSON API) - Corrected barcode display width constraints in print module - Added proper date format parsing for MySQL compatibility 🎨 UI/UX Improvements: - Updated view_orders template with theme-compliant styling - Hidden barcode text in print module preview for cleaner display - Enhanced CSV upload with two-step preview-then-save workflow - Improved error handling and debugging throughout upload process 🚀 Production Infrastructure: - Added Gunicorn WSGI server with proper configuration - Created systemd service for production deployment - Implemented production management scripts (start/stop/status) - Added comprehensive logging setup - Updated requirements.txt with production dependencies 📊 Database & Data: - Enhanced order_for_labels table compatibility - Fixed column mappings for real CSV data structure - Added proper date parsing and validation - Improved error handling with detailed debugging 🔧 Technical Debt: - Reorganized database setup documentation - Added proper error handling throughout upload workflow - Enhanced debugging capabilities for troubleshooting - Improved code organization and documentation
This commit is contained in:
@@ -242,27 +242,42 @@ def scan():
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check if the CP_full_code already exists
|
||||
cursor.execute("SELECT Id FROM scan1_orders WHERE CP_full_code = ?", (cp_code,))
|
||||
existing_entry = cursor.fetchone()
|
||||
|
||||
if existing_entry:
|
||||
# Update the existing entry
|
||||
update_query = """
|
||||
UPDATE scan1_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.')
|
||||
# Always insert a new entry - each scan is a separate record
|
||||
insert_query = """
|
||||
INSERT INTO scan1_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
cursor.execute(insert_query, (operator_code, cp_code, oc1_code, oc2_code, defect_code, date, time))
|
||||
|
||||
# Get the CP_base_code (first 10 characters of CP_full_code)
|
||||
cp_base_code = cp_code[:10]
|
||||
|
||||
# Count approved quantities (quality_code = 0) for this CP_base_code
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM scan1_orders
|
||||
WHERE CP_base_code = %s AND quality_code = 0
|
||||
""", (cp_base_code,))
|
||||
approved_count = cursor.fetchone()[0]
|
||||
|
||||
# Count rejected quantities (quality_code != 0) for this CP_base_code
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM scan1_orders
|
||||
WHERE CP_base_code = %s AND quality_code != 0
|
||||
""", (cp_base_code,))
|
||||
rejected_count = cursor.fetchone()[0]
|
||||
|
||||
# Update all records with the same CP_base_code with new quantities
|
||||
cursor.execute("""
|
||||
UPDATE scan1_orders
|
||||
SET approved_quantity = %s, rejected_quantity = %s
|
||||
WHERE CP_base_code = %s
|
||||
""", (approved_count, rejected_count, cp_base_code))
|
||||
|
||||
# Flash appropriate message
|
||||
if int(defect_code) == 0:
|
||||
flash(f'✅ APPROVED scan recorded for {cp_code}. Total approved: {approved_count}')
|
||||
else:
|
||||
# Insert a new entry
|
||||
insert_query = """
|
||||
INSERT INTO scan1_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.')
|
||||
flash(f'❌ REJECTED scan recorded for {cp_code} (defect: {defect_code}). Total rejected: {rejected_count}')
|
||||
|
||||
# Commit the transaction
|
||||
conn.commit()
|
||||
@@ -278,7 +293,7 @@ def scan():
|
||||
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
|
||||
SELECT Id, operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity
|
||||
FROM scan1_orders
|
||||
ORDER BY Id DESC
|
||||
LIMIT 15
|
||||
@@ -321,27 +336,42 @@ def fg_scan():
|
||||
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.')
|
||||
# Always insert a new entry - each scan is a separate record
|
||||
insert_query = """
|
||||
INSERT INTO scanfg_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
cursor.execute(insert_query, (operator_code, cp_code, oc1_code, oc2_code, defect_code, date, time))
|
||||
|
||||
# Get the CP_base_code (first 10 characters of CP_full_code)
|
||||
cp_base_code = cp_code[:10]
|
||||
|
||||
# Count approved quantities (quality_code = 0) for this CP_base_code
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM scanfg_orders
|
||||
WHERE CP_base_code = %s AND quality_code = 0
|
||||
""", (cp_base_code,))
|
||||
approved_count = cursor.fetchone()[0]
|
||||
|
||||
# Count rejected quantities (quality_code != 0) for this CP_base_code
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM scanfg_orders
|
||||
WHERE CP_base_code = %s AND quality_code != 0
|
||||
""", (cp_base_code,))
|
||||
rejected_count = cursor.fetchone()[0]
|
||||
|
||||
# Update all records with the same CP_base_code with new quantities
|
||||
cursor.execute("""
|
||||
UPDATE scanfg_orders
|
||||
SET approved_quantity = %s, rejected_quantity = %s
|
||||
WHERE CP_base_code = %s
|
||||
""", (approved_count, rejected_count, cp_base_code))
|
||||
|
||||
# Flash appropriate message
|
||||
if int(defect_code) == 0:
|
||||
flash(f'✅ APPROVED scan recorded for {cp_code}. Total approved: {approved_count}')
|
||||
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.')
|
||||
flash(f'❌ REJECTED scan recorded for {cp_code} (defect: {defect_code}). Total rejected: {rejected_count}')
|
||||
|
||||
# Commit the transaction
|
||||
conn.commit()
|
||||
@@ -357,7 +387,7 @@ def fg_scan():
|
||||
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
|
||||
SELECT Id, operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity
|
||||
FROM scanfg_orders
|
||||
ORDER BY Id DESC
|
||||
LIMIT 15
|
||||
@@ -1036,13 +1066,273 @@ def etichete():
|
||||
return redirect(url_for('main.dashboard'))
|
||||
return render_template('main_page_etichete.html')
|
||||
|
||||
@bp.route('/upload_data')
|
||||
@bp.route('/upload_data', methods=['GET', 'POST'])
|
||||
def upload_data():
|
||||
if request.method == 'POST':
|
||||
action = request.form.get('action', 'preview')
|
||||
|
||||
if action == 'preview':
|
||||
# Handle file upload and show preview
|
||||
if 'file' not in request.files:
|
||||
flash('No file selected', 'error')
|
||||
return redirect(request.url)
|
||||
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
flash('No file selected', 'error')
|
||||
return redirect(request.url)
|
||||
|
||||
if file and file.filename.lower().endswith('.csv'):
|
||||
try:
|
||||
# Read CSV file
|
||||
import csv
|
||||
import io
|
||||
|
||||
# Read the file content
|
||||
stream = io.StringIO(file.stream.read().decode("UTF8"), newline=None)
|
||||
csv_input = csv.DictReader(stream)
|
||||
|
||||
# Convert to list for preview
|
||||
preview_data = []
|
||||
headers = []
|
||||
|
||||
for i, row in enumerate(csv_input):
|
||||
if i == 0:
|
||||
headers = list(row.keys())
|
||||
if i < 10: # Show only first 10 rows for preview
|
||||
preview_data.append(row)
|
||||
else:
|
||||
break
|
||||
|
||||
# Store the full file content in session for later processing
|
||||
file.stream.seek(0) # Reset file pointer
|
||||
session['csv_content'] = file.stream.read().decode("UTF8")
|
||||
session['csv_filename'] = file.filename
|
||||
|
||||
return render_template('upload_orders.html',
|
||||
preview_data=preview_data,
|
||||
headers=headers,
|
||||
show_preview=True,
|
||||
filename=file.filename)
|
||||
|
||||
except Exception as e:
|
||||
flash(f'Error reading CSV file: {str(e)}', 'error')
|
||||
return redirect(request.url)
|
||||
else:
|
||||
flash('Please upload a CSV file', 'error')
|
||||
return redirect(request.url)
|
||||
|
||||
elif action == 'save':
|
||||
# Save the data to database
|
||||
if 'csv_content' not in session:
|
||||
flash('No data to save. Please upload a file first.', 'error')
|
||||
return redirect(request.url)
|
||||
|
||||
try:
|
||||
import csv
|
||||
import io
|
||||
|
||||
print(f"DEBUG: Starting CSV upload processing...")
|
||||
|
||||
# Read the CSV content from session
|
||||
stream = io.StringIO(session['csv_content'], newline=None)
|
||||
csv_input = csv.DictReader(stream)
|
||||
|
||||
# Connect to database
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
inserted_count = 0
|
||||
error_count = 0
|
||||
errors = []
|
||||
|
||||
print(f"DEBUG: Connected to database, processing rows...")
|
||||
|
||||
# Process each row
|
||||
for index, row in enumerate(csv_input):
|
||||
try:
|
||||
print(f"DEBUG: Processing row {index + 1}: {row}")
|
||||
|
||||
# Extract data from CSV row with proper column mapping
|
||||
comanda_productie = str(row.get('comanda_productie', row.get('Comanda Productie', row.get('Order Number', '')))).strip()
|
||||
cod_articol = str(row.get('cod_articol', row.get('Cod Articol', row.get('Article Code', '')))).strip()
|
||||
descr_com_prod = str(row.get('descr_com_prod', row.get('Descr. Com. Prod', row.get('Descr Com Prod', row.get('Description', ''))))).strip()
|
||||
cantitate = int(float(row.get('cantitate', row.get('Cantitate', row.get('Quantity', 0)))))
|
||||
com_achiz_client = str(row.get('com_achiz_client', row.get('Com.Achiz.Client', row.get('Com Achiz Client', '')))).strip()
|
||||
nr_linie_com_client = row.get('nr_linie_com_client', row.get('Nr. Linie com. Client', row.get('Nr Linie Com Client', '')))
|
||||
customer_name = str(row.get('customer_name', row.get('Customer Name', ''))).strip()
|
||||
customer_article_number = str(row.get('customer_article_number', row.get('Customer Article Number', ''))).strip()
|
||||
open_for_order = str(row.get('open_for_order', row.get('Open for order', row.get('Open For Order', '')))).strip()
|
||||
line_number = row.get('line_number', row.get('Line ', row.get('Line Number', '')))
|
||||
data_livrare = str(row.get('data_livrare', row.get('DataLivrare', row.get('Data Livrare', '')))).strip()
|
||||
dimensiune = str(row.get('dimensiune', row.get('Dimensiune', ''))).strip()
|
||||
|
||||
print(f"DEBUG: Extracted data - comanda_productie: {comanda_productie}, descr_com_prod: {descr_com_prod}, cantitate: {cantitate}")
|
||||
|
||||
# Convert empty strings to None for integer fields
|
||||
nr_linie_com_client = int(nr_linie_com_client) if nr_linie_com_client and str(nr_linie_com_client).strip() else None
|
||||
line_number = int(line_number) if line_number and str(line_number).strip() else None
|
||||
|
||||
# Convert empty string to None for date field
|
||||
if data_livrare:
|
||||
try:
|
||||
# Parse date from various formats (9/23/2023, 23/9/2023, 2023-09-23, etc.)
|
||||
from datetime import datetime
|
||||
# Try different date formats
|
||||
date_formats = ['%m/%d/%Y', '%d/%m/%Y', '%Y-%m-%d', '%m-%d-%Y', '%d-%m-%Y']
|
||||
parsed_date = None
|
||||
for fmt in date_formats:
|
||||
try:
|
||||
parsed_date = datetime.strptime(data_livrare, fmt)
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if parsed_date:
|
||||
data_livrare = parsed_date.strftime('%Y-%m-%d') # MySQL date format
|
||||
print(f"DEBUG: Parsed date: {data_livrare}")
|
||||
else:
|
||||
print(f"DEBUG: Could not parse date: {data_livrare}, setting to None")
|
||||
data_livrare = None
|
||||
except Exception as date_error:
|
||||
print(f"DEBUG: Date parsing error: {date_error}")
|
||||
data_livrare = None
|
||||
else:
|
||||
data_livrare = None
|
||||
|
||||
dimensiune = dimensiune if dimensiune else None
|
||||
|
||||
print(f"DEBUG: Final data before insert - nr_linie: {nr_linie_com_client}, line_number: {line_number}, data_livrare: {data_livrare}")
|
||||
|
||||
if comanda_productie and descr_com_prod and cantitate > 0:
|
||||
# Insert into order_for_labels table with correct columns
|
||||
print(f"DEBUG: Inserting order: {comanda_productie}")
|
||||
try:
|
||||
cursor.execute("""
|
||||
INSERT INTO order_for_labels (
|
||||
comanda_productie, cod_articol, descr_com_prod, cantitate,
|
||||
com_achiz_client, nr_linie_com_client, customer_name,
|
||||
customer_article_number, open_for_order, line_number,
|
||||
data_livrare, dimensiune, printed_labels
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 0)
|
||||
""", (comanda_productie, cod_articol, descr_com_prod, cantitate,
|
||||
com_achiz_client, nr_linie_com_client, customer_name,
|
||||
customer_article_number, open_for_order, line_number,
|
||||
data_livrare, dimensiune))
|
||||
inserted_count += 1
|
||||
print(f"DEBUG: Successfully inserted order: {comanda_productie}")
|
||||
except Exception as insert_error:
|
||||
print(f"DEBUG: Database insert error for {comanda_productie}: {insert_error}")
|
||||
errors.append(f"Row {index + 1}: Database error - {str(insert_error)}")
|
||||
error_count += 1
|
||||
else:
|
||||
missing_fields = []
|
||||
if not comanda_productie:
|
||||
missing_fields.append("comanda_productie")
|
||||
if not descr_com_prod:
|
||||
missing_fields.append("descr_com_prod")
|
||||
if cantitate <= 0:
|
||||
missing_fields.append("cantitate (must be > 0)")
|
||||
errors.append(f"Row {index + 1}: Missing required fields: {', '.join(missing_fields)}")
|
||||
error_count += 1
|
||||
except ValueError as e:
|
||||
errors.append(f"Row {index + 1}: Invalid quantity value")
|
||||
error_count += 1
|
||||
except Exception as e:
|
||||
errors.append(f"Row {index + 1}: {str(e)}")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
# Commit the transaction
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print(f"DEBUG: Committed {inserted_count} records to database")
|
||||
|
||||
# Clear session data
|
||||
session.pop('csv_content', None)
|
||||
session.pop('csv_filename', None)
|
||||
|
||||
# Show results
|
||||
if error_count > 0:
|
||||
flash(f'Upload completed: {inserted_count} orders saved, {error_count} errors', 'warning')
|
||||
for error in errors[:5]: # Show only first 5 errors
|
||||
flash(error, 'error')
|
||||
if len(errors) > 5:
|
||||
flash(f'... and {len(errors) - 5} more errors', 'error')
|
||||
else:
|
||||
flash(f'Successfully uploaded {inserted_count} orders for labels', 'success')
|
||||
|
||||
except Exception as e:
|
||||
flash(f'Error processing data: {str(e)}', 'error')
|
||||
|
||||
return redirect(url_for('main.upload_data'))
|
||||
|
||||
# GET request - show the upload form
|
||||
return render_template('upload_orders.html')
|
||||
|
||||
@bp.route('/upload_orders')
|
||||
def upload_orders():
|
||||
"""Redirect to upload_data for compatibility"""
|
||||
return redirect(url_for('main.upload_data'))
|
||||
|
||||
@bp.route('/print_module')
|
||||
def print_module():
|
||||
return render_template('print_module.html')
|
||||
try:
|
||||
# Get unprinted orders data
|
||||
orders_data = get_unprinted_orders_data(limit=100)
|
||||
return render_template('print_module.html', orders=orders_data)
|
||||
except Exception as e:
|
||||
print(f"Error loading print module data: {e}")
|
||||
flash(f"Error loading orders: {e}", 'error')
|
||||
return render_template('print_module.html', orders=[])
|
||||
|
||||
@bp.route('/view_orders')
|
||||
def view_orders():
|
||||
"""View all orders in a table format"""
|
||||
try:
|
||||
# Get all orders data (not just unprinted)
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT id, comanda_productie, cod_articol, descr_com_prod, cantitate,
|
||||
com_achiz_client, nr_linie_com_client, customer_name,
|
||||
customer_article_number, open_for_order, line_number,
|
||||
created_at, updated_at, printed_labels, data_livrare, dimensiune
|
||||
FROM order_for_labels
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 500
|
||||
""")
|
||||
|
||||
orders_data = []
|
||||
for row in cursor.fetchall():
|
||||
orders_data.append({
|
||||
'id': row[0],
|
||||
'comanda_productie': row[1],
|
||||
'cod_articol': row[2],
|
||||
'descr_com_prod': row[3],
|
||||
'cantitate': row[4],
|
||||
'com_achiz_client': row[5],
|
||||
'nr_linie_com_client': row[6],
|
||||
'customer_name': row[7],
|
||||
'customer_article_number': row[8],
|
||||
'open_for_order': row[9],
|
||||
'line_number': row[10],
|
||||
'created_at': row[11],
|
||||
'updated_at': row[12],
|
||||
'printed_labels': row[13],
|
||||
'data_livrare': row[14] or '-',
|
||||
'dimensiune': row[15] or '-'
|
||||
})
|
||||
|
||||
conn.close()
|
||||
return render_template('view_orders.html', orders=orders_data)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error loading view orders data: {e}")
|
||||
flash(f"Error loading orders: {e}", 'error')
|
||||
return render_template('view_orders.html', orders=[])
|
||||
|
||||
|
||||
import secrets
|
||||
|
||||
Reference in New Issue
Block a user