"""
Order Labels Module - Handles CSV upload and processing for order label generation
Author: Auto-generated module for order management
"""
import mariadb
from flask import current_app, request, render_template, session, redirect, url_for, flash
import csv
import os
import tempfile
from datetime import datetime
def get_db_connection():
"""Get database connection using external server configuration"""
settings_file = current_app.instance_path + '/external_server.conf'
settings = {}
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
settings[key] = value
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
def validate_order_row(row_data):
"""
Validate a single order row for required fields and data types
Required fields: Comanda Productie, Cantitate, Descr. Com. Prod
"""
errors = []
warnings = []
# Check required fields
if not row_data.get('comanda_productie', '').strip():
errors.append("Comanda Productie is required")
if not row_data.get('descr_com_prod', '').strip():
errors.append("Descr. Com. Prod is required")
# Validate Cantitate (quantity) - must be integer
cantitate_str = row_data.get('cantitate', '').strip()
if not cantitate_str:
errors.append("Cantitate is required")
else:
try:
cantitate = int(cantitate_str)
if cantitate <= 0:
errors.append("Cantitate must be a positive number")
elif cantitate > 999: # INT(3) limit
warnings.append("Cantitate exceeds 999 (will be truncated)")
except ValueError:
errors.append("Cantitate must be a valid number")
# Validate numeric fields (optional but must be valid if provided)
for field, max_val in [('nr_linie_com_client', 999), ('line_number', 999)]:
value = row_data.get(field, '').strip()
if value:
try:
num_val = int(value)
if num_val < 0:
warnings.append(f"{field} should be positive")
elif num_val > max_val:
warnings.append(f"{field} exceeds {max_val} (will be truncated)")
except ValueError:
errors.append(f"{field} must be a valid number")
# Validate data_livrare (optional date field)
data_livrare = row_data.get('data_livrare', '').strip()
if data_livrare:
try:
# Try to parse common date formats
for date_format in ['%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y', '%d.%m.%Y']:
try:
datetime.strptime(data_livrare, date_format)
break
except ValueError:
continue
else:
errors.append("data_livrare must be a valid date (YYYY-MM-DD, DD/MM/YYYY, MM/DD/YYYY, or DD.MM.YYYY)")
except Exception:
errors.append("data_livrare date format error")
# Validate string length limits
field_limits = {
'comanda_productie': 15,
'cod_articol': 15,
'descr_com_prod': 50,
'com_achiz_client': 25,
'customer_name': 50,
'customer_article_number': 25,
'open_for_order': 25
}
for field, max_len in field_limits.items():
value = row_data.get(field, '').strip()
if value and len(value) > max_len:
warnings.append(f"{field} exceeds {max_len} characters (will be truncated)")
return errors, warnings
def add_order_to_database(order_data):
"""
Add a single order to the order_for_labels table
Returns (success: bool, message: str)
"""
try:
conn = get_db_connection()
cursor = conn.cursor()
# Prepare data with proper types and limits
# Handle date conversion for data_livrare
data_livrare_value = None
data_livrare_str = order_data.get('data_livrare', '').strip()
if data_livrare_str:
try:
# Try to parse common date formats and convert to YYYY-MM-DD
for date_format in ['%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y', '%d.%m.%Y']:
try:
parsed_date = datetime.strptime(data_livrare_str, date_format)
data_livrare_value = parsed_date.strftime('%Y-%m-%d')
break
except ValueError:
continue
except Exception:
pass # Leave as None if parsing fails
insert_data = {
'comanda_productie': order_data.get('comanda_productie', '').strip()[:15],
'cod_articol': order_data.get('cod_articol', '').strip()[:15] or None,
'descr_com_prod': order_data.get('descr_com_prod', '').strip()[:50],
'cantitate': int(order_data.get('cantitate', 0)),
'data_livrare': data_livrare_value,
'dimensiune': order_data.get('dimensiune', '').strip()[:10] or None,
'com_achiz_client': order_data.get('com_achiz_client', '').strip()[:25] or None,
'nr_linie_com_client': int(order_data.get('nr_linie_com_client', 0)) if order_data.get('nr_linie_com_client', '').strip() else None,
'customer_name': order_data.get('customer_name', '').strip()[:50] or None,
'customer_article_number': order_data.get('customer_article_number', '').strip()[:25] or None,
'open_for_order': order_data.get('open_for_order', '').strip()[:25] or None,
'line_number': int(order_data.get('line_number', 0)) if order_data.get('line_number', '').strip() else None
}
sql = """
INSERT INTO order_for_labels
(comanda_productie, cod_articol, descr_com_prod, cantitate, data_livrare, dimensiune,
com_achiz_client, nr_linie_com_client, customer_name,
customer_article_number, open_for_order, line_number, printed_labels)
VALUES (%(comanda_productie)s, %(cod_articol)s, %(descr_com_prod)s, %(cantitate)s, %(data_livrare)s, %(dimensiune)s,
%(com_achiz_client)s, %(nr_linie_com_client)s, %(customer_name)s,
%(customer_article_number)s, %(open_for_order)s, %(line_number)s, 0)
"""
cursor.execute(sql, insert_data)
conn.commit()
conn.close()
return True, f"Successfully added order {insert_data['comanda_productie']}"
except mariadb.Error as e:
return False, f"Database error: {str(e)}"
except ValueError as e:
return False, f"Data validation error: {str(e)}"
except Exception as e:
return False, f"Unexpected error: {str(e)}"
def process_csv_file(file_path):
"""
Process uploaded CSV file and return parsed data with validation
Returns: (orders_data: list, validation_errors: list, validation_warnings: list)
"""
orders_data = []
all_errors = []
all_warnings = []
try:
with open(file_path, 'r', encoding='utf-8') as f:
# Try to detect the CSV dialect
sample = f.read(1024)
f.seek(0)
# Create CSV reader
reader = csv.DictReader(f)
# Map possible column names (case-insensitive)
column_mapping = {
'comanda productie': 'comanda_productie',
'cod articol': 'cod_articol',
'descr. com. prod': 'descr_com_prod',
'cantitate': 'cantitate',
'datalivrare': 'data_livrare',
'data livrare': 'data_livrare',
'dimensiune': 'dimensiune',
'com.achiz.client': 'com_achiz_client',
'nr. linie com. client': 'nr_linie_com_client',
'customer name': 'customer_name',
'customer article number': 'customer_article_number',
'open for order': 'open_for_order',
'line': 'line_number'
}
for row_num, row in enumerate(reader, start=2): # Start at 2 (row 1 is header)
# Normalize column names and create order data
normalized_row = {}
for col_name, col_value in row.items():
if col_name:
col_key = col_name.lower().strip()
mapped_key = column_mapping.get(col_key, col_key.replace(' ', '_').replace('.', '_'))
normalized_row[mapped_key] = col_value.strip() if col_value else ''
# Validate the row
errors, warnings = validate_order_row(normalized_row)
if errors:
all_errors.extend([f"Row {row_num}: {error}" for error in errors])
else:
# Only add valid rows
orders_data.append(normalized_row)
if warnings:
all_warnings.extend([f"Row {row_num}: {warning}" for warning in warnings])
except UnicodeDecodeError:
# Try different encodings
try:
with open(file_path, 'r', encoding='latin-1') as f:
reader = csv.DictReader(f)
# ... repeat the same processing logic
except Exception as e:
all_errors.append(f"File encoding error: {str(e)}")
except Exception as e:
all_errors.append(f"File processing error: {str(e)}")
return orders_data, all_errors, all_warnings
def upload_orders_handler():
"""
Main handler for the upload orders functionality
Handles both CSV upload/preview and database insertion
"""
report = None
orders_data = []
validation_errors = []
validation_warnings = []
temp_dir = tempfile.gettempdir()
if request.method == 'POST':
# Handle file upload
file = request.files.get('csv_file')
if file and file.filename.endswith(('.csv', '.CSV')):
try:
# Save uploaded file
temp_path = os.path.join(temp_dir, file.filename)
file.save(temp_path)
# Store file info in session
session['csv_filename'] = file.filename
session['orders_csv_filepath'] = temp_path
# Process the CSV file
orders_data, validation_errors, validation_warnings = process_csv_file(temp_path)
# Store processed data in session
session['orders_csv_data'] = orders_data
session['orders_validation_errors'] = validation_errors
session['orders_validation_warnings'] = validation_warnings
flash(f"đ File '{file.filename}' uploaded and processed successfully!", "info")
if validation_errors:
flash(f"â ī¸ Found {len(validation_errors)} validation errors. Please fix them before importing.", "warning")
if validation_warnings:
flash(f"âšī¸ Found {len(validation_warnings)} warnings. Data will be adjusted automatically.", "info")
except Exception as e:
flash(f"â Error processing file: {str(e)}", "error")
# Handle database insertion
elif request.form.get('save_to_database') and 'orders_csv_data' in session:
orders_data = session['orders_csv_data']
if not orders_data:
flash("â No valid data to save to database.", "error")
else:
success_count = 0
failed_count = 0
failed_orders = []
for order in orders_data:
success, message = add_order_to_database(order)
if success:
success_count += 1
else:
failed_count += 1
failed_orders.append(f"{order.get('comanda_productie', 'Unknown')}: {message}")
# Create report
report = f"â
Successfully imported {success_count} orders."
if failed_count > 0:
report += f" â {failed_count} orders failed to import."
for failure in failed_orders[:5]: # Show first 5 failures
report += f"
âĸ {failure}"
if len(failed_orders) > 5:
report += f"
âĸ ... and {len(failed_orders) - 5} more failures."
# Clear session data after successful import
if success_count > 0:
session.pop('orders_csv_data', None)
session.pop('csv_filename', None)
session.pop('orders_csv_filepath', None)
session.pop('orders_validation_errors', None)
session.pop('orders_validation_warnings', None)
flash(report, "success" if failed_count == 0 else "warning")
return redirect(url_for('main.view_orders'))
# Load data from session if available
elif 'orders_csv_data' in session:
orders_data = session['orders_csv_data']
validation_errors = session.get('orders_validation_errors', [])
validation_warnings = session.get('orders_validation_warnings', [])
return render_template('upload_orders.html',
orders=orders_data,
validation_errors=validation_errors,
validation_warnings=validation_warnings,
report=report)
def get_orders_from_database(limit=100):
"""
Retrieve orders from the database for display
Returns list of order dictionaries
"""
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT id, comanda_productie, cod_articol, descr_com_prod, cantitate,
data_livrare, dimensiune, com_achiz_client, nr_linie_com_client, customer_name,
customer_article_number, open_for_order, line_number,
printed_labels, created_at, updated_at
FROM order_for_labels
ORDER BY created_at DESC
LIMIT %s
""", (limit,))
orders = []
for row in cursor.fetchall():
orders.append({
'id': row[0],
'comanda_productie': row[1],
'cod_articol': row[2],
'descr_com_prod': row[3],
'cantitate': row[4],
'data_livrare': row[5],
'dimensiune': row[6],
'com_achiz_client': row[7],
'nr_linie_com_client': row[8],
'customer_name': row[9],
'customer_article_number': row[10],
'open_for_order': row[11],
'line_number': row[12],
'printed_labels': row[13],
'created_at': row[14],
'updated_at': row[15]
})
conn.close()
return orders
except Exception as e:
print(f"Error retrieving orders: {e}")
return []