""" Flask主应用 订单日报系统 """ import os import json from datetime import datetime from flask import Flask, request, jsonify, send_from_directory, render_template from flask_cors import CORS from apscheduler.schedulers.background import BackgroundScheduler import pytz from lib.db import execute_query, execute_update, execute_insert from lib.field_mapping import get_all_fields_with_display from lib.report_generator import generate_daily_report # 创建Flask应用 app = Flask(__name__, template_folder='templates', static_folder='public') CORS(app) # 确保public/reports目录存在 os.makedirs(os.path.join('public', 'reports'), exist_ok=True) # ==================== 页面路由 ==================== @app.route('/') def index(): """主页""" return render_template('index.html') # ==================== 配置管理API ==================== @app.route('/api/config', methods=['GET']) def get_configs(): """获取所有配置""" try: configs = execute_query( 'SELECT * FROM t_daily_report_config ORDER BY sort_order ASC, id DESC' ) # 解析JSON字段 for config in configs: config['selected_fields'] = json.loads(config['selected_fields']) config['sum_fields'] = json.loads(config['sum_fields']) if config['sum_fields'] else [] return jsonify({'success': True, 'data': configs}) except Exception as e: return jsonify({'success': False, 'message': str(e)}), 500 @app.route('/api/config', methods=['POST']) def create_config(): """创建配置""" try: data = request.json config_name = data.get('config_name') split_type = data.get('split_type') split_value = data.get('split_value') split_name = data.get('split_name', '') selected_fields = data.get('selected_fields', []) sum_fields = data.get('sum_fields', []) if not config_name or not split_type or not split_value: return jsonify({'success': False, 'message': '缺少必填字段'}), 400 if not selected_fields: return jsonify({'success': False, 'message': '请至少选择一个字段'}), 400 # 生成ID config_id = int(datetime.now().timestamp() * 1000) # 获取最大排序值 max_sort = execute_query('SELECT MAX(sort_order) as max_sort FROM t_daily_report_config') sort_order = (max_sort[0]['max_sort'] or 0) + 1 sql = """ INSERT INTO t_daily_report_config (id, config_name, split_type, split_value, split_name, selected_fields, sum_fields, is_active, sort_order, create_time, update_time) VALUES (%s, %s, %s, %s, %s, %s, %s, 1, %s, NOW(), NOW()) """ params = ( config_id, config_name, split_type, split_value, split_name, json.dumps(selected_fields, ensure_ascii=False), json.dumps(sum_fields, ensure_ascii=False), sort_order ) execute_insert(sql, params) return jsonify({ 'success': True, 'message': '配置创建成功', 'data': {'id': config_id} }) except Exception as e: return jsonify({'success': False, 'message': str(e)}), 500 @app.route('/api/config/', methods=['PUT']) def update_config(config_id): """更新配置""" try: data = request.json config_name = data.get('config_name') split_type = data.get('split_type') split_value = data.get('split_value') split_name = data.get('split_name', '') selected_fields = data.get('selected_fields', []) sum_fields = data.get('sum_fields', []) sql = """ UPDATE t_daily_report_config SET config_name = %s, split_type = %s, split_value = %s, split_name = %s, selected_fields = %s, sum_fields = %s, update_time = NOW() WHERE id = %s """ params = ( config_name, split_type, split_value, split_name, json.dumps(selected_fields, ensure_ascii=False), json.dumps(sum_fields, ensure_ascii=False), config_id ) execute_update(sql, params) return jsonify({'success': True, 'message': '配置更新成功'}) except Exception as e: return jsonify({'success': False, 'message': str(e)}), 500 @app.route('/api/config/', methods=['DELETE']) def delete_config(config_id): """删除配置""" try: execute_update('DELETE FROM t_daily_report_config WHERE id = %s', (config_id,)) return jsonify({'success': True, 'message': '配置删除成功'}) except Exception as e: return jsonify({'success': False, 'message': str(e)}), 500 @app.route('/api/config//toggle', methods=['POST']) def toggle_config(config_id): """启用/禁用配置""" try: execute_update( 'UPDATE t_daily_report_config SET is_active = NOT is_active, update_time = NOW() WHERE id = %s', (config_id,) ) return jsonify({'success': True, 'message': '操作成功'}) except Exception as e: return jsonify({'success': False, 'message': str(e)}), 500 @app.route('/api/config//move', methods=['POST']) def move_config(config_id): """移动配置排序""" try: direction = request.json.get('direction') # up 或 down # 获取当前配置 current = execute_query( 'SELECT sort_order FROM t_daily_report_config WHERE id = %s', (config_id,) ) if not current: return jsonify({'success': False, 'message': '配置不存在'}), 404 current_order = current[0]['sort_order'] if direction == 'up': # 找到上一个配置 prev = execute_query( 'SELECT id, sort_order FROM t_daily_report_config WHERE sort_order < %s ORDER BY sort_order DESC LIMIT 1', (current_order,) ) if prev: # 交换排序 execute_update( 'UPDATE t_daily_report_config SET sort_order = %s WHERE id = %s', (prev[0]['sort_order'], config_id) ) execute_update( 'UPDATE t_daily_report_config SET sort_order = %s WHERE id = %s', (current_order, prev[0]['id']) ) elif direction == 'down': # 找到下一个配置 next_config = execute_query( 'SELECT id, sort_order FROM t_daily_report_config WHERE sort_order > %s ORDER BY sort_order ASC LIMIT 1', (current_order,) ) if next_config: # 交换排序 execute_update( 'UPDATE t_daily_report_config SET sort_order = %s WHERE id = %s', (next_config[0]['sort_order'], config_id) ) execute_update( 'UPDATE t_daily_report_config SET sort_order = %s WHERE id = %s', (current_order, next_config[0]['id']) ) return jsonify({'success': True, 'message': '移动成功'}) except Exception as e: return jsonify({'success': False, 'message': str(e)}), 500 # ==================== 企业和用户列表API ==================== @app.route('/api/entities', methods=['GET']) def get_entities(): """获取企业或用户列表""" try: entity_type = request.args.get('type', 'company') if entity_type == 'company': # 获取企业列表 companies = execute_query( 'SELECT DISTINCT company_id, company_name FROM t_company ORDER BY company_name' ) return jsonify({'success': True, 'data': companies}) else: # 获取用户列表 users = execute_query( 'SELECT DISTINCT user_id, user_name, order_type FROM t_equipment_charge_order WHERE user_id IS NOT NULL ORDER BY user_name' ) return jsonify({'success': True, 'data': users}) except Exception as e: return jsonify({'success': False, 'message': str(e)}), 500 # ==================== 字段列表API ==================== @app.route('/api/fields', methods=['GET']) def get_fields(): """获取所有可用字段""" try: fields = get_all_fields_with_display() return jsonify({'success': True, 'data': fields}) except Exception as e: return jsonify({'success': False, 'message': str(e)}), 500 # ==================== 日报生成API ==================== @app.route('/api/report/generate', methods=['POST']) def generate_report(): """生成日报""" try: data = request.json config_id = data.get('config_id') if not config_id: return jsonify({'success': False, 'message': '请选择配置'}), 400 # 解析时间参数 start_time = None end_time = None if data.get('start_time'): start_time = datetime.strptime(data['start_time'], '%Y-%m-%d %H:%M:%S') if data.get('end_time'): end_time = datetime.strptime(data['end_time'], '%Y-%m-%d %H:%M:%S') result = generate_daily_report(config_id, start_time, end_time) if result['success']: return jsonify(result) else: return jsonify(result), 400 except Exception as e: return jsonify({'success': False, 'message': str(e)}), 500 # ==================== 历史记录API ==================== @app.route('/api/report/history', methods=['GET']) def get_report_history(): """获取日报生成历史""" try: page = int(request.args.get('page', 1)) page_size = int(request.args.get('page_size', 20)) offset = (page - 1) * page_size # 查询总数 total_result = execute_query('SELECT COUNT(*) as total FROM t_daily_report_history') total = total_result[0]['total'] # 查询数据 history = execute_query( 'SELECT * FROM t_daily_report_history ORDER BY create_time DESC LIMIT %s OFFSET %s', (page_size, offset) ) # 解析JSON字段并确保file_path正确 for item in history: item['sum_results'] = json.loads(item['sum_results']) if item['sum_results'] else {} # 确保file_path是字符串 if item['file_path'] is None: item['file_path'] = '' # 调试日志 print(f"[历史记录] ID: {item['id']}, status: {item['status']}, file_path: '{item['file_path']}'") return jsonify({ 'success': True, 'data': { 'list': history, 'total': total, 'page': page, 'page_size': page_size } }) except Exception as e: print(f"[历史记录API] 错误: {e}") import traceback traceback.print_exc() return jsonify({'success': False, 'message': str(e)}), 500 # ==================== 数据库初始化API ==================== @app.route('/api/init-tables', methods=['POST']) def init_tables(): """初始化数据库表""" try: # 创建配置表 create_config_table = """ CREATE TABLE IF NOT EXISTS t_daily_report_config ( id BIGINT NOT NULL COMMENT '配置ID', config_name VARCHAR(100) NOT NULL COMMENT '配置名称', split_type VARCHAR(20) NOT NULL COMMENT '拆分方式', split_value VARCHAR(100) NOT NULL COMMENT '拆分值', split_name VARCHAR(200) NULL COMMENT '拆分显示名称', selected_fields VARCHAR(3000) NOT NULL COMMENT '选择的字段列表', sum_fields VARCHAR(3000) NULL COMMENT '求和字段列表', is_active TINYINT NULL COMMENT '是否启用', sort_order INT NULL COMMENT '排序顺序', create_time DATETIME NULL COMMENT '创建时间', update_time DATETIME NULL COMMENT '更新时间' ) UNIQUE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1 PROPERTIES("replication_num" = "1") """ execute_update(create_config_table) # 创建历史表 create_history_table = """ CREATE TABLE IF NOT EXISTS t_daily_report_history ( id BIGINT NOT NULL COMMENT '历史ID', report_date VARCHAR(20) NULL COMMENT '报表日期', start_time VARCHAR(50) NULL COMMENT '开始时间', end_time VARCHAR(50) NULL COMMENT '结束时间', split_type VARCHAR(20) NULL COMMENT '拆分方式', split_value VARCHAR(100) NULL COMMENT '拆分值', split_name VARCHAR(200) NULL COMMENT '拆分显示名称', config_id BIGINT NULL COMMENT '配置ID', total_orders INT NULL COMMENT '订单总数', total_amount DECIMAL(10,2) NULL COMMENT '总金额', sum_results VARCHAR(2000) NULL COMMENT '求和结果', file_path VARCHAR(500) NULL COMMENT '文件路径', status TINYINT NULL COMMENT '状态', create_time DATETIME NULL COMMENT '创建时间' ) UNIQUE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1 PROPERTIES("replication_num" = "1") """ execute_update(create_history_table) return jsonify({'success': True, 'message': '数据库表初始化成功'}) except Exception as e: return jsonify({'success': False, 'message': str(e)}), 500 # ==================== 文件下载API ==================== @app.route('/api/download', methods=['GET']) def download_file(): """下载文件""" try: file_path = request.args.get('path') print(f"[下载] 请求路径: {file_path}") if not file_path: return jsonify({'success': False, 'message': '文件路径不能为空'}), 400 # 安全检查:确保路径在public目录下 if not file_path.startswith('/reports/'): return jsonify({'success': False, 'message': '无效的文件路径'}), 400 filename = file_path.replace('/reports/', '') # 使用绝对路径 reports_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'public', 'reports') print(f"[下载] 文件目录: {reports_dir}") print(f"[下载] 文件名: {filename}") print(f"[下载] 文件是否存在: {os.path.exists(os.path.join(reports_dir, filename))}") # 列出目录中的所有文件 if os.path.exists(reports_dir): print(f"[下载] 目录中的文件: {os.listdir(reports_dir)}") return send_from_directory(reports_dir, filename, as_attachment=True) except Exception as e: print(f"[下载] 错误: {str(e)}") import traceback traceback.print_exc() return jsonify({'success': False, 'message': str(e)}), 500 # ==================== 定时任务 ==================== def scheduled_job(): """定时任务:每天8:01自动生成日报""" print(f'[{datetime.now()}] 开始执行定时任务...') try: # 获取所有启用的配置 configs = execute_query( 'SELECT id, config_name FROM t_daily_report_config WHERE is_active = 1' ) if not configs: print('没有启用的配置,跳过生成') return print(f'找到 {len(configs)} 个启用的配置') # 为每个配置生成日报 for config in configs: print(f'正在生成: {config["config_name"]}') result = generate_daily_report(config['id']) if result['success']: print(f' ✓ 成功: {result["total_orders"]} 条订单, 金额: {result["total_amount"]}') else: print(f' ✗ 失败: {result["message"]}') print('定时任务执行完成') except Exception as e: print(f'定时任务执行失败: {e}') # 启动定时任务 scheduler = BackgroundScheduler(timezone='Asia/Shanghai') scheduler.add_job(scheduled_job, 'cron', hour=8, minute=1) scheduler.start() print('定时任务已启动:每天 08:01 执行') # ==================== 启动应用 ==================== if __name__ == '__main__': # 支持沙箱环境和本地环境 port = int(os.environ.get('DEPLOY_RUN_PORT', os.environ.get('PORT', 5000))) print(f'启动服务器: http://localhost:{port}') print(f'数据库: {os.environ.get("DB_HOST", "haoslm2.xicp.net")}:{os.environ.get("DB_PORT", "10216")}') app.run(host='0.0.0.0', port=port, debug=True)