增加日志
This commit is contained in:
33
app.py
33
app.py
@@ -13,6 +13,7 @@ from lib.db import execute_query
|
||||
from lib.report_generator import generate_daily_report
|
||||
from lib.api import register_blueprints
|
||||
from lib.db_init import init_database_tables
|
||||
from lib.logger import log_info, log_error, log_warning
|
||||
|
||||
# 创建 Flask 应用
|
||||
app = Flask(__name__,
|
||||
@@ -27,11 +28,11 @@ os.makedirs(os.path.join('public', 'reports'), exist_ok=True)
|
||||
register_blueprints(app)
|
||||
|
||||
# 应用启动时自动初始化数据库表
|
||||
print('正在初始化数据库表...')
|
||||
log_info('正在初始化数据库表...', 'app')
|
||||
if init_database_tables():
|
||||
print('数据库表初始化成功')
|
||||
log_info('数据库表初始化成功', 'app')
|
||||
else:
|
||||
print('数据库表初始化失败,请检查数据库连接')
|
||||
log_error('数据库表初始化失败,请检查数据库连接', 'app')
|
||||
|
||||
|
||||
# ==================== 页面路由 ====================
|
||||
@@ -46,7 +47,7 @@ def index():
|
||||
|
||||
def scheduled_job():
|
||||
"""定时任务:每天 8:01 自动生成日报"""
|
||||
print(f'[{datetime.now()}] 开始执行定时任务...')
|
||||
log_info(f'开始执行定时任务...', 'scheduler')
|
||||
|
||||
try:
|
||||
# 获取所有启用的配置
|
||||
@@ -55,31 +56,35 @@ def scheduled_job():
|
||||
)
|
||||
|
||||
if not configs:
|
||||
print('没有启用的配置,跳过生成')
|
||||
log_warning('没有启用的配置,跳过生成', 'scheduler')
|
||||
return
|
||||
|
||||
print(f'找到 {len(configs)} 个启用的配置')
|
||||
log_info(f'找到 {len(configs)} 个启用的配置', 'scheduler')
|
||||
|
||||
# 为每个配置生成日报
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
for config in configs:
|
||||
print(f'正在生成:{config["config_name"]}')
|
||||
log_info(f'正在生成:{config["config_name"]} (ID: {config["id"]})', 'scheduler')
|
||||
result = generate_daily_report(config['id'])
|
||||
|
||||
if result['success']:
|
||||
print(f' ✓ 成功:{result["total_orders"]} 条订单,金额:{result["total_amount"]}')
|
||||
log_info(f' ✓ 成功:{result["total_orders"]} 条订单,金额:{result["total_amount"]}', 'scheduler')
|
||||
success_count += 1
|
||||
else:
|
||||
print(f' ✗ 失败:{result["message"]}')
|
||||
log_error(f' ✗ 失败:{result["message"]}', 'scheduler')
|
||||
fail_count += 1
|
||||
|
||||
print('定时任务执行完成')
|
||||
log_info(f'定时任务执行完成,成功 {success_count} 个,失败 {fail_count} 个', 'scheduler')
|
||||
except Exception as e:
|
||||
print(f'定时任务执行失败:{e}')
|
||||
log_error(f'定时任务执行失败:{e}', 'scheduler')
|
||||
|
||||
|
||||
# 启动定时任务
|
||||
scheduler = BackgroundScheduler(timezone='Asia/Shanghai')
|
||||
scheduler.add_job(scheduled_job, 'cron', hour=8, minute=1)
|
||||
scheduler.start()
|
||||
print('定时任务已启动:每天 08:01 执行')
|
||||
log_info('定时任务已启动:每天 08:01 执行', 'app')
|
||||
|
||||
|
||||
# ==================== 启动应用 ====================
|
||||
@@ -87,6 +92,6 @@ 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")}')
|
||||
log_info(f'启动服务器:http://localhost:{port}', 'app')
|
||||
log_info(f'数据库:{os.environ.get("DB_HOST", "haoslm2.xicp.net")}:{os.environ.get("DB_PORT", "10216")}', 'app')
|
||||
app.run(host='0.0.0.0', port=port, debug=True)
|
||||
|
||||
@@ -5,6 +5,7 @@ import json
|
||||
from datetime import datetime
|
||||
from flask import request, jsonify
|
||||
from lib.db import execute_query, execute_update, execute_insert
|
||||
from lib.logger import log_info, log_error, log_warning
|
||||
from . import config_bp
|
||||
|
||||
|
||||
@@ -12,6 +13,7 @@ from . import config_bp
|
||||
def get_configs():
|
||||
"""获取所有配置"""
|
||||
try:
|
||||
log_info('[配置API] 获取配置列表', 'api')
|
||||
configs = execute_query(
|
||||
'SELECT * FROM t_daily_report_config ORDER BY sort_order ASC, id DESC'
|
||||
)
|
||||
@@ -22,8 +24,10 @@ def get_configs():
|
||||
config['sum_fields'] = json.loads(config['sum_fields']) if config['sum_fields'] else []
|
||||
config['time_periods'] = json.loads(config['time_periods']) if config.get('time_periods') else {}
|
||||
|
||||
log_info(f'[配置API] 获取到 {len(configs)} 条配置', 'api')
|
||||
return jsonify({'success': True, 'data': configs})
|
||||
except Exception as e:
|
||||
log_error(f'[配置API] 获取配置列表失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -31,6 +35,7 @@ def get_configs():
|
||||
def create_config():
|
||||
"""创建配置"""
|
||||
try:
|
||||
log_info('[配置API] 创建配置', 'api')
|
||||
data = request.json
|
||||
config_name = data.get('config_name')
|
||||
split_type = data.get('split_type')
|
||||
@@ -52,9 +57,11 @@ def create_config():
|
||||
custom_service_fee_name = data.get('custom_service_fee_name')
|
||||
|
||||
if not config_name or not split_type or not split_value:
|
||||
log_warning('[配置API] 创建配置失败:缺少必填字段', 'api')
|
||||
return jsonify({'success': False, 'message': '缺少必填字段'}), 400
|
||||
|
||||
if not selected_fields:
|
||||
log_warning('[配置API] 创建配置失败:未选择字段', 'api')
|
||||
return jsonify({'success': False, 'message': '请至少选择一个字段'}), 400
|
||||
|
||||
# 生成 ID
|
||||
@@ -92,12 +99,14 @@ def create_config():
|
||||
|
||||
execute_insert(sql, params)
|
||||
|
||||
log_info(f'[配置API] 配置创建成功,ID: {config_id},名称: {config_name}', 'api')
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '配置创建成功',
|
||||
'data': {'id': config_id}
|
||||
})
|
||||
except Exception as e:
|
||||
log_error(f'[配置API] 创建配置失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -105,6 +114,7 @@ def create_config():
|
||||
def update_config(config_id):
|
||||
"""更新配置"""
|
||||
try:
|
||||
log_info(f'[配置API] 更新配置,ID: {config_id}', 'api')
|
||||
data = request.json
|
||||
config_name = data.get('config_name')
|
||||
split_type = data.get('split_type')
|
||||
@@ -125,6 +135,12 @@ def update_config(config_id):
|
||||
|
||||
custom_service_fee_name = data.get('custom_service_fee_name')
|
||||
|
||||
# 查询原配置,检查拆分是否变化
|
||||
old_configs = execute_query('SELECT * FROM t_daily_report_config WHERE id = %s', (config_id,))
|
||||
if not old_configs:
|
||||
return jsonify({'success': False, 'message': '配置不存在'}), 404
|
||||
old_config = old_configs[0]
|
||||
|
||||
sql = """
|
||||
UPDATE t_daily_report_config
|
||||
SET config_name = %s, split_type = %s, split_value = %s, split_name = %s,
|
||||
@@ -154,8 +170,32 @@ def update_config(config_id):
|
||||
|
||||
execute_update(sql, params)
|
||||
|
||||
# 如果拆分类型或拆分值变化了,清理旧的求和数据(以当前拆分为准)
|
||||
old_split_type = old_config.get('split_type')
|
||||
old_split_value = old_config.get('split_value', '')
|
||||
if old_split_type != split_type or old_split_value != split_value:
|
||||
new_values = set(v.strip() for v in split_value.split(',') if v.strip())
|
||||
old_values = set(v.strip() for v in str(old_split_value).split(',') if v.strip())
|
||||
removed_values = old_values - new_values
|
||||
if removed_values:
|
||||
placeholders = ','.join(['%s'] * len(removed_values))
|
||||
delete_sql = f'''
|
||||
DELETE FROM t_daily_report_sum_data
|
||||
WHERE config_id = %s AND split_type = %s
|
||||
AND split_value IN ({placeholders})
|
||||
'''
|
||||
delete_params = [config_id, old_split_type] + list(removed_values)
|
||||
try:
|
||||
from lib.db import execute_update
|
||||
execute_update(delete_sql, tuple(delete_params))
|
||||
log_info(f'[配置API] 拆分值变化,已清理{len(removed_values)}个旧拆分的求和数据', 'api')
|
||||
except Exception as e:
|
||||
log_error(f'[配置API] 清理旧求和数据失败: {e}', 'api')
|
||||
|
||||
log_info(f'[配置API] 配置更新成功,ID: {config_id}', 'api')
|
||||
return jsonify({'success': True, 'message': '配置更新成功'})
|
||||
except Exception as e:
|
||||
log_error(f'[配置API] 更新配置失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -163,9 +203,18 @@ def update_config(config_id):
|
||||
def delete_config(config_id):
|
||||
"""删除配置"""
|
||||
try:
|
||||
log_info(f'[配置API] 删除配置,ID: {config_id}', 'api')
|
||||
execute_update('DELETE FROM t_daily_report_config WHERE id = %s', (config_id,))
|
||||
# 同时删除对应的求和数据
|
||||
try:
|
||||
execute_update('DELETE FROM t_daily_report_sum_data WHERE config_id = %s', (config_id,))
|
||||
log_info(f'[配置API] 已清理配置对应的求和数据', 'api')
|
||||
except Exception as e:
|
||||
log_error(f'[配置API] 清理求和数据失败: {e}', 'api')
|
||||
log_info(f'[配置API] 配置删除成功,ID: {config_id}', 'api')
|
||||
return jsonify({'success': True, 'message': '配置删除成功'})
|
||||
except Exception as e:
|
||||
log_error(f'[配置API] 删除配置失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -236,15 +285,13 @@ def move_config(config_id):
|
||||
"""移动配置排序"""
|
||||
try:
|
||||
direction = request.json.get('direction') # up 或 down
|
||||
print(f"[move_config] 开始移动 config_id={config_id}, direction={direction}")
|
||||
log_info(f"[配置API] 移动配置,ID: {config_id}, 方向: {direction}", 'api')
|
||||
|
||||
# 查出所有配置,按 sort_order, id 排序
|
||||
all_configs = execute_query(
|
||||
'SELECT id, sort_order FROM t_daily_report_config ORDER BY sort_order ASC, id ASC'
|
||||
)
|
||||
print(f"[move_config] 所有配置数量: {len(all_configs)}")
|
||||
for idx, c in enumerate(all_configs):
|
||||
print(f" [{idx}] id={c['id']}, sort_order={c['sort_order']}")
|
||||
log_info(f"[配置API] 所有配置数量: {len(all_configs)}", 'api')
|
||||
|
||||
# 找到当前配置的索引
|
||||
current_index = None
|
||||
@@ -254,10 +301,10 @@ def move_config(config_id):
|
||||
break
|
||||
|
||||
if current_index is None:
|
||||
print(f"[move_config] 未找到配置 id={config_id}")
|
||||
log_warning(f"[配置API] 未找到配置,ID: {config_id}", 'api')
|
||||
return jsonify({'success': False, 'message': '配置不存在'}), 404
|
||||
|
||||
print(f"[move_config] 当前配置索引: {current_index}")
|
||||
log_info(f"[配置API] 当前配置索引: {current_index}", 'api')
|
||||
|
||||
# 计算目标索引
|
||||
if direction == 'up':
|
||||
@@ -269,29 +316,24 @@ def move_config(config_id):
|
||||
|
||||
# 边界检查
|
||||
if target_index < 0 or target_index >= len(all_configs):
|
||||
print(f"[move_config] 已到边界,无法移动 target_index={target_index}")
|
||||
log_info(f"[配置API] 已到边界,无法移动", 'api')
|
||||
return jsonify({'success': True, 'message': '已到边界'})
|
||||
|
||||
# 在内存数组中交换位置
|
||||
all_configs[current_index], all_configs[target_index] = all_configs[target_index], all_configs[current_index]
|
||||
|
||||
print(f"[move_config] 交换后顺序: {[c['id'] for c in all_configs]}")
|
||||
|
||||
# 全量重排:按新顺序给每个配置重新赋值 sort_order
|
||||
for idx, cfg in enumerate(all_configs):
|
||||
new_sort = idx + 1
|
||||
print(f" 更新 id={cfg['id']}: sort_order {cfg['sort_order']} -> {new_sort}")
|
||||
execute_update(
|
||||
'UPDATE t_daily_report_config SET sort_order = %s WHERE id = %s',
|
||||
(new_sort, cfg['id'])
|
||||
)
|
||||
|
||||
print("[move_config] 重排完成")
|
||||
log_info(f"[配置API] 配置移动成功,ID: {config_id}", 'api')
|
||||
return jsonify({'success': True, 'message': '移动成功'})
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print(f"[move_config] 异常: {e}")
|
||||
log_error(f"[配置API] 移动配置失败: {e}", 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from datetime import datetime
|
||||
from flask import request, jsonify, send_from_directory
|
||||
from lib.db import execute_query, execute_update
|
||||
from lib.report_generator import generate_daily_report
|
||||
from lib.logger import log_info, log_error, log_warning
|
||||
from . import report_bp
|
||||
|
||||
|
||||
@@ -16,8 +17,10 @@ def generate_report():
|
||||
try:
|
||||
data = request.json
|
||||
config_id = data.get('config_id')
|
||||
log_info(f'[报表API] 生成日报,配置ID: {config_id}', 'api')
|
||||
|
||||
if not config_id:
|
||||
log_warning('[报表API] 生成日报失败:未选择配置', 'api')
|
||||
return jsonify({'success': False, 'message': '请选择配置'}), 400
|
||||
|
||||
# 解析时间参数
|
||||
@@ -32,10 +35,13 @@ def generate_report():
|
||||
result = generate_daily_report(config_id, start_time, end_time)
|
||||
|
||||
if result['success']:
|
||||
log_info(f'[报表API] 日报生成成功,订单数: {result.get("total_orders", 0)}', 'api')
|
||||
return jsonify(result)
|
||||
else:
|
||||
log_warning(f'[报表API] 日报生成失败: {result.get("message", "")}', 'api')
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
log_error(f'[报表API] 生成日报异常: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -47,6 +53,8 @@ def get_report_history():
|
||||
page_size = int(request.args.get('page_size', 20))
|
||||
start_date = request.args.get('start_date')
|
||||
end_date = request.args.get('end_date')
|
||||
log_info(f'[报表API] 获取历史记录,第{page}页,日期范围: {start_date or "开始"} ~ {end_date or "结束"}', 'api')
|
||||
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
# 构建查询条件
|
||||
@@ -83,9 +91,7 @@ def get_report_history():
|
||||
if item['file_path'] is None:
|
||||
item['file_path'] = ''
|
||||
|
||||
# 调试日志
|
||||
print(f"[历史记录] ID: {item['id']}, status: {item['status']}, file_path: '{item['file_path']}'")
|
||||
|
||||
log_info(f'[报表API] 获取到 {len(history)} 条历史记录,共 {total} 条', 'api')
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
@@ -96,9 +102,7 @@ def get_report_history():
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[历史记录 API] 错误:{e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[报表API] 获取历史记录失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -106,6 +110,7 @@ def get_report_history():
|
||||
def delete_report_history(history_id):
|
||||
"""删除单个历史记录"""
|
||||
try:
|
||||
log_info(f'[报表API] 删除历史记录,ID: {history_id}', 'api')
|
||||
# 先查询文件路径
|
||||
result = execute_query(
|
||||
'SELECT file_path FROM t_daily_report_history WHERE id = %s',
|
||||
@@ -117,15 +122,15 @@ def delete_report_history(history_id):
|
||||
# 删除文件
|
||||
if file_path and os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'public', file_path.lstrip('/'))):
|
||||
os.remove(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'public', file_path.lstrip('/')))
|
||||
log_info(f'[报表API] 已删除报表文件: {file_path}', 'api')
|
||||
|
||||
# 删除数据库记录
|
||||
execute_update('DELETE FROM t_daily_report_history WHERE id = %s', (history_id,))
|
||||
|
||||
log_info(f'[报表API] 历史记录删除成功,ID: {history_id}', 'api')
|
||||
return jsonify({'success': True, 'message': '删除成功'})
|
||||
except Exception as e:
|
||||
print(f"[删除历史记录] 错误:{e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[报表API] 删除历史记录失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -135,8 +140,10 @@ def batch_delete_report_history():
|
||||
try:
|
||||
data = request.get_json()
|
||||
ids = data.get('ids', [])
|
||||
log_info(f'[报表API] 批量删除历史记录,数量: {len(ids)}', 'api')
|
||||
|
||||
if not ids:
|
||||
log_warning('[报表API] 批量删除失败:未选择记录', 'api')
|
||||
return jsonify({'success': False, 'message': '请选择要删除的记录'}), 400
|
||||
|
||||
# 查询文件路径
|
||||
@@ -146,10 +153,14 @@ def batch_delete_report_history():
|
||||
)
|
||||
|
||||
# 删除文件
|
||||
deleted_files = 0
|
||||
for result in results:
|
||||
file_path = result.get('file_path', '')
|
||||
if file_path and os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'public', file_path.lstrip('/'))):
|
||||
os.remove(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'public', file_path.lstrip('/')))
|
||||
deleted_files += 1
|
||||
|
||||
log_info(f'[报表API] 已删除 {deleted_files} 个报表文件', 'api')
|
||||
|
||||
# 删除数据库记录
|
||||
execute_update(
|
||||
@@ -157,11 +168,10 @@ def batch_delete_report_history():
|
||||
tuple(ids)
|
||||
)
|
||||
|
||||
log_info(f'[报表API] 批量删除成功,共 {len(ids)} 条记录', 'api')
|
||||
return jsonify({'success': True, 'message': f'成功删除 {len(ids)} 条记录'})
|
||||
except Exception as e:
|
||||
print(f"[批量删除历史记录] 错误:{e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[报表API] 批量删除历史记录失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -169,6 +179,7 @@ def batch_delete_report_history():
|
||||
def download_report_by_id(history_id):
|
||||
"""根据 ID 下载历史记录"""
|
||||
try:
|
||||
log_info(f'[报表API] 下载报表,历史记录ID: {history_id}', 'api')
|
||||
# 查询历史记录
|
||||
result = execute_query(
|
||||
'SELECT file_path, split_name, report_date FROM t_daily_report_history WHERE id = %s',
|
||||
@@ -176,24 +187,26 @@ def download_report_by_id(history_id):
|
||||
)
|
||||
|
||||
if not result:
|
||||
log_warning(f'[报表API] 下载报表失败:记录不存在,ID: {history_id}', 'api')
|
||||
return jsonify({'success': False, 'message': '记录不存在'}), 404
|
||||
|
||||
file_path = result[0]['file_path']
|
||||
if not file_path:
|
||||
log_warning(f'[报表API] 下载报表失败:文件路径为空,ID: {history_id}', 'api')
|
||||
return jsonify({'success': False, 'message': '文件不存在'}), 404
|
||||
|
||||
# 构建完整路径
|
||||
full_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'public', file_path.lstrip('/'))
|
||||
|
||||
if not os.path.exists(full_path):
|
||||
log_warning(f'[报表API] 下载报表失败:文件不存在,路径: {full_path}', 'api')
|
||||
return jsonify({'success': False, 'message': '文件不存在'}), 404
|
||||
|
||||
# 返回文件
|
||||
directory = os.path.dirname(full_path)
|
||||
filename = os.path.basename(full_path)
|
||||
log_info(f'[报表API] 报表下载成功: {filename}', 'api')
|
||||
return send_from_directory(directory, filename, as_attachment=True)
|
||||
except Exception as e:
|
||||
print(f"[下载历史记录] 错误:{e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[报表API] 下载报表失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
@@ -12,6 +12,7 @@ from lib.sum_data_collector import (
|
||||
get_monthly_charge_degree_total,
|
||||
export_sum_data_to_excel
|
||||
)
|
||||
from lib.logger import log_info, log_error, log_warning
|
||||
from . import sum_data_bp
|
||||
|
||||
|
||||
@@ -21,15 +22,17 @@ def collect_sum_data():
|
||||
try:
|
||||
data = request.json or {}
|
||||
history_id = data.get('history_id')
|
||||
log_info(f'[求和数据API] 采集求和数据,历史记录ID: {history_id or "全部"}', 'api')
|
||||
|
||||
result = collect_sum_data_from_history(history_id)
|
||||
if result['success']:
|
||||
log_info(f'[求和数据API] 采集成功: {result["message"]}', 'api')
|
||||
return jsonify(result)
|
||||
else:
|
||||
log_warning(f'[求和数据API] 采集失败: {result["message"]}', 'api')
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[求和数据API] 采集异常: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -45,6 +48,7 @@ def list_sum_data():
|
||||
config_name = request.args.get('config_name')
|
||||
sum_field_key = request.args.get('sum_field_key')
|
||||
data_source = request.args.get('data_source')
|
||||
log_info(f'[求和数据API] 查询求和数据列表,第{page}页', 'api')
|
||||
|
||||
result = get_sum_data_list(
|
||||
page=page,
|
||||
@@ -58,12 +62,12 @@ def list_sum_data():
|
||||
)
|
||||
|
||||
if result['success']:
|
||||
log_info(f'[求和数据API] 查询成功,共{result["data"]["total"]}条记录', 'api')
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[求和数据API] 查询求和数据列表失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -72,14 +76,15 @@ def create_sum_data():
|
||||
"""手动添加求和数据"""
|
||||
try:
|
||||
data = request.json
|
||||
log_info('[求和数据API] 手动添加求和数据', 'api')
|
||||
result = add_sum_data(data)
|
||||
if result['success']:
|
||||
log_info(f'[求和数据API] 添加成功,ID: {result.get("id", "")}', 'api')
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[求和数据API] 添加求和数据失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -88,14 +93,15 @@ def update_sum_data_api(sum_data_id):
|
||||
"""更新求和数据"""
|
||||
try:
|
||||
data = request.json
|
||||
log_info(f'[求和数据API] 更新求和数据,ID: {sum_data_id}', 'api')
|
||||
result = update_sum_data(sum_data_id, data)
|
||||
if result['success']:
|
||||
log_info(f'[求和数据API] 更新成功,ID: {sum_data_id}', 'api')
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[求和数据API] 更新求和数据失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -103,14 +109,15 @@ def update_sum_data_api(sum_data_id):
|
||||
def delete_sum_data_api(sum_data_id):
|
||||
"""删除求和数据"""
|
||||
try:
|
||||
log_info(f'[求和数据API] 删除求和数据,ID: {sum_data_id}', 'api')
|
||||
result = delete_sum_data(sum_data_id)
|
||||
if result['success']:
|
||||
log_info(f'[求和数据API] 删除成功,ID: {sum_data_id}', 'api')
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[求和数据API] 删除求和数据失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -120,17 +127,19 @@ def batch_delete_sum_data_api():
|
||||
try:
|
||||
data = request.json
|
||||
ids = data.get('ids', [])
|
||||
log_info(f'[求和数据API] 批量删除求和数据,数量: {len(ids)}', 'api')
|
||||
if not ids:
|
||||
log_warning('[求和数据API] 批量删除失败:未选择记录', 'api')
|
||||
return jsonify({'success': False, 'message': '请选择要删除的记录'}), 400
|
||||
|
||||
result = batch_delete_sum_data(ids)
|
||||
if result['success']:
|
||||
log_info(f'[求和数据API] 批量删除成功,{result["message"]}', 'api')
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[求和数据API] 批量删除求和数据失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -145,15 +154,16 @@ def monthly_charge_degree_total():
|
||||
try:
|
||||
config_id = request.args.get('config_id')
|
||||
month = request.args.get('month')
|
||||
log_info(f'[求和数据API] 查询月度累计,配置ID: {config_id or "全部"},月份: {month or "当月"}', 'api')
|
||||
|
||||
result = get_monthly_charge_degree_total(config_id, month)
|
||||
if result['success']:
|
||||
log_info(f'[求和数据API] 查询成功,累计电量: {result["data"]["total_degree"]} kWh', 'api')
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[求和数据API] 查询月度累计失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@@ -174,6 +184,7 @@ def export_sum_data():
|
||||
config_id = request.args.get('config_id')
|
||||
data_source = request.args.get('data_source')
|
||||
keyword = request.args.get('keyword')
|
||||
log_info(f'[求和数据API] 导出求和数据,日期范围: {start_date or "开始"} ~ {end_date or "结束"}', 'api')
|
||||
|
||||
result = export_sum_data_to_excel(
|
||||
start_date=start_date,
|
||||
@@ -184,10 +195,10 @@ def export_sum_data():
|
||||
)
|
||||
|
||||
if result['success']:
|
||||
log_info(f'[求和数据API] 导出成功,共{result.get("total", 0)}条记录', 'api')
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[求和数据API] 导出求和数据失败: {e}', 'api')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
29
lib/db.py
29
lib/db.py
@@ -3,6 +3,7 @@
|
||||
"""
|
||||
import pymysql
|
||||
from contextlib import contextmanager
|
||||
from lib.logger import log_info, log_error, log_warning
|
||||
|
||||
# 数据库配置
|
||||
DB_CONFIG = {
|
||||
@@ -23,32 +24,52 @@ DB_CONFIG = {
|
||||
@contextmanager
|
||||
def get_connection():
|
||||
"""获取数据库连接(上下文管理器)"""
|
||||
conn = pymysql.connect(**DB_CONFIG)
|
||||
try:
|
||||
conn = pymysql.connect(**DB_CONFIG)
|
||||
yield conn
|
||||
except pymysql.Error as e:
|
||||
log_error(f'数据库连接失败: {e}', 'db')
|
||||
raise
|
||||
finally:
|
||||
try:
|
||||
conn.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def execute_query(sql, params=None):
|
||||
"""执行查询并返回结果"""
|
||||
try:
|
||||
with get_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute(sql, params)
|
||||
return cursor.fetchall()
|
||||
result = cursor.fetchall()
|
||||
return result
|
||||
except Exception as e:
|
||||
log_error(f'查询失败: {e}\nSQL: {sql}\nParams: {params}', 'db')
|
||||
raise
|
||||
|
||||
|
||||
def execute_update(sql, params=None):
|
||||
"""执行更新操作"""
|
||||
try:
|
||||
with get_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute(sql, params)
|
||||
affected = cursor.execute(sql, params)
|
||||
return cursor.rowcount
|
||||
except Exception as e:
|
||||
log_error(f'更新失败: {e}\nSQL: {sql}\nParams: {params}', 'db')
|
||||
raise
|
||||
|
||||
|
||||
def execute_insert(sql, params=None):
|
||||
"""执行插入操作并返回插入ID"""
|
||||
try:
|
||||
with get_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute(sql, params)
|
||||
return cursor.lastrowid
|
||||
last_id = cursor.lastrowid
|
||||
return last_id
|
||||
except Exception as e:
|
||||
log_error(f'插入失败: {e}\nSQL: {sql}\nParams: {params}', 'db')
|
||||
raise
|
||||
110
lib/db_init.py
110
lib/db_init.py
@@ -2,6 +2,36 @@
|
||||
数据库初始化
|
||||
"""
|
||||
from lib.db import execute_update, execute_query
|
||||
from lib.logger import log_info, log_error, log_warning
|
||||
|
||||
|
||||
def _column_exists(table_name, column_name):
|
||||
"""检查字段是否存在(通过 information_schema 查询)"""
|
||||
try:
|
||||
result = execute_query("""
|
||||
SELECT COUNT(*) as cnt
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = %s
|
||||
AND column_name = %s
|
||||
""", (table_name, column_name))
|
||||
return result and result[0]['cnt'] > 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _add_column_if_not_exists(table_name, column_def_sql, column_name):
|
||||
"""安全地添加字段:先检查是否存在,不存在再添加"""
|
||||
if _column_exists(table_name, column_name):
|
||||
log_info(f"[数据库] 字段 {table_name}.{column_name} 已存在,跳过", 'db_init')
|
||||
return True
|
||||
try:
|
||||
execute_update(f"ALTER TABLE {table_name} ADD COLUMN {column_def_sql}")
|
||||
log_info(f"[数据库] 新增字段 {table_name}.{column_name} 成功", 'db_init')
|
||||
return True
|
||||
except Exception as e:
|
||||
log_warning(f"[数据库] 新增字段 {table_name}.{column_name} 失败: {e}", 'db_init')
|
||||
return False
|
||||
|
||||
|
||||
def init_database_tables():
|
||||
@@ -34,7 +64,7 @@ def init_database_tables():
|
||||
PROPERTIES("replication_num" = "1")
|
||||
"""
|
||||
execute_update(create_config_table)
|
||||
print("[数据库] t_daily_report_config 表已就绪")
|
||||
log_info("[数据库] t_daily_report_config 表已就绪", 'db_init')
|
||||
|
||||
# 创建历史表
|
||||
create_history_table = """
|
||||
@@ -60,16 +90,14 @@ def init_database_tables():
|
||||
PROPERTIES("replication_num" = "1")
|
||||
"""
|
||||
execute_update(create_history_table)
|
||||
print("[数据库] t_daily_report_history 表已就绪")
|
||||
log_info("[数据库] t_daily_report_history 表已就绪", 'db_init')
|
||||
|
||||
# 为已存在的表添加 config_name 字段(如果不存在)
|
||||
try:
|
||||
execute_update("""
|
||||
ALTER TABLE t_daily_report_history
|
||||
ADD COLUMN IF NOT EXISTS config_name VARCHAR(200) NULL COMMENT '配置名称'
|
||||
""")
|
||||
except Exception as e:
|
||||
print(f"[数据库] 添加 config_name 字段时出错(可能已存在): {e}")
|
||||
_add_column_if_not_exists(
|
||||
't_daily_report_history',
|
||||
"config_name VARCHAR(200) NULL COMMENT '配置名称'",
|
||||
'config_name'
|
||||
)
|
||||
|
||||
# 创建求和数据表
|
||||
create_sum_data_table = """
|
||||
@@ -96,56 +124,35 @@ def init_database_tables():
|
||||
PROPERTIES("replication_num" = "1")
|
||||
"""
|
||||
execute_update(create_sum_data_table)
|
||||
print("[数据库] t_daily_report_sum_data 表已就绪")
|
||||
log_info("[数据库] t_daily_report_sum_data 表已就绪", 'db_init')
|
||||
|
||||
# 给配置表增加 show_monthly_total 字段
|
||||
try:
|
||||
execute_update(
|
||||
"ALTER TABLE t_daily_report_config ADD COLUMN show_monthly_total TINYINT NULL COMMENT '是否显示当月充电量总计' "
|
||||
_add_column_if_not_exists(
|
||||
't_daily_report_config',
|
||||
"show_monthly_total TINYINT NULL COMMENT '是否显示当月充电量总计'",
|
||||
'show_monthly_total'
|
||||
)
|
||||
print("[数据库] 配置表新增 show_monthly_total 字段成功")
|
||||
except Exception as e:
|
||||
# 字段已存在会报错,忽略
|
||||
if "Duplicate" in str(e) or "duplicate" in str(e) or "Exists" in str(e) or "exists" in str(e):
|
||||
pass
|
||||
else:
|
||||
print(f"[数据库] 新增 show_monthly_total 字段时出错(可能已存在): {e}")
|
||||
|
||||
# 给求和数据表增加 sort_order 字段
|
||||
try:
|
||||
execute_update(
|
||||
"ALTER TABLE t_daily_report_sum_data ADD COLUMN sort_order INT NULL COMMENT '配置排序号' "
|
||||
_add_column_if_not_exists(
|
||||
't_daily_report_sum_data',
|
||||
"sort_order INT NULL COMMENT '配置排序号'",
|
||||
'sort_order'
|
||||
)
|
||||
print("[数据库] 求和数据表新增 sort_order 字段成功")
|
||||
except Exception as e:
|
||||
if "Duplicate" in str(e) or "duplicate" in str(e) or "Exists" in str(e) or "exists" in str(e):
|
||||
pass
|
||||
else:
|
||||
print(f"[数据库] 新增 sort_order 字段时出错(可能已存在): {e}")
|
||||
|
||||
# 给配置表增加 custom_service_fee_price 字段(自定义服务费单价)
|
||||
try:
|
||||
execute_update(
|
||||
"ALTER TABLE t_daily_report_config ADD COLUMN custom_service_fee_price DECIMAL(10,4) NULL COMMENT '自定义服务费单价(元/kWh)' "
|
||||
_add_column_if_not_exists(
|
||||
't_daily_report_config',
|
||||
"custom_service_fee_price DECIMAL(10,4) NULL COMMENT '自定义服务费单价(元/kWh)'",
|
||||
'custom_service_fee_price'
|
||||
)
|
||||
print("[数据库] 配置表新增 custom_service_fee_price 字段成功")
|
||||
except Exception as e:
|
||||
if "Duplicate" in str(e) or "duplicate" in str(e) or "Exists" in str(e) or "exists" in str(e):
|
||||
pass
|
||||
else:
|
||||
print(f"[数据库] 新增 custom_service_fee_price 字段时出错(可能已存在): {e}")
|
||||
|
||||
# 给配置表增加 custom_service_fee_name 字段(自定义服务费表头名称)
|
||||
try:
|
||||
execute_update(
|
||||
"ALTER TABLE t_daily_report_config ADD COLUMN custom_service_fee_name VARCHAR(100) NULL COMMENT '自定义服务费报表表头名称' "
|
||||
_add_column_if_not_exists(
|
||||
't_daily_report_config',
|
||||
"custom_service_fee_name VARCHAR(100) NULL COMMENT '自定义服务费报表表头名称'",
|
||||
'custom_service_fee_name'
|
||||
)
|
||||
print("[数据库] 配置表新增 custom_service_fee_name 字段成功")
|
||||
except Exception as e:
|
||||
if "Duplicate" in str(e) or "duplicate" in str(e) or "Exists" in str(e) or "exists" in str(e):
|
||||
pass
|
||||
else:
|
||||
print(f"[数据库] 新增 custom_service_fee_name 字段时出错(可能已存在): {e}")
|
||||
|
||||
# 数据迁移:给 sort_order 为 NULL 的配置记录按 id 顺序赋值
|
||||
try:
|
||||
@@ -153,7 +160,7 @@ def init_database_tables():
|
||||
"SELECT COUNT(*) as cnt FROM t_daily_report_config WHERE sort_order IS NULL"
|
||||
)
|
||||
if null_count and null_count[0]['cnt'] > 0:
|
||||
print(f"[数据库] 检测到 {null_count[0]['cnt']} 条配置 sort_order 为空,开始初始化排序...")
|
||||
log_info(f"[数据库] 检测到 {null_count[0]['cnt']} 条配置 sort_order 为空,开始初始化排序...", 'db_init')
|
||||
all_configs = execute_query(
|
||||
"SELECT id FROM t_daily_report_config ORDER BY id ASC"
|
||||
)
|
||||
@@ -162,11 +169,12 @@ def init_database_tables():
|
||||
"UPDATE t_daily_report_config SET sort_order = %s WHERE id = %s",
|
||||
(idx + 1, cfg['id'])
|
||||
)
|
||||
print("[数据库] 配置 sort_order 初始化完成")
|
||||
log_info("[数据库] 配置 sort_order 初始化完成", 'db_init')
|
||||
except Exception as e:
|
||||
print(f"[数据库] 初始化 sort_order 时出错: {e}")
|
||||
log_warning(f"[数据库] 初始化 sort_order 时出错: {e}", 'db_init')
|
||||
|
||||
log_info("[数据库] 初始化完成", 'db_init')
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[数据库] 初始化失败: {e}")
|
||||
log_error(f"[数据库] 初始化失败: {e}", 'db_init')
|
||||
return False
|
||||
|
||||
85
lib/logger.py
Normal file
85
lib/logger.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import os
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from datetime import datetime
|
||||
|
||||
_loggers = {}
|
||||
|
||||
|
||||
def get_logger(name='app'):
|
||||
"""
|
||||
获取日志记录器
|
||||
|
||||
Args:
|
||||
name: 日志名称(对应日志文件名)
|
||||
|
||||
Returns:
|
||||
logging.Logger: 日志记录器
|
||||
"""
|
||||
if name in _loggers:
|
||||
return _loggers[name]
|
||||
|
||||
# 日志目录
|
||||
log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'logs')
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
# 日志文件路径
|
||||
log_file = os.path.join(log_dir, f'{name}.log')
|
||||
|
||||
# 创建 logger
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.INFO)
|
||||
logger.propagate = False
|
||||
|
||||
# 避免重复添加 handler
|
||||
if logger.handlers:
|
||||
_loggers[name] = logger
|
||||
return logger
|
||||
|
||||
# 日志格式
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s | %(levelname)-7s | %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# 文件 handler(按大小轮转,最多 5 个备份,每个 10MB)
|
||||
file_handler = RotatingFileHandler(
|
||||
log_file,
|
||||
maxBytes=10 * 1024 * 1024,
|
||||
backupCount=5,
|
||||
encoding='utf-8'
|
||||
)
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# 控制台 handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.INFO)
|
||||
console_handler.setFormatter(formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
_loggers[name] = logger
|
||||
return logger
|
||||
|
||||
|
||||
def log_info(message, module='app'):
|
||||
"""记录信息日志"""
|
||||
get_logger(module).info(message)
|
||||
|
||||
|
||||
def log_error(message, module='app'):
|
||||
"""记录错误日志"""
|
||||
get_logger(module).error(message)
|
||||
import traceback
|
||||
get_logger(module).error(traceback.format_exc())
|
||||
|
||||
|
||||
def log_warning(message, module='app'):
|
||||
"""记录警告日志"""
|
||||
get_logger(module).warning(message)
|
||||
|
||||
|
||||
def log_debug(message, module='app'):
|
||||
"""记录调试日志"""
|
||||
get_logger(module).debug(message)
|
||||
@@ -10,6 +10,7 @@ import pytz
|
||||
|
||||
from lib.db import execute_query, execute_insert
|
||||
from lib.field_mapping import get_field_display_name
|
||||
from lib.logger import log_info, log_error, log_warning
|
||||
|
||||
|
||||
# ID字段到名称的映射配置
|
||||
@@ -102,9 +103,9 @@ def query_telecom_orders(vehicle_self_nos, start_time_str, end_time_str):
|
||||
|
||||
try:
|
||||
results = execute_query(sql, params)
|
||||
print(f"[特来电] 查询到 {len(results)} 条订单")
|
||||
log_info(f"[特来电] 查询到 {len(results)} 条订单", 'report')
|
||||
except Exception as e:
|
||||
print(f"[特来电] 查询失败: {e}")
|
||||
log_error(f"[特来电] 查询失败: {e}", 'report')
|
||||
return []
|
||||
|
||||
# 转换字段名
|
||||
@@ -165,7 +166,7 @@ def build_id_to_name_map(orders, selected_fields):
|
||||
# 合并特来电的名称映射
|
||||
if telecom_name_values:
|
||||
id_name_maps[field] = telecom_name_values
|
||||
print(f"[ID转换] {field}: 特来电数据直接使用名称 {len(telecom_name_values)} 个")
|
||||
log_info(f"[ID转换] {field}: 特来电数据直接使用名称 {len(telecom_name_values)} 个", 'report')
|
||||
|
||||
# 查询驿来特数据的ID映射
|
||||
if not id_values:
|
||||
@@ -180,9 +181,9 @@ def build_id_to_name_map(orders, selected_fields):
|
||||
if field not in id_name_maps:
|
||||
id_name_maps[field] = {}
|
||||
id_name_maps[field].update({str(row['id_val']): row['name_val'] for row in results})
|
||||
print(f"[ID转换] {field}: 查询到 {len(results)} 个名称映射")
|
||||
log_info(f"[ID转换] {field}: 查询到 {len(results)} 个名称映射", 'report')
|
||||
except Exception as e:
|
||||
print(f"[ID转换] {field} 查询失败: {e}")
|
||||
log_error(f"[ID转换] {field} 查询失败: {e}", 'report')
|
||||
if field not in id_name_maps:
|
||||
id_name_maps[field] = {}
|
||||
|
||||
@@ -231,9 +232,9 @@ def calculate_time_period_electricity(orders, time_periods_config):
|
||||
|
||||
try:
|
||||
details = execute_query(sql, tuple(order_nos))
|
||||
print(f"[时段电量] 查询到 {len(details)} 条分时记录")
|
||||
log_info(f"[时段电量] 查询到 {len(details)} 条分时记录", 'report')
|
||||
except Exception as e:
|
||||
print(f"[时段电量] 查询失败: {e}")
|
||||
log_error(f"[时段电量] 查询失败: {e}", 'report')
|
||||
return {}
|
||||
|
||||
# 按订单号分组计算各时段电量
|
||||
@@ -320,12 +321,12 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
start_time_str = start_time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
end_time_str = end_time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
print(f"[日报生成] 开始生成日报")
|
||||
print(f" 配置ID: {config_id}")
|
||||
print(f" 配置名称: {config['config_name']}")
|
||||
print(f" 拆分方式: {config['split_type']} = {config['split_value']}")
|
||||
print(f" 时间范围: {start_time_str} 至 {end_time_str}")
|
||||
print(f" 选择字段: {len(selected_fields)} 个")
|
||||
log_info(f"[日报生成] 开始生成日报", 'report')
|
||||
log_info(f" 配置ID: {config_id}", 'report')
|
||||
log_info(f" 配置名称: {config['config_name']}", 'report')
|
||||
log_info(f" 拆分方式: {config['split_type']} = {config['split_value']}", 'report')
|
||||
log_info(f" 时间范围: {start_time_str} 至 {end_time_str}", 'report')
|
||||
log_info(f" 选择字段: {len(selected_fields)} 个", 'report')
|
||||
|
||||
# 构建查询SQL - 过滤掉虚拟字段(时段电量等)
|
||||
# 虚拟字段不是数据库表中的实际字段,需要动态计算
|
||||
@@ -370,7 +371,7 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
# 执行查询
|
||||
orders = execute_query(sql, tuple(params))
|
||||
|
||||
print(f"[日报生成] 驿来特查询到 {len(orders)} 条订单")
|
||||
log_info(f"[日报生成] 驿来特查询到 {len(orders)} 条订单", 'report')
|
||||
|
||||
# 添加数据来源标记
|
||||
for order in orders:
|
||||
@@ -379,7 +380,7 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
# 检查是否有补单记录(finish_type = 2 表示补单结束)
|
||||
has_supplement = any(order.get('finish_type') == 2 for order in orders)
|
||||
if has_supplement:
|
||||
print(f"[日报生成] 检测到补单记录,将在文件名中添加标记")
|
||||
log_info(f"[日报生成] 检测到补单记录,将在文件名中添加标记", 'report')
|
||||
|
||||
# 检查是否需要合并特来电数据
|
||||
merge_telecom = config.get('merge_telecom', 0)
|
||||
@@ -389,12 +390,12 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
# 解析车量自编号(支持逗号分隔的多个)
|
||||
vehicle_nos = [v.strip() for v in telecom_vehicle_no.split(',') if v.strip()]
|
||||
if vehicle_nos:
|
||||
print(f"[日报生成] 开始合并特来电数据,车量自编号: {vehicle_nos}")
|
||||
log_info(f"[日报生成] 开始合并特来电数据,车量自编号: {vehicle_nos}", 'report')
|
||||
telecom_orders = query_telecom_orders(vehicle_nos, start_time_str, end_time_str)
|
||||
if telecom_orders:
|
||||
# 将特来电数据添加到订单列表
|
||||
orders.extend(telecom_orders)
|
||||
print(f"[日报生成] 合并后总订单数: {len(orders)}")
|
||||
log_info(f"[日报生成] 合并后总订单数: {len(orders)}", 'report')
|
||||
# 检查特来电数据中是否有补单记录
|
||||
if not has_supplement:
|
||||
has_supplement = any(order.get('finish_type') == 2 for order in telecom_orders)
|
||||
@@ -424,7 +425,7 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
# 解析时段配置并计算分时段电量
|
||||
time_periods_config = json.loads(config['time_periods']) if config.get('time_periods') else {}
|
||||
if time_periods_config and any(time_periods_config.values()):
|
||||
print(f"[日报生成] 时段配置: {time_periods_config}")
|
||||
log_info(f"[日报生成] 时段配置: {time_periods_config}", 'report')
|
||||
time_period_electricity = calculate_time_period_electricity(orders, time_periods_config)
|
||||
|
||||
# 将分时段电量添加到订单数据中(仅对驿来特订单,特来电订单已有数据)
|
||||
@@ -441,7 +442,7 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
order['flat_electricity'] = ep['flat_electricity']
|
||||
order['valley_electricity'] = ep['valley_electricity']
|
||||
|
||||
print(f"[日报生成] 已计算 {len(time_period_electricity)} 条订单的分时段电量")
|
||||
log_info(f"[日报生成] 已计算 {len(time_period_electricity)} 条订单的分时段电量", 'report')
|
||||
|
||||
# 计算求和(保留三位小数)
|
||||
sum_results = {}
|
||||
@@ -454,7 +455,7 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
if 'charge_degree' not in sum_results and orders and 'charge_degree' in orders[0]:
|
||||
total_charge_degree = sum(float(order.get('charge_degree', 0) or 0) for order in orders)
|
||||
sum_results['charge_degree'] = round(total_charge_degree, 3)
|
||||
print(f"[日报生成] 强制采集充电电量: {sum_results['charge_degree']} kWh")
|
||||
log_info(f"[日报生成] 强制采集充电电量: {sum_results['charge_degree']} kWh", 'report')
|
||||
|
||||
# 构建ID到名称的映射
|
||||
id_name_maps = build_id_to_name_map(orders, selected_fields)
|
||||
@@ -476,17 +477,19 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
config_id=config['id'],
|
||||
report_date=start_time.strftime('%Y-%m-%d'),
|
||||
sort_order=config.get('sort_order'),
|
||||
split_type=config.get('split_type'),
|
||||
split_value=config.get('split_value'),
|
||||
custom_service_fee_price=config.get('custom_service_fee_price'),
|
||||
custom_service_fee_name=config.get('custom_service_fee_name')
|
||||
)
|
||||
|
||||
print(f"[日报生成] Excel文件已生成: {file_path}")
|
||||
log_info(f"[日报生成] Excel文件已生成: {file_path}", 'report')
|
||||
|
||||
# 计算总金额
|
||||
total_amount = sum(float(order.get('total_money', 0) or 0) for order in orders)
|
||||
|
||||
# 保存历史记录
|
||||
print(f"[日报生成] 准备保存历史记录,file_path={file_path}")
|
||||
log_info(f"[日报生成] 准备保存历史记录,file_path={file_path}", 'report')
|
||||
history_id = save_report_history(
|
||||
report_date=start_time.strftime('%Y-%m-%d'),
|
||||
start_time=start_time_str,
|
||||
@@ -502,7 +505,7 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
file_path=file_path,
|
||||
status=1
|
||||
)
|
||||
print(f"[日报生成] 历史记录已保存,ID={history_id}")
|
||||
log_info(f"[日报生成] 历史记录已保存,ID={history_id}", 'report')
|
||||
|
||||
# 自动采集求和数据到求和数据表
|
||||
try:
|
||||
@@ -510,17 +513,17 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
from lib.sum_data_collector import collect_sum_data_from_history
|
||||
collect_result = collect_sum_data_from_history(history_id)
|
||||
if collect_result['success']:
|
||||
print(f"[日报生成] 求和数据采集成功")
|
||||
log_info(f"[日报生成] 求和数据采集成功", 'report')
|
||||
else:
|
||||
print(f"[日报生成] 求和数据采集失败: {collect_result['message']}")
|
||||
log_warning(f"[日报生成] 求和数据采集失败: {collect_result['message']}", 'report')
|
||||
except Exception as collect_error:
|
||||
print(f"[日报生成] 求和数据采集异常: {str(collect_error)}")
|
||||
log_error(f"[日报生成] 求和数据采集异常: {str(collect_error)}", 'report')
|
||||
|
||||
print(f"[日报生成] ✓ 生成成功")
|
||||
print(f" 订单数量: {len(orders)}")
|
||||
print(f" 总金额: {total_amount}")
|
||||
print(f" 文件路径: {file_path}")
|
||||
print(f" 历史记录ID: {history_id}")
|
||||
log_info(f"[日报生成] ✓ 生成成功", 'report')
|
||||
log_info(f" 订单数量: {len(orders)}", 'report')
|
||||
log_info(f" 总金额: {total_amount}", 'report')
|
||||
log_info(f" 文件路径: {file_path}", 'report')
|
||||
log_info(f" 历史记录ID: {history_id}", 'report')
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
@@ -533,9 +536,7 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"[日报生成] ✗ 生成失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f"[日报生成] ✗ 生成失败: {str(e)}", 'report')
|
||||
|
||||
# 保存失败记录
|
||||
try:
|
||||
@@ -554,9 +555,9 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
file_path='',
|
||||
status=0
|
||||
)
|
||||
print(f"[日报生成] 已保存失败记录到历史表")
|
||||
log_info(f"[日报生成] 已保存失败记录到历史表", 'report')
|
||||
except Exception as save_error:
|
||||
print(f"[日报生成] 保存失败记录时出错: {str(save_error)}")
|
||||
log_error(f"[日报生成] 保存失败记录时出错: {str(save_error)}", 'report')
|
||||
|
||||
return {
|
||||
'success': False,
|
||||
@@ -568,6 +569,7 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
|
||||
config_name, split_name, start_time, end_time,
|
||||
id_name_maps=None, field_custom_names=None, has_supplement=False,
|
||||
show_monthly_total=False, config_id=None, report_date=None, sort_order=None,
|
||||
split_type=None, split_value=None,
|
||||
custom_service_fee_price=None, custom_service_fee_name=None):
|
||||
"""
|
||||
生成Excel文件
|
||||
@@ -615,7 +617,14 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
|
||||
|
||||
# 表头从第1行开始
|
||||
header_row = 1
|
||||
field_custom_names = field_custom_names or {}
|
||||
if field_custom_names:
|
||||
if isinstance(field_custom_names, str):
|
||||
try:
|
||||
field_custom_names = json.loads(field_custom_names)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
field_custom_names = {}
|
||||
else:
|
||||
field_custom_names = {}
|
||||
|
||||
# 导入边框样式
|
||||
from openpyxl.styles import Border, Side
|
||||
@@ -820,7 +829,12 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
|
||||
try:
|
||||
from lib.sum_data_collector import get_monthly_charge_degree_total
|
||||
month = report_date[:7]
|
||||
monthly_result = get_monthly_charge_degree_total(config_id, month)
|
||||
monthly_result = get_monthly_charge_degree_total(
|
||||
config_id=config_id,
|
||||
month=month,
|
||||
split_type=split_type,
|
||||
split_value=split_value
|
||||
)
|
||||
|
||||
if monthly_result['success'] and monthly_result['data']:
|
||||
total_degree = monthly_result['data']['total_degree']
|
||||
@@ -868,7 +882,7 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
|
||||
|
||||
ws.row_dimensions[monthly_row].height = 25
|
||||
except Exception as e:
|
||||
print(f"[日报生成] 写入月度总计时出错: {e}")
|
||||
log_error(f"[日报生成] 写入月度总计时出错: {e}", 'report')
|
||||
|
||||
# 调整列宽(中文按2个字符宽度计算)
|
||||
# 序号列固定宽度
|
||||
@@ -929,10 +943,10 @@ def save_report_history(report_date, start_time, end_time, split_type, split_val
|
||||
# 生成ID
|
||||
history_id = int(datetime.now().timestamp() * 1000)
|
||||
|
||||
print(f"[保存历史] 准备保存历史记录")
|
||||
print(f" ID: {history_id}")
|
||||
print(f" file_path: '{file_path}'")
|
||||
print(f" status: {status}")
|
||||
log_info(f"[保存历史] 准备保存历史记录", 'report')
|
||||
log_info(f" ID: {history_id}", 'report')
|
||||
log_info(f" file_path: '{file_path}'", 'report')
|
||||
log_info(f" status: {status}", 'report')
|
||||
|
||||
sql = """
|
||||
INSERT INTO t_daily_report_history
|
||||
@@ -958,8 +972,8 @@ def save_report_history(report_date, start_time, end_time, split_type, split_val
|
||||
status
|
||||
)
|
||||
|
||||
print(f"[保存历史] 执行SQL插入...")
|
||||
log_info(f"[保存历史] 执行SQL插入...", 'report')
|
||||
result = execute_insert(sql, params)
|
||||
print(f"[保存历史] 插入完成,返回ID: {result}")
|
||||
log_info(f"[保存历史] 插入完成,返回ID: {result}", 'report')
|
||||
|
||||
return history_id
|
||||
@@ -6,6 +6,7 @@ import os
|
||||
from datetime import datetime
|
||||
from lib.db import execute_query, execute_update, execute_insert
|
||||
from lib.field_mapping import get_field_display_name
|
||||
from lib.logger import log_info, log_error, log_warning
|
||||
|
||||
|
||||
def collect_sum_data_from_history(history_id=None):
|
||||
@@ -85,9 +86,9 @@ def collect_sum_data_from_history(history_id=None):
|
||||
charge_degree = _calculate_charge_degree(history)
|
||||
if charge_degree is not None:
|
||||
sum_results['charge_degree'] = charge_degree
|
||||
print(f"[采集] 历史记录 {history['id']} 补充计算充电电量: {charge_degree} kWh")
|
||||
log_info(f"[采集] 历史记录 {history['id']} 补充计算充电电量: {charge_degree} kWh", 'sum_data')
|
||||
except Exception as calc_error:
|
||||
print(f"[采集] 历史记录 {history['id']} 计算充电电量失败: {calc_error}")
|
||||
log_error(f"[采集] 历史记录 {history['id']} 计算充电电量失败: {calc_error}", 'sum_data')
|
||||
|
||||
# 如果还是没有任何求和数据,跳过
|
||||
if not sum_results:
|
||||
@@ -167,15 +168,15 @@ def collect_sum_data_from_history(history_id=None):
|
||||
execute_insert(sql, params)
|
||||
collected_count += 1
|
||||
except Exception as fee_error:
|
||||
print(f"[采集] 计算自定义服务费失败: {fee_error}")
|
||||
log_error(f"[采集] 计算自定义服务费失败: {fee_error}", 'sum_data')
|
||||
|
||||
log_info(f'[采集] 采集完成,共采集 {collected_count} 条求和数据,跳过 {skipped_count} 条无求和数据的记录', 'sum_data')
|
||||
return {
|
||||
'success': True,
|
||||
'message': f'采集完成,共采集 {collected_count} 条求和数据,跳过 {skipped_count} 条无求和数据的记录'
|
||||
}
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'[采集] 采集失败: {e}', 'sum_data')
|
||||
return {'success': False, 'message': f'采集失败: {str(e)}'}
|
||||
|
||||
|
||||
@@ -229,7 +230,7 @@ def _calculate_charge_degree(history):
|
||||
|
||||
return 0.0
|
||||
except Exception as e:
|
||||
print(f"[计算充电电量] 失败: {e}")
|
||||
log_error(f"[计算充电电量] 失败: {e}", 'sum_data')
|
||||
return None
|
||||
|
||||
|
||||
@@ -669,14 +670,17 @@ def export_sum_data_to_excel(start_date=None, end_date=None, config_id=None, dat
|
||||
return {'success': False, 'message': str(e)}
|
||||
|
||||
|
||||
def get_monthly_charge_degree_total(config_id=None, month=None):
|
||||
def get_monthly_charge_degree_total(config_id=None, month=None, split_type=None, split_value=None):
|
||||
"""
|
||||
获取当月充电电量累计(从求和数据表查询,对账后的准确数据)
|
||||
- 如果当天已经生成了日报并采集,自动包含在内,不会重复计算
|
||||
- 以配置当前拆分为准,只统计匹配的拆分值数据
|
||||
|
||||
Args:
|
||||
config_id: 配置ID(可选,不传则统计所有配置)
|
||||
month: 月份,格式 YYYY-MM(可选,默认当月)
|
||||
split_type: 拆分类型(可选,company_id/user_id/station_id)
|
||||
split_value: 拆分值,逗号分隔的字符串(可选,用于过滤)
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
@@ -716,6 +720,15 @@ def get_monthly_charge_degree_total(config_id=None, month=None):
|
||||
sql += ' AND config_id = %s'
|
||||
params.append(config_id)
|
||||
|
||||
# 按拆分值过滤(以配置当前拆分为准)
|
||||
if split_type and split_value:
|
||||
values = [v.strip() for v in split_value.split(',') if v.strip()]
|
||||
if values:
|
||||
placeholders = ','.join(['%s'] * len(values))
|
||||
sql += f' AND split_type = %s AND split_value IN ({placeholders})'
|
||||
params.append(split_type)
|
||||
params.extend(values)
|
||||
|
||||
sql += ' GROUP BY report_date ORDER BY report_date ASC'
|
||||
|
||||
rows = execute_query(sql, tuple(params))
|
||||
|
||||
@@ -16,8 +16,8 @@ async function batchDownloadHistory() {
|
||||
|
||||
// 按日期查询
|
||||
function queryHistoryByDate() {
|
||||
const startDate = document.getElementById('query-start-date').value;
|
||||
const endDate = document.getElementById('query-end-date').value;
|
||||
const startDate = document.getElementById('history-start-date').value;
|
||||
const endDate = document.getElementById('history-end-date').value;
|
||||
if (!startDate && !endDate) {
|
||||
alert('请至少选择一个日期');
|
||||
return;
|
||||
@@ -28,8 +28,8 @@ function queryHistoryByDate() {
|
||||
|
||||
// 清除日期查询
|
||||
function clearDateQuery() {
|
||||
document.getElementById('query-start-date').value = '';
|
||||
document.getElementById('query-end-date').value = '';
|
||||
document.getElementById('history-start-date').value = '';
|
||||
document.getElementById('history-end-date').value = '';
|
||||
loadHistory(1);
|
||||
}
|
||||
|
||||
@@ -227,3 +227,59 @@ function toggleSelectAllHistory() {
|
||||
cb.checked = selectAllCheckbox.checked;
|
||||
});
|
||||
}
|
||||
|
||||
// 删除单条历史记录
|
||||
async function deleteHistory(id) {
|
||||
if (!confirm('确定要删除这条历史记录吗?对应的报表文件也会被删除。')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/report/history/batch-delete', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ids: [id] })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('删除成功');
|
||||
loadHistory();
|
||||
} else {
|
||||
alert('删除失败: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('删除失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 批量删除历史记录
|
||||
async function batchDeleteHistory() {
|
||||
const checkboxes = document.querySelectorAll('.history-checkbox:checked');
|
||||
if (checkboxes.length === 0) {
|
||||
alert('请至少选择一条记录');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`确定要删除选中的 ${checkboxes.length} 条历史记录吗?对应的报表文件也会被删除。`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = Array.from(checkboxes).map(cb => parseInt(cb.value));
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/report/history/batch-delete', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ids: ids })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert(result.message);
|
||||
loadHistory();
|
||||
} else {
|
||||
alert('删除失败: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('删除失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>订单日报系统</title>
|
||||
<link rel="stylesheet" href="/public/static/css/style.css?v=2026071506">
|
||||
<link rel="stylesheet" href="/public/static/css/style.css?v=2026071507">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
@@ -97,7 +97,8 @@
|
||||
<input type="date" id="history-end-date" class="form-control" style="width: auto;">
|
||||
<button class="btn btn-primary" onclick="queryHistoryByDate()">查询</button>
|
||||
<button class="btn" onclick="clearDateQuery()">清除</button>
|
||||
<button class="btn btn-danger" onclick="batchDownloadHistory()" style="margin-left: auto;">批量下载</button>
|
||||
<button class="btn btn-danger" onclick="batchDeleteHistory()" style="margin-left: auto;">批量删除</button>
|
||||
<button class="btn btn-danger" onclick="batchDownloadHistory()">批量下载</button>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
@@ -350,11 +351,11 @@
|
||||
</div>
|
||||
|
||||
<!-- JavaScript 文件 -->
|
||||
<script src="/public/static/js/common.js?v=2026071506"></script>
|
||||
<script src="/public/static/js/config.js?v=2026071506"></script>
|
||||
<script src="/public/static/js/entity.js?v=2026071506"></script>
|
||||
<script src="/public/static/js/report.js?v=2026071506"></script>
|
||||
<script src="/public/static/js/history.js?v=2026071506"></script>
|
||||
<script src="/public/static/js/sum_data.js?v=2026071506"></script>
|
||||
<script src="/public/static/js/common.js?v=2026071507"></script>
|
||||
<script src="/public/static/js/config.js?v=2026071507"></script>
|
||||
<script src="/public/static/js/entity.js?v=2026071507"></script>
|
||||
<script src="/public/static/js/report.js?v=2026071507"></script>
|
||||
<script src="/public/static/js/history.js?v=2026071507"></script>
|
||||
<script src="/public/static/js/sum_data.js?v=2026071507"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user