- 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
203 lines
6.2 KiB
Python
203 lines
6.2 KiB
Python
"""
|
|
Quality App v2 - Flask Application Factory
|
|
Robust, modular application with login, dashboard, and multiple modules
|
|
"""
|
|
from flask import Flask
|
|
from flask_session import Session
|
|
from datetime import datetime, timedelta
|
|
import os
|
|
import logging
|
|
from logging.handlers import RotatingFileHandler
|
|
|
|
|
|
def create_app(config=None):
|
|
"""
|
|
Application factory function
|
|
Creates and configures the Flask application
|
|
"""
|
|
app = Flask(__name__)
|
|
|
|
# Load configuration
|
|
if config is None:
|
|
from app.config import Config
|
|
config = Config
|
|
|
|
app.config.from_object(config)
|
|
|
|
# Setup logging
|
|
setup_logging(app)
|
|
logger = logging.getLogger(__name__)
|
|
logger.info("=" * 80)
|
|
logger.info("Flask App Initialization Started")
|
|
logger.info("=" * 80)
|
|
|
|
# Configure session
|
|
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=8)
|
|
app.config['SESSION_COOKIE_SECURE'] = False # Set True in production with HTTPS
|
|
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
|
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
|
|
app.config['SESSION_COOKIE_NAME'] = 'quality_app_session'
|
|
app.config['SESSION_REFRESH_EACH_REQUEST'] = True
|
|
|
|
# Use filesystem for session storage (works with multiple gunicorn workers)
|
|
sessions_dir = os.path.join(app.config.get('LOG_DIR', '/app/data/logs'), '..', 'sessions')
|
|
os.makedirs(sessions_dir, exist_ok=True)
|
|
app.config['SESSION_TYPE'] = 'filesystem'
|
|
app.config['SESSION_FILE_DIR'] = sessions_dir
|
|
app.config['SESSION_FILE_THRESHOLD'] = 500
|
|
|
|
# Initialize Flask-Session
|
|
Session(app)
|
|
|
|
# Initialize database connection
|
|
logger.info("Initializing database connection...")
|
|
from app.database import init_db, close_db
|
|
init_db(app)
|
|
app.teardown_appcontext(close_db)
|
|
|
|
# Register blueprints
|
|
logger.info("Registering blueprints...")
|
|
register_blueprints(app)
|
|
|
|
# Register error handlers
|
|
logger.info("Registering error handlers...")
|
|
register_error_handlers(app)
|
|
|
|
# Add template globals
|
|
app.jinja_env.globals['now'] = datetime.now
|
|
|
|
# Add context processor for app name
|
|
@app.context_processor
|
|
def inject_app_settings():
|
|
"""Inject app settings into all templates"""
|
|
try:
|
|
from app.database import get_db
|
|
conn = get_db()
|
|
cursor = conn.cursor()
|
|
cursor.execute(
|
|
"SELECT setting_value FROM application_settings WHERE setting_key = %s",
|
|
('app_name',)
|
|
)
|
|
result = cursor.fetchone()
|
|
cursor.close()
|
|
app_name = result[0] if result else 'Quality App v2'
|
|
except:
|
|
app_name = 'Quality App v2'
|
|
|
|
return {'app_name': app_name}
|
|
|
|
# Add before_request handlers
|
|
register_request_handlers(app)
|
|
|
|
# Initialize backup scheduler
|
|
logger.info("Initializing backup scheduler...")
|
|
try:
|
|
from app.scheduler import init_scheduler
|
|
init_scheduler(app)
|
|
except Exception as e:
|
|
logger.error(f"Failed to initialize backup scheduler: {e}")
|
|
|
|
logger.info("=" * 80)
|
|
logger.info("Flask App Initialization Completed Successfully")
|
|
logger.info("=" * 80)
|
|
|
|
return app
|
|
|
|
|
|
|
|
def setup_logging(app):
|
|
"""Configure application logging"""
|
|
log_dir = app.config.get('LOG_DIR', '/app/data/logs')
|
|
|
|
# Create log directory if it doesn't exist
|
|
if not os.path.exists(log_dir):
|
|
os.makedirs(log_dir, exist_ok=True)
|
|
|
|
# Configure rotating file handler
|
|
log_file = os.path.join(log_dir, 'app.log')
|
|
handler = RotatingFileHandler(
|
|
log_file,
|
|
maxBytes=10485760, # 10MB
|
|
backupCount=10
|
|
)
|
|
|
|
# Create formatter
|
|
formatter = logging.Formatter(
|
|
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
)
|
|
handler.setFormatter(formatter)
|
|
|
|
# Set logging level
|
|
log_level = app.config.get('LOG_LEVEL', 'INFO')
|
|
handler.setLevel(getattr(logging, log_level))
|
|
|
|
# Add handler to app logger
|
|
app.logger.addHandler(handler)
|
|
app.logger.setLevel(getattr(logging, log_level))
|
|
|
|
|
|
def register_blueprints(app):
|
|
"""Register application blueprints"""
|
|
from app.routes import main_bp
|
|
from app.modules.quality.routes import quality_bp
|
|
from app.modules.settings.routes import settings_bp
|
|
from app.modules.warehouse.routes import warehouse_bp
|
|
from app.modules.warehouse.boxes_routes import boxes_bp
|
|
from app.modules.labels.routes import labels_bp
|
|
|
|
app.register_blueprint(main_bp)
|
|
app.register_blueprint(quality_bp, url_prefix='/quality')
|
|
app.register_blueprint(settings_bp, url_prefix='/settings')
|
|
app.register_blueprint(warehouse_bp, url_prefix='/warehouse')
|
|
app.register_blueprint(boxes_bp)
|
|
app.register_blueprint(labels_bp, url_prefix='/labels')
|
|
|
|
app.logger.info("Blueprints registered: main, quality, settings, warehouse, boxes, labels")
|
|
|
|
|
|
def register_error_handlers(app):
|
|
"""Register error handlers"""
|
|
|
|
@app.errorhandler(404)
|
|
def page_not_found(e):
|
|
from flask import render_template
|
|
return render_template('errors/404.html'), 404
|
|
|
|
@app.errorhandler(500)
|
|
def internal_error(e):
|
|
from flask import render_template
|
|
app.logger.error(f"Internal error: {e}")
|
|
return render_template('errors/500.html'), 500
|
|
|
|
@app.errorhandler(403)
|
|
def forbidden(e):
|
|
from flask import render_template
|
|
return render_template('errors/403.html'), 403
|
|
|
|
|
|
def register_request_handlers(app):
|
|
"""Register before/after request handlers"""
|
|
|
|
@app.before_request
|
|
def before_request():
|
|
"""Handle pre-request logic"""
|
|
from flask import session, request, redirect, url_for
|
|
|
|
# Skip authentication check for login and static files
|
|
if request.endpoint and (
|
|
request.endpoint in ['static', 'main.login', 'main.index'] or
|
|
request.path.startswith('/static/')
|
|
):
|
|
return None
|
|
|
|
# Check if user is logged in
|
|
if 'user_id' not in session:
|
|
return redirect(url_for('main.login'))
|
|
|
|
return None
|
|
|
|
@app.after_request
|
|
def after_request(response):
|
|
"""Handle post-request logic"""
|
|
return response
|