feat: Migrate label printing module from legacy app

- Created labels module with complete structure in app/modules/labels/
- Implemented print_module.py with database functions:
  * get_unprinted_orders_data() - Retrieve unprinted orders from database
  * get_printed_orders_data() - Retrieve printed orders from database
  * update_order_printed_status() - Mark orders as printed
  * search_orders_by_cp_code() - Search orders by production code
- Created routes.py with Flask Blueprint and API endpoints:
  * GET /labels/ - Module home page with feature launchers
  * GET /labels/print-module - Main label printing interface
  * GET /labels/print-lost-labels - Lost label reprinting interface
  * GET /labels/api/unprinted-orders - API for unprinted orders
  * GET /labels/api/printed-orders - API for printed orders
  * POST /labels/api/search-orders - Search orders API
  * POST /labels/api/update-printed-status/<id> - Update status API
- Migrated HTML templates from legacy app with theme support:
  * print_module.html - Thermal label printing with QZ Tray integration
  * print_lost_labels.html - Lost label search and reprint interface
  * Added comprehensive CSS variables for dark/light mode theming
  * Preserved all original JavaScript functionality for printing
- Created index.html module home page with feature launchers
- Registered labels blueprint in app/__init__.py with /labels prefix
- Added 'Label Printing' module card to dashboard with print icon

All functionality preserved from original implementation:
- QZ Tray thermal printer integration
- JsBarcode barcode generation (horizontal + vertical)
- PDF export fallback
- Session-based authentication for all routes
- Database integration with proper error handling
This commit is contained in:
Quality App Developer
2026-02-02 01:18:54 +02:00
parent f54e1bebc3
commit 2f6bb5d029
8 changed files with 3095 additions and 1 deletions

View File

@@ -0,0 +1,2 @@
# Labels Module
# Handles label printing and management for thermal printers

View File

@@ -0,0 +1,200 @@
"""
Labels Module - Print Module Functions
Handles retrieval of orders for label printing
"""
import logging
from app.database import get_db
logger = logging.getLogger(__name__)
def get_unprinted_orders_data(limit=100):
"""
Retrieve unprinted orders from the database for label printing
Returns list of order dictionaries where printed_labels != 1
"""
try:
conn = get_db()
cursor = conn.cursor()
# Check if order_for_labels table exists
cursor.execute("SHOW TABLES LIKE 'order_for_labels'")
if not cursor.fetchone():
logger.warning("order_for_labels table does not exist")
return []
# Check if printed_labels column exists
cursor.execute("SHOW COLUMNS FROM order_for_labels LIKE 'printed_labels'")
column_exists = cursor.fetchone()
if column_exists:
# Use printed_labels column
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
WHERE printed_labels IS NULL OR printed_labels = 0
ORDER BY created_at DESC
LIMIT %s
""", (limit,))
else:
# Fallback: get all orders if no printed_labels column
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, 0 as printed_labels, data_livrare, dimensiune
FROM order_for_labels
ORDER BY created_at DESC
LIMIT %s
""", (limit,))
columns = [col[0] for col in cursor.description]
orders = []
for row in cursor.fetchall():
order_dict = {columns[i]: row[i] for i in range(len(columns))}
# Ensure date fields are strings
if order_dict.get('created_at'):
order_dict['created_at'] = str(order_dict['created_at'])
if order_dict.get('updated_at'):
order_dict['updated_at'] = str(order_dict['updated_at'])
if order_dict.get('data_livrare'):
order_dict['data_livrare'] = str(order_dict['data_livrare'])
orders.append(order_dict)
cursor.close()
logger.info(f"Retrieved {len(orders)} unprinted orders")
return orders
except Exception as e:
logger.error(f"Error retrieving unprinted orders: {e}")
return []
def get_printed_orders_data(limit=100):
"""
Retrieve printed orders from the database
Returns list of order dictionaries where printed_labels = 1
"""
try:
conn = get_db()
cursor = conn.cursor()
# Check if order_for_labels table exists
cursor.execute("SHOW TABLES LIKE 'order_for_labels'")
if not cursor.fetchone():
logger.warning("order_for_labels table does not exist")
return []
# Check if printed_labels column exists
cursor.execute("SHOW COLUMNS FROM order_for_labels LIKE 'printed_labels'")
column_exists = cursor.fetchone()
if column_exists:
# Get orders where printed_labels = 1
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
WHERE printed_labels = 1
ORDER BY updated_at DESC
LIMIT %s
""", (limit,))
else:
# Fallback: no printed orders if column doesn't exist
logger.info("printed_labels column does not exist - no printed orders available")
cursor.close()
return []
columns = [col[0] for col in cursor.description]
orders = []
for row in cursor.fetchall():
order_dict = {columns[i]: row[i] for i in range(len(columns))}
# Ensure date fields are strings
if order_dict.get('created_at'):
order_dict['created_at'] = str(order_dict['created_at'])
if order_dict.get('updated_at'):
order_dict['updated_at'] = str(order_dict['updated_at'])
if order_dict.get('data_livrare'):
order_dict['data_livrare'] = str(order_dict['data_livrare'])
orders.append(order_dict)
cursor.close()
logger.info(f"Retrieved {len(orders)} printed orders")
return orders
except Exception as e:
logger.error(f"Error retrieving printed orders: {e}")
return []
def update_order_printed_status(order_id, printed=True):
"""
Update the printed_labels status for an order
"""
try:
conn = get_db()
cursor = conn.cursor()
cursor.execute("""
UPDATE order_for_labels
SET printed_labels = %s
WHERE id = %s
""", (1 if printed else 0, order_id))
conn.commit()
cursor.close()
logger.info(f"Updated order {order_id} printed status to {printed}")
return True
except Exception as e:
logger.error(f"Error updating order printed status: {e}")
return False
def search_orders_by_cp_code(cp_code_search):
"""
Search for orders by CP code / Production Order
"""
try:
conn = get_db()
cursor = conn.cursor()
search_term = cp_code_search.strip().upper()
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
WHERE UPPER(comanda_productie) LIKE %s
ORDER BY created_at DESC
LIMIT 100
""", (f"{search_term}%",))
columns = [col[0] for col in cursor.description]
orders = []
for row in cursor.fetchall():
order_dict = {columns[i]: row[i] for i in range(len(columns))}
if order_dict.get('created_at'):
order_dict['created_at'] = str(order_dict['created_at'])
if order_dict.get('updated_at'):
order_dict['updated_at'] = str(order_dict['updated_at'])
if order_dict.get('data_livrare'):
order_dict['data_livrare'] = str(order_dict['data_livrare'])
orders.append(order_dict)
cursor.close()
return orders
except Exception as e:
logger.error(f"Error searching orders by CP code: {e}")
return []

View File

@@ -0,0 +1,128 @@
"""
Labels Module Routes
Handles label printing pages and API endpoints
"""
from flask import Blueprint, render_template, session, redirect, url_for, jsonify, request
import logging
from .print_module import (
get_unprinted_orders_data,
get_printed_orders_data,
update_order_printed_status,
search_orders_by_cp_code
)
logger = logging.getLogger(__name__)
labels_bp = Blueprint('labels', __name__, url_prefix='/labels')
@labels_bp.route('/', methods=['GET'])
def labels_index():
"""Labels module home page"""
if 'user_id' not in session:
return redirect(url_for('main.login'))
return render_template('modules/labels/index.html')
@labels_bp.route('/print-module', methods=['GET'])
def print_module():
"""Label printing interface with thermal printer support"""
if 'user_id' not in session:
return redirect(url_for('main.login'))
return render_template('modules/labels/print_module.html')
@labels_bp.route('/print-lost-labels', methods=['GET'])
def print_lost_labels():
"""Print lost/missing labels interface"""
if 'user_id' not in session:
return redirect(url_for('main.login'))
return render_template('modules/labels/print_lost_labels.html')
# ============================================================================
# API Endpoints for Labels Module
# ============================================================================
@labels_bp.route('/api/unprinted-orders', methods=['GET'], endpoint='api_unprinted_orders')
def api_unprinted_orders():
"""Get all unprinted orders for label printing"""
if 'user_id' not in session:
return jsonify({'success': False, 'error': 'Unauthorized'}), 401
try:
limit = request.args.get('limit', 100, type=int)
if limit > 500:
limit = 500
if limit < 1:
limit = 1
orders = get_unprinted_orders_data(limit)
return jsonify({'success': True, 'orders': orders, 'count': len(orders)}), 200
except Exception as e:
logger.error(f"Error getting unprinted orders: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@labels_bp.route('/api/printed-orders', methods=['GET'], endpoint='api_printed_orders')
def api_printed_orders():
"""Get all printed orders"""
if 'user_id' not in session:
return jsonify({'success': False, 'error': 'Unauthorized'}), 401
try:
limit = request.args.get('limit', 100, type=int)
if limit > 500:
limit = 500
if limit < 1:
limit = 1
orders = get_printed_orders_data(limit)
return jsonify({'success': True, 'orders': orders, 'count': len(orders)}), 200
except Exception as e:
logger.error(f"Error getting printed orders: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@labels_bp.route('/api/search-orders', methods=['POST'], endpoint='api_search_orders')
def api_search_orders():
"""Search for orders by CP code"""
if 'user_id' not in session:
return jsonify({'success': False, 'error': 'Unauthorized'}), 401
try:
data = request.get_json()
cp_code = data.get('cp_code', '').strip()
if not cp_code or len(cp_code) < 1:
return jsonify({'success': False, 'error': 'CP code is required'}), 400
results = search_orders_by_cp_code(cp_code)
return jsonify({'success': True, 'orders': results, 'count': len(results)}), 200
except Exception as e:
logger.error(f"Error searching orders: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@labels_bp.route('/api/update-printed-status/<int:order_id>', methods=['POST'], endpoint='api_update_printed_status')
def api_update_printed_status(order_id):
"""Mark an order as printed"""
if 'user_id' not in session:
return jsonify({'success': False, 'error': 'Unauthorized'}), 401
try:
data = request.get_json() or {}
printed = data.get('printed', True)
success = update_order_printed_status(order_id, printed)
if success:
return jsonify({'success': True, 'message': 'Order status updated'}), 200
else:
return jsonify({'success': False, 'error': 'Failed to update order'}), 500
except Exception as e:
logger.error(f"Error updating order status: {e}")
return jsonify({'success': False, 'error': str(e)}), 500