2026-07-13 15:43:03 +08:00
|
|
|
|
"""
|
|
|
|
|
|
配置管理 API
|
|
|
|
|
|
"""
|
|
|
|
|
|
import json
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from flask import request, jsonify
|
|
|
|
|
|
from lib.db import execute_query, execute_update, execute_insert
|
2026-07-15 16:01:06 +08:00
|
|
|
|
from lib.logger import log_info, log_error, log_warning
|
2026-07-13 15:43:03 +08:00
|
|
|
|
from . import config_bp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@config_bp.route('/config', methods=['GET'])
|
|
|
|
|
|
def get_configs():
|
|
|
|
|
|
"""获取所有配置"""
|
|
|
|
|
|
try:
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_info('[配置API] 获取配置列表', 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
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 []
|
|
|
|
|
|
config['time_periods'] = json.loads(config['time_periods']) if config.get('time_periods') else {}
|
2026-07-15 16:41:13 +08:00
|
|
|
|
config['field_custom_names'] = json.loads(config['field_custom_names']) if config.get('field_custom_names') else {}
|
2026-07-13 15:43:03 +08:00
|
|
|
|
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_info(f'[配置API] 获取到 {len(configs)} 条配置', 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
return jsonify({'success': True, 'data': configs})
|
|
|
|
|
|
except Exception as e:
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_error(f'[配置API] 获取配置列表失败: {e}', 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@config_bp.route('/config', methods=['POST'])
|
|
|
|
|
|
def create_config():
|
|
|
|
|
|
"""创建配置"""
|
|
|
|
|
|
try:
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_info('[配置API] 创建配置', 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
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', [])
|
|
|
|
|
|
time_periods = data.get('time_periods', {})
|
|
|
|
|
|
merge_telecom = 1 if data.get('merge_telecom') else 0
|
|
|
|
|
|
telecom_vehicle_no = data.get('telecom_vehicle_no', '')
|
|
|
|
|
|
field_custom_names = data.get('field_custom_names', {})
|
2026-07-14 14:39:38 +08:00
|
|
|
|
show_monthly_total = 1 if data.get('show_monthly_total') else 0
|
2026-07-15 11:23:02 +08:00
|
|
|
|
custom_service_fee_price = data.get('custom_service_fee_price')
|
|
|
|
|
|
if custom_service_fee_price is not None and custom_service_fee_price != '':
|
|
|
|
|
|
custom_service_fee_price = float(custom_service_fee_price)
|
|
|
|
|
|
else:
|
|
|
|
|
|
custom_service_fee_price = None
|
|
|
|
|
|
|
|
|
|
|
|
custom_service_fee_name = data.get('custom_service_fee_name')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
|
2026-07-23 14:57:29 +08:00
|
|
|
|
show_custom_service_fee = 1 if data.get('show_custom_service_fee') else 0
|
|
|
|
|
|
show_total_amount = 1 if data.get('show_total_amount') else 0
|
|
|
|
|
|
total_amount_name = data.get('total_amount_name')
|
|
|
|
|
|
|
2026-07-13 15:43:03 +08:00
|
|
|
|
if not config_name or not split_type or not split_value:
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_warning('[配置API] 创建配置失败:缺少必填字段', 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
return jsonify({'success': False, 'message': '缺少必填字段'}), 400
|
|
|
|
|
|
|
|
|
|
|
|
if not selected_fields:
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_warning('[配置API] 创建配置失败:未选择字段', 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
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,
|
2026-07-14 14:39:38 +08:00
|
|
|
|
sum_fields, time_periods, merge_telecom, telecom_vehicle_no, field_custom_names,
|
2026-07-23 14:57:29 +08:00
|
|
|
|
show_monthly_total, custom_service_fee_price, custom_service_fee_name,
|
|
|
|
|
|
show_custom_service_fee, show_total_amount, total_amount_name,
|
|
|
|
|
|
is_active, sort_order, create_time, update_time)
|
|
|
|
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 1, %s, NOW(), NOW())
|
2026-07-13 15:43:03 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
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),
|
|
|
|
|
|
json.dumps(time_periods, ensure_ascii=False),
|
|
|
|
|
|
merge_telecom,
|
|
|
|
|
|
telecom_vehicle_no,
|
|
|
|
|
|
json.dumps(field_custom_names, ensure_ascii=False) if field_custom_names else None,
|
2026-07-14 14:39:38 +08:00
|
|
|
|
show_monthly_total,
|
2026-07-15 11:23:02 +08:00
|
|
|
|
custom_service_fee_price,
|
|
|
|
|
|
custom_service_fee_name if custom_service_fee_name else None,
|
2026-07-23 14:57:29 +08:00
|
|
|
|
show_custom_service_fee,
|
|
|
|
|
|
show_total_amount,
|
|
|
|
|
|
total_amount_name if total_amount_name else None,
|
2026-07-13 15:43:03 +08:00
|
|
|
|
sort_order
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
execute_insert(sql, params)
|
|
|
|
|
|
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_info(f'[配置API] 配置创建成功,ID: {config_id},名称: {config_name}', 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': True,
|
|
|
|
|
|
'message': '配置创建成功',
|
|
|
|
|
|
'data': {'id': config_id}
|
|
|
|
|
|
})
|
|
|
|
|
|
except Exception as e:
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_error(f'[配置API] 创建配置失败: {e}', 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@config_bp.route('/config/<int:config_id>', methods=['PUT'])
|
|
|
|
|
|
def update_config(config_id):
|
|
|
|
|
|
"""更新配置"""
|
|
|
|
|
|
try:
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_info(f'[配置API] 更新配置,ID: {config_id}', 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
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', [])
|
|
|
|
|
|
time_periods = data.get('time_periods', {})
|
|
|
|
|
|
merge_telecom = 1 if data.get('merge_telecom') else 0
|
|
|
|
|
|
telecom_vehicle_no = data.get('telecom_vehicle_no', '')
|
|
|
|
|
|
field_custom_names = data.get('field_custom_names', {})
|
2026-07-14 14:39:38 +08:00
|
|
|
|
show_monthly_total = 1 if data.get('show_monthly_total') else 0
|
2026-07-15 11:23:02 +08:00
|
|
|
|
custom_service_fee_price = data.get('custom_service_fee_price')
|
|
|
|
|
|
if custom_service_fee_price is not None and custom_service_fee_price != '':
|
|
|
|
|
|
custom_service_fee_price = float(custom_service_fee_price)
|
|
|
|
|
|
else:
|
|
|
|
|
|
custom_service_fee_price = None
|
|
|
|
|
|
|
|
|
|
|
|
custom_service_fee_name = data.get('custom_service_fee_name')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
|
2026-07-23 14:57:29 +08:00
|
|
|
|
show_custom_service_fee = 1 if data.get('show_custom_service_fee') else 0
|
|
|
|
|
|
show_total_amount = 1 if data.get('show_total_amount') else 0
|
|
|
|
|
|
total_amount_name = data.get('total_amount_name')
|
|
|
|
|
|
|
2026-07-15 16:01:06 +08:00
|
|
|
|
# 查询原配置,检查拆分是否变化
|
|
|
|
|
|
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]
|
|
|
|
|
|
|
2026-07-13 15:43:03 +08:00
|
|
|
|
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, time_periods = %s,
|
2026-07-14 14:39:38 +08:00
|
|
|
|
merge_telecom = %s, telecom_vehicle_no = %s, field_custom_names = %s,
|
2026-07-15 11:23:02 +08:00
|
|
|
|
show_monthly_total = %s, custom_service_fee_price = %s, custom_service_fee_name = %s,
|
2026-07-23 14:57:29 +08:00
|
|
|
|
show_custom_service_fee = %s, show_total_amount = %s, total_amount_name = %s,
|
2026-07-15 11:23:02 +08:00
|
|
|
|
update_time = NOW()
|
2026-07-13 15:43:03 +08:00
|
|
|
|
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),
|
|
|
|
|
|
json.dumps(time_periods, ensure_ascii=False),
|
|
|
|
|
|
merge_telecom,
|
|
|
|
|
|
telecom_vehicle_no,
|
|
|
|
|
|
json.dumps(field_custom_names, ensure_ascii=False) if field_custom_names else None,
|
2026-07-14 14:39:38 +08:00
|
|
|
|
show_monthly_total,
|
2026-07-15 11:23:02 +08:00
|
|
|
|
custom_service_fee_price,
|
|
|
|
|
|
custom_service_fee_name if custom_service_fee_name else None,
|
2026-07-23 14:57:29 +08:00
|
|
|
|
show_custom_service_fee,
|
|
|
|
|
|
show_total_amount,
|
|
|
|
|
|
total_amount_name if total_amount_name else None,
|
2026-07-13 15:43:03 +08:00
|
|
|
|
config_id
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
execute_update(sql, params)
|
|
|
|
|
|
|
2026-07-15 16:01:06 +08:00
|
|
|
|
# 如果拆分类型或拆分值变化了,清理旧的求和数据(以当前拆分为准)
|
|
|
|
|
|
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:
|
|
|
|
|
|
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')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
return jsonify({'success': True, 'message': '配置更新成功'})
|
|
|
|
|
|
except Exception as e:
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_error(f'[配置API] 更新配置失败: {e}', 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@config_bp.route('/config/<int:config_id>', methods=['DELETE'])
|
|
|
|
|
|
def delete_config(config_id):
|
|
|
|
|
|
"""删除配置"""
|
|
|
|
|
|
try:
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_info(f'[配置API] 删除配置,ID: {config_id}', 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
execute_update('DELETE FROM t_daily_report_config WHERE id = %s', (config_id,))
|
2026-07-15 16:01:06 +08:00
|
|
|
|
# 同时删除对应的求和数据
|
|
|
|
|
|
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')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
return jsonify({'success': True, 'message': '配置删除成功'})
|
|
|
|
|
|
except Exception as e:
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_error(f'[配置API] 删除配置失败: {e}', 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@config_bp.route('/config/<int:config_id>/copy', methods=['POST'])
|
|
|
|
|
|
def copy_config(config_id):
|
|
|
|
|
|
"""复制配置"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 获取原配置
|
|
|
|
|
|
configs = execute_query('SELECT * FROM t_daily_report_config WHERE id = %s', (config_id,))
|
|
|
|
|
|
if not configs:
|
|
|
|
|
|
return jsonify({'success': False, 'message': '原配置不存在'}), 404
|
|
|
|
|
|
|
|
|
|
|
|
original = configs[0]
|
|
|
|
|
|
|
|
|
|
|
|
# 生成新 ID 和新名称
|
|
|
|
|
|
new_id = int(datetime.now().timestamp() * 1000)
|
|
|
|
|
|
new_name = f"{original['config_name']}_副本"
|
|
|
|
|
|
|
|
|
|
|
|
# 获取最大排序值
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
# 插入新配置
|
|
|
|
|
|
execute_insert(
|
|
|
|
|
|
'''INSERT INTO t_daily_report_config
|
2026-07-23 14:57:29 +08:00
|
|
|
|
(id, config_name, split_type, split_value, split_name, selected_fields, sum_fields, time_periods, merge_telecom, telecom_vehicle_no, field_custom_names, show_monthly_total, custom_service_fee_price, custom_service_fee_name, show_custom_service_fee, show_total_amount, total_amount_name, is_active, sort_order, create_time, update_time)
|
|
|
|
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())''',
|
2026-07-13 15:43:03 +08:00
|
|
|
|
(
|
|
|
|
|
|
new_id,
|
|
|
|
|
|
new_name,
|
|
|
|
|
|
original['split_type'],
|
|
|
|
|
|
original['split_value'],
|
|
|
|
|
|
original['split_name'],
|
|
|
|
|
|
original['selected_fields'],
|
|
|
|
|
|
original.get('sum_fields', ''),
|
|
|
|
|
|
original.get('time_periods', ''),
|
|
|
|
|
|
original.get('merge_telecom', 0),
|
|
|
|
|
|
original.get('telecom_vehicle_no', ''),
|
|
|
|
|
|
original.get('field_custom_names', None),
|
2026-07-14 14:39:38 +08:00
|
|
|
|
original.get('show_monthly_total', 0),
|
2026-07-15 11:23:02 +08:00
|
|
|
|
original.get('custom_service_fee_price'),
|
|
|
|
|
|
original.get('custom_service_fee_name'),
|
2026-07-23 14:57:29 +08:00
|
|
|
|
original.get('show_custom_service_fee', 0),
|
|
|
|
|
|
original.get('show_total_amount', 0),
|
|
|
|
|
|
original.get('total_amount_name'),
|
2026-07-13 15:43:03 +08:00
|
|
|
|
original['is_active'],
|
|
|
|
|
|
sort_order
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return jsonify({'success': True, 'message': '配置复制成功', 'id': new_id})
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@config_bp.route('/config/<int:config_id>/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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@config_bp.route('/config/<int:config_id>/move', methods=['POST'])
|
|
|
|
|
|
def move_config(config_id):
|
|
|
|
|
|
"""移动配置排序"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
direction = request.json.get('direction') # up 或 down
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_info(f"[配置API] 移动配置,ID: {config_id}, 方向: {direction}", 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
|
2026-07-14 14:24:21 +08:00
|
|
|
|
# 查出所有配置,按 sort_order, id 排序
|
|
|
|
|
|
all_configs = execute_query(
|
|
|
|
|
|
'SELECT id, sort_order FROM t_daily_report_config ORDER BY sort_order ASC, id ASC'
|
2026-07-13 15:43:03 +08:00
|
|
|
|
)
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_info(f"[配置API] 所有配置数量: {len(all_configs)}", 'api')
|
2026-07-14 14:24:21 +08:00
|
|
|
|
|
|
|
|
|
|
# 找到当前配置的索引
|
|
|
|
|
|
current_index = None
|
|
|
|
|
|
for i, c in enumerate(all_configs):
|
|
|
|
|
|
if c['id'] == config_id:
|
|
|
|
|
|
current_index = i
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
if current_index is None:
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_warning(f"[配置API] 未找到配置,ID: {config_id}", 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
return jsonify({'success': False, 'message': '配置不存在'}), 404
|
|
|
|
|
|
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_info(f"[配置API] 当前配置索引: {current_index}", 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
|
2026-07-14 14:24:21 +08:00
|
|
|
|
# 计算目标索引
|
2026-07-13 15:43:03 +08:00
|
|
|
|
if direction == 'up':
|
2026-07-14 14:24:21 +08:00
|
|
|
|
target_index = current_index - 1
|
2026-07-13 15:43:03 +08:00
|
|
|
|
elif direction == 'down':
|
2026-07-14 14:24:21 +08:00
|
|
|
|
target_index = current_index + 1
|
|
|
|
|
|
else:
|
|
|
|
|
|
return jsonify({'success': False, 'message': '无效的移动方向'}), 400
|
|
|
|
|
|
|
|
|
|
|
|
# 边界检查
|
|
|
|
|
|
if target_index < 0 or target_index >= len(all_configs):
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_info(f"[配置API] 已到边界,无法移动", 'api')
|
2026-07-14 14:24:21 +08:00
|
|
|
|
return jsonify({'success': True, 'message': '已到边界'})
|
|
|
|
|
|
|
|
|
|
|
|
# 在内存数组中交换位置
|
|
|
|
|
|
all_configs[current_index], all_configs[target_index] = all_configs[target_index], all_configs[current_index]
|
|
|
|
|
|
|
|
|
|
|
|
# 全量重排:按新顺序给每个配置重新赋值 sort_order
|
|
|
|
|
|
for idx, cfg in enumerate(all_configs):
|
|
|
|
|
|
new_sort = idx + 1
|
|
|
|
|
|
execute_update(
|
|
|
|
|
|
'UPDATE t_daily_report_config SET sort_order = %s WHERE id = %s',
|
|
|
|
|
|
(new_sort, cfg['id'])
|
2026-07-13 15:43:03 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_info(f"[配置API] 配置移动成功,ID: {config_id}", 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
return jsonify({'success': True, 'message': '移动成功'})
|
|
|
|
|
|
except Exception as e:
|
2026-07-15 16:01:06 +08:00
|
|
|
|
log_error(f"[配置API] 移动配置失败: {e}", 'api')
|
2026-07-13 15:43:03 +08:00
|
|
|
|
return jsonify({'success': False, 'message': str(e)}), 500
|
2026-07-14 16:05:43 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-07-14 16:30:26 +08:00
|
|
|
|
EXCEL_COLUMNS = [
|
|
|
|
|
|
{'key': 'config_name', 'name': '配置名称', 'required': True},
|
|
|
|
|
|
{'key': 'split_type', 'name': '拆分方式', 'required': True, 'comment': 'company_id=按企业, user_id=按用户, station_id=按场站'},
|
|
|
|
|
|
{'key': 'split_value', 'name': '拆分值', 'required': True, 'comment': '多个值用英文逗号分隔'},
|
|
|
|
|
|
{'key': 'split_name', 'name': '拆分显示名称', 'required': False},
|
|
|
|
|
|
{'key': 'selected_fields', 'name': '选择字段(JSON数组)', 'required': True, 'comment': '如: ["charge_degree","total_energy","charge_money"]'},
|
|
|
|
|
|
{'key': 'sum_fields', 'name': '求和字段(JSON数组)', 'required': False, 'comment': '如: ["peak_charge","flat_charge","valley_charge","charge_money"]'},
|
|
|
|
|
|
{'key': 'time_periods', 'name': '分时段配置(JSON对象)', 'required': False, 'comment': '如: {"尖":{"start":"10:00","end":"12:00"}}'},
|
|
|
|
|
|
{'key': 'merge_telecom', 'name': '是否合并特来电', 'required': False, 'comment': '0=否, 1=是'},
|
|
|
|
|
|
{'key': 'telecom_vehicle_no', 'name': '特来电自编号', 'required': False},
|
|
|
|
|
|
{'key': 'field_custom_names', 'name': '字段自定义表头(JSON对象)', 'required': False, 'comment': '如: {"charge_degree":"充电量(kWh)"}'},
|
|
|
|
|
|
{'key': 'show_monthly_total', 'name': '是否显示月度总计', 'required': False, 'comment': '0=否, 1=是'},
|
2026-07-15 11:23:02 +08:00
|
|
|
|
{'key': 'custom_service_fee_price', 'name': '自定义服务费单价(元/kWh)', 'required': False, 'comment': '设置后按充电电量×单价计算自定义服务费'},
|
|
|
|
|
|
{'key': 'custom_service_fee_name', 'name': '自定义服务费表头名称', 'required': False, 'comment': '报表中显示的列名,留空则使用默认名称'},
|
2026-07-14 16:30:26 +08:00
|
|
|
|
{'key': 'is_active', 'name': '是否启用', 'required': False, 'comment': '0=禁用, 1=启用'},
|
|
|
|
|
|
{'key': 'sort_order', 'name': '排序号', 'required': False, 'comment': '数字越小越靠前'},
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-14 16:05:43 +08:00
|
|
|
|
@config_bp.route('/config/export', methods=['GET'])
|
|
|
|
|
|
def export_configs():
|
2026-07-14 16:30:26 +08:00
|
|
|
|
"""导出所有配置为Excel文件"""
|
2026-07-14 16:05:43 +08:00
|
|
|
|
try:
|
|
|
|
|
|
from flask import Response
|
2026-07-14 16:30:26 +08:00
|
|
|
|
from openpyxl import Workbook
|
|
|
|
|
|
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
|
|
|
|
|
|
from io import BytesIO
|
|
|
|
|
|
from urllib.parse import quote
|
2026-07-14 16:05:43 +08:00
|
|
|
|
|
|
|
|
|
|
configs = execute_query(
|
|
|
|
|
|
'SELECT * FROM t_daily_report_config ORDER BY sort_order ASC, id ASC'
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-07-14 16:30:26 +08:00
|
|
|
|
wb = Workbook()
|
|
|
|
|
|
ws = wb.active
|
|
|
|
|
|
ws.title = '日报配置'
|
|
|
|
|
|
|
|
|
|
|
|
header_font = Font(bold=True, color='FFFFFF', size=11)
|
|
|
|
|
|
header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
|
|
|
|
|
|
header_alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
|
|
|
|
|
thin_border = Border(
|
|
|
|
|
|
left=Side(style='thin', color='000000'),
|
|
|
|
|
|
right=Side(style='thin', color='000000'),
|
|
|
|
|
|
top=Side(style='thin', color='000000'),
|
|
|
|
|
|
bottom=Side(style='thin', color='000000')
|
|
|
|
|
|
)
|
|
|
|
|
|
even_fill = PatternFill(start_color='F2F2F2', end_color='F2F2F2', fill_type='solid')
|
|
|
|
|
|
|
|
|
|
|
|
for col_idx, col in enumerate(EXCEL_COLUMNS, 1):
|
|
|
|
|
|
cell = ws.cell(row=1, column=col_idx)
|
|
|
|
|
|
cell.value = col['name']
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
ws.column_dimensions[cell.column_letter].width = max(len(col['name']) * 2 + 4, 18)
|
|
|
|
|
|
|
|
|
|
|
|
ws.row_dimensions[1].height = 30
|
|
|
|
|
|
|
|
|
|
|
|
for row_idx, config in enumerate(configs, 2):
|
|
|
|
|
|
is_even = (row_idx - 2) % 2 == 1
|
|
|
|
|
|
for col_idx, col in enumerate(EXCEL_COLUMNS, 1):
|
|
|
|
|
|
cell = ws.cell(row=row_idx, column=col_idx)
|
|
|
|
|
|
value = config.get(col['key'], '')
|
|
|
|
|
|
if value is None:
|
|
|
|
|
|
value = ''
|
|
|
|
|
|
if isinstance(value, datetime):
|
|
|
|
|
|
value = value.strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
|
|
if isinstance(value, list) or isinstance(value, dict):
|
|
|
|
|
|
value = json.dumps(value, ensure_ascii=False)
|
|
|
|
|
|
cell.value = str(value) if value is not None else ''
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
cell.alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
|
|
|
|
|
|
if is_even:
|
|
|
|
|
|
cell.fill = even_fill
|
|
|
|
|
|
|
|
|
|
|
|
ws.freeze_panes = 'A2'
|
|
|
|
|
|
|
|
|
|
|
|
output = BytesIO()
|
|
|
|
|
|
wb.save(output)
|
|
|
|
|
|
output.seek(0)
|
|
|
|
|
|
|
|
|
|
|
|
filename = f'日报配置导出_{datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx'
|
|
|
|
|
|
response = Response(
|
|
|
|
|
|
output.read(),
|
|
|
|
|
|
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
|
|
|
|
)
|
|
|
|
|
|
response.headers['Content-Disposition'] = f"attachment; filename*=UTF-8''{quote(filename)}"
|
2026-07-14 16:05:43 +08:00
|
|
|
|
return response
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
return jsonify({'success': False, 'message': f'导出失败: {str(e)}'}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-14 16:30:26 +08:00
|
|
|
|
@config_bp.route('/config/template', methods=['GET'])
|
|
|
|
|
|
def download_template():
|
|
|
|
|
|
"""下载导入模板"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
from flask import Response
|
|
|
|
|
|
from openpyxl import Workbook
|
|
|
|
|
|
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
|
|
|
|
|
|
from io import BytesIO
|
|
|
|
|
|
from urllib.parse import quote
|
|
|
|
|
|
|
|
|
|
|
|
wb = Workbook()
|
|
|
|
|
|
ws = wb.active
|
|
|
|
|
|
ws.title = '日报配置'
|
|
|
|
|
|
|
|
|
|
|
|
header_font = Font(bold=True, color='FFFFFF', size=11)
|
|
|
|
|
|
header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
|
|
|
|
|
|
header_alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
|
|
|
|
|
comment_font = Font(color='FF0000', size=9)
|
|
|
|
|
|
thin_border = Border(
|
|
|
|
|
|
left=Side(style='thin', color='000000'),
|
|
|
|
|
|
right=Side(style='thin', color='000000'),
|
|
|
|
|
|
top=Side(style='thin', color='000000'),
|
|
|
|
|
|
bottom=Side(style='thin', color='000000')
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
for col_idx, col in enumerate(EXCEL_COLUMNS, 1):
|
|
|
|
|
|
cell = ws.cell(row=1, column=col_idx)
|
|
|
|
|
|
title = col['name']
|
|
|
|
|
|
if col.get('required'):
|
|
|
|
|
|
title = '*' + title
|
|
|
|
|
|
cell.value = title
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
ws.column_dimensions[cell.column_letter].width = max(len(col['name']) * 2 + 4, 20)
|
|
|
|
|
|
|
|
|
|
|
|
ws.row_dimensions[1].height = 30
|
|
|
|
|
|
|
|
|
|
|
|
for col_idx, col in enumerate(EXCEL_COLUMNS, 1):
|
|
|
|
|
|
cell = ws.cell(row=2, column=col_idx)
|
|
|
|
|
|
if col.get('comment'):
|
|
|
|
|
|
cell.value = col['comment']
|
|
|
|
|
|
cell.font = comment_font
|
|
|
|
|
|
cell.fill = PatternFill(start_color='FFF2CC', end_color='FFF2CC', fill_type='solid')
|
|
|
|
|
|
else:
|
|
|
|
|
|
cell.value = ''
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
cell.alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
|
|
|
|
|
|
|
|
|
|
|
|
ws.row_dimensions[2].height = 40
|
|
|
|
|
|
|
|
|
|
|
|
example_row = 3
|
|
|
|
|
|
example_data = {
|
|
|
|
|
|
'config_name': '示例-企业日报',
|
|
|
|
|
|
'split_type': 'company_id',
|
|
|
|
|
|
'split_value': '1001,1002',
|
|
|
|
|
|
'split_name': '',
|
|
|
|
|
|
'selected_fields': '["charge_degree","total_energy","charge_money"]',
|
|
|
|
|
|
'sum_fields': '["peak_charge","flat_charge","valley_charge","charge_money"]',
|
|
|
|
|
|
'time_periods': '{"尖":{"start":"10:00","end":"12:00"},"峰":{"start":"08:00","end":"10:00"}}',
|
|
|
|
|
|
'merge_telecom': '0',
|
|
|
|
|
|
'telecom_vehicle_no': '',
|
|
|
|
|
|
'field_custom_names': '{"charge_degree":"充电量(kWh)"}',
|
|
|
|
|
|
'show_monthly_total': '1',
|
2026-07-15 11:23:02 +08:00
|
|
|
|
'custom_service_fee_price': '0.8',
|
|
|
|
|
|
'custom_service_fee_name': '服务费(自定义)',
|
2026-07-14 16:30:26 +08:00
|
|
|
|
'is_active': '0',
|
|
|
|
|
|
'sort_order': '1',
|
|
|
|
|
|
}
|
|
|
|
|
|
for col_idx, col in enumerate(EXCEL_COLUMNS, 1):
|
|
|
|
|
|
cell = ws.cell(row=example_row, column=col_idx)
|
|
|
|
|
|
cell.value = example_data.get(col['key'], '')
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
cell.alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
|
|
|
|
|
|
cell.font = Font(color='808080', size=10)
|
|
|
|
|
|
|
|
|
|
|
|
ws.freeze_panes = 'A3'
|
|
|
|
|
|
|
|
|
|
|
|
ws2 = wb.create_sheet('填写说明')
|
|
|
|
|
|
instructions = [
|
|
|
|
|
|
['日报配置导入模板填写说明', ''],
|
|
|
|
|
|
['', ''],
|
|
|
|
|
|
['1. 带 * 号的列为必填项', ''],
|
|
|
|
|
|
['2. 拆分方式可选值', 'company_id=按企业, user_id=按用户, station_id=按场站'],
|
|
|
|
|
|
['3. 拆分值', '多个值用英文逗号分隔,如: 1001,1002,1003'],
|
|
|
|
|
|
['4. JSON格式字段', '选择字段、求和字段、分时段配置、自定义表头都需要填写JSON格式字符串'],
|
|
|
|
|
|
['5. 是否类字段', '填 0 表示否,填 1 表示是'],
|
|
|
|
|
|
['6. 导入规则', '导入后配置名称自动添加"_导入"后缀,默认禁用,排序号追加到末尾'],
|
|
|
|
|
|
['7. 注意事项', '请勿修改第一行表头,第二行为说明行,从第三行开始填写数据(第三行为示例)'],
|
|
|
|
|
|
]
|
|
|
|
|
|
for row_idx, (key, val) in enumerate(instructions, 1):
|
|
|
|
|
|
ws2.cell(row=row_idx, column=1, value=key)
|
|
|
|
|
|
ws2.cell(row=row_idx, column=2, value=val)
|
|
|
|
|
|
ws2.cell(row=row_idx, column=1).font = Font(bold=True if row_idx == 1 else False)
|
|
|
|
|
|
ws2.column_dimensions['A'].width = 25
|
|
|
|
|
|
ws2.column_dimensions['B'].width = 60
|
|
|
|
|
|
|
|
|
|
|
|
output = BytesIO()
|
|
|
|
|
|
wb.save(output)
|
|
|
|
|
|
output.seek(0)
|
|
|
|
|
|
|
|
|
|
|
|
filename = '日报配置导入模板.xlsx'
|
|
|
|
|
|
response = Response(
|
|
|
|
|
|
output.read(),
|
|
|
|
|
|
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
|
|
|
|
)
|
|
|
|
|
|
response.headers['Content-Disposition'] = f"attachment; filename*=UTF-8''{quote(filename)}"
|
|
|
|
|
|
return response
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
return jsonify({'success': False, 'message': f'模板下载失败: {str(e)}'}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-14 16:05:43 +08:00
|
|
|
|
@config_bp.route('/config/import', methods=['POST'])
|
|
|
|
|
|
def import_configs():
|
2026-07-14 16:30:26 +08:00
|
|
|
|
"""从Excel文件导入配置"""
|
2026-07-14 16:05:43 +08:00
|
|
|
|
try:
|
|
|
|
|
|
if 'file' not in request.files:
|
|
|
|
|
|
return jsonify({'success': False, 'message': '请选择要导入的文件'}), 400
|
|
|
|
|
|
|
|
|
|
|
|
file = request.files['file']
|
|
|
|
|
|
if file.filename == '':
|
|
|
|
|
|
return jsonify({'success': False, 'message': '请选择要导入的文件'}), 400
|
|
|
|
|
|
|
2026-07-14 16:30:26 +08:00
|
|
|
|
if not (file.filename.endswith('.xlsx') or file.filename.endswith('.xls')):
|
|
|
|
|
|
return jsonify({'success': False, 'message': '只支持 Excel 格式(.xlsx/.xls)的配置文件'}), 400
|
|
|
|
|
|
|
|
|
|
|
|
from openpyxl import load_workbook
|
|
|
|
|
|
from io import BytesIO
|
|
|
|
|
|
|
|
|
|
|
|
wb = load_workbook(BytesIO(file.read()), data_only=True)
|
|
|
|
|
|
ws = wb.active
|
|
|
|
|
|
|
|
|
|
|
|
headers = []
|
|
|
|
|
|
for cell in ws[1]:
|
|
|
|
|
|
headers.append(str(cell.value).strip() if cell.value else '')
|
|
|
|
|
|
|
|
|
|
|
|
col_key_map = {}
|
|
|
|
|
|
for col in EXCEL_COLUMNS:
|
|
|
|
|
|
if col['name'] in headers:
|
|
|
|
|
|
col_idx = headers.index(col['name'])
|
|
|
|
|
|
col_key_map[col['key']] = col_idx
|
|
|
|
|
|
|
|
|
|
|
|
configs_to_import = []
|
|
|
|
|
|
for row in ws.iter_rows(min_row=3, values_only=True):
|
|
|
|
|
|
row_data = {}
|
|
|
|
|
|
has_data = False
|
|
|
|
|
|
for key, col_idx in col_key_map.items():
|
|
|
|
|
|
value = row[col_idx] if col_idx < len(row) else None
|
|
|
|
|
|
if value is not None and str(value).strip():
|
|
|
|
|
|
has_data = True
|
|
|
|
|
|
row_data[key] = str(value).strip() if value is not None else ''
|
|
|
|
|
|
|
|
|
|
|
|
if has_data and row_data.get('config_name'):
|
|
|
|
|
|
configs_to_import.append(row_data)
|
|
|
|
|
|
|
|
|
|
|
|
if not configs_to_import:
|
|
|
|
|
|
return jsonify({'success': False, 'message': '没有可导入的配置,请从第3行开始填写数据'}), 400
|
2026-07-14 16:05:43 +08:00
|
|
|
|
|
|
|
|
|
|
max_sort_result = execute_query('SELECT MAX(sort_order) as max_sort FROM t_daily_report_config')
|
|
|
|
|
|
current_max_sort = max_sort_result[0]['max_sort'] or 0
|
|
|
|
|
|
|
|
|
|
|
|
success_count = 0
|
|
|
|
|
|
fail_count = 0
|
|
|
|
|
|
fail_messages = []
|
|
|
|
|
|
|
|
|
|
|
|
for idx, config_data in enumerate(configs_to_import):
|
|
|
|
|
|
try:
|
2026-07-14 16:30:26 +08:00
|
|
|
|
config_name = config_data.get('config_name', '').strip()
|
|
|
|
|
|
split_type = config_data.get('split_type', '').strip()
|
|
|
|
|
|
split_value = config_data.get('split_value', '').strip()
|
2026-07-14 16:05:43 +08:00
|
|
|
|
|
2026-07-14 16:30:26 +08:00
|
|
|
|
if not config_name:
|
2026-07-14 16:05:43 +08:00
|
|
|
|
fail_count += 1
|
2026-07-14 16:30:26 +08:00
|
|
|
|
fail_messages.append(f'第{idx+3}行:缺少配置名称')
|
|
|
|
|
|
continue
|
|
|
|
|
|
if not split_type:
|
|
|
|
|
|
fail_count += 1
|
|
|
|
|
|
fail_messages.append(f'第{idx+3}行:缺少拆分方式')
|
|
|
|
|
|
continue
|
|
|
|
|
|
if not split_value:
|
|
|
|
|
|
fail_count += 1
|
|
|
|
|
|
fail_messages.append(f'第{idx+3}行:缺少拆分值')
|
2026-07-14 16:05:43 +08:00
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
new_id = int(datetime.now().timestamp() * 1000) + idx
|
|
|
|
|
|
new_name = f"{config_name}_导入"
|
2026-07-14 16:30:26 +08:00
|
|
|
|
|
|
|
|
|
|
import_sort_order = config_data.get('sort_order', '').strip()
|
|
|
|
|
|
if import_sort_order and import_sort_order.isdigit():
|
|
|
|
|
|
sort_order = current_max_sort + 1
|
|
|
|
|
|
else:
|
|
|
|
|
|
sort_order = current_max_sort + 1
|
2026-07-14 16:05:43 +08:00
|
|
|
|
current_max_sort += 1
|
|
|
|
|
|
|
2026-07-14 16:30:26 +08:00
|
|
|
|
selected_fields_str = config_data.get('selected_fields', '[]').strip()
|
|
|
|
|
|
try:
|
|
|
|
|
|
selected_fields = json.loads(selected_fields_str) if selected_fields_str else []
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
selected_fields = []
|
|
|
|
|
|
|
|
|
|
|
|
sum_fields_str = config_data.get('sum_fields', '[]').strip()
|
|
|
|
|
|
try:
|
|
|
|
|
|
sum_fields = json.loads(sum_fields_str) if sum_fields_str else []
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
sum_fields = []
|
|
|
|
|
|
|
|
|
|
|
|
time_periods_str = config_data.get('time_periods', '{}').strip()
|
|
|
|
|
|
try:
|
|
|
|
|
|
time_periods = json.loads(time_periods_str) if time_periods_str else {}
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
time_periods = {}
|
|
|
|
|
|
|
|
|
|
|
|
field_custom_names_str = config_data.get('field_custom_names', '').strip()
|
|
|
|
|
|
if field_custom_names_str:
|
|
|
|
|
|
try:
|
|
|
|
|
|
field_custom_names = json.loads(field_custom_names_str)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
field_custom_names = None
|
|
|
|
|
|
else:
|
|
|
|
|
|
field_custom_names = None
|
2026-07-14 16:05:43 +08:00
|
|
|
|
|
2026-07-14 16:30:26 +08:00
|
|
|
|
merge_telecom = config_data.get('merge_telecom', '0').strip()
|
|
|
|
|
|
merge_telecom = 1 if merge_telecom in ('1', '是', 'true', 'True') else 0
|
2026-07-14 16:05:43 +08:00
|
|
|
|
|
2026-07-14 16:30:26 +08:00
|
|
|
|
show_monthly_total = config_data.get('show_monthly_total', '0').strip()
|
|
|
|
|
|
show_monthly_total = 1 if show_monthly_total in ('1', '是', 'true', 'True') else 0
|
2026-07-14 16:05:43 +08:00
|
|
|
|
|
2026-07-14 16:30:26 +08:00
|
|
|
|
is_active = config_data.get('is_active', '0').strip()
|
|
|
|
|
|
is_active = 1 if is_active in ('1', '是', 'true', 'True') else 0
|
2026-07-14 16:05:43 +08:00
|
|
|
|
|
2026-07-15 11:23:02 +08:00
|
|
|
|
custom_service_fee_price_str = config_data.get('custom_service_fee_price', '').strip()
|
|
|
|
|
|
if custom_service_fee_price_str:
|
|
|
|
|
|
try:
|
|
|
|
|
|
custom_service_fee_price = float(custom_service_fee_price_str)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
custom_service_fee_price = None
|
|
|
|
|
|
else:
|
|
|
|
|
|
custom_service_fee_price = None
|
|
|
|
|
|
|
|
|
|
|
|
custom_service_fee_name = config_data.get('custom_service_fee_name', '').strip()
|
|
|
|
|
|
if not custom_service_fee_name:
|
|
|
|
|
|
custom_service_fee_name = None
|
|
|
|
|
|
|
2026-07-14 16:05:43 +08:00
|
|
|
|
sql = """
|
|
|
|
|
|
INSERT INTO t_daily_report_config
|
|
|
|
|
|
(id, config_name, split_type, split_value, split_name, selected_fields,
|
|
|
|
|
|
sum_fields, time_periods, merge_telecom, telecom_vehicle_no, field_custom_names,
|
2026-07-15 11:23:02 +08:00
|
|
|
|
show_monthly_total, custom_service_fee_price, custom_service_fee_name, is_active, sort_order, create_time, update_time)
|
|
|
|
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())
|
2026-07-14 16:05:43 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
params = (
|
|
|
|
|
|
new_id,
|
|
|
|
|
|
new_name,
|
|
|
|
|
|
split_type,
|
|
|
|
|
|
split_value,
|
|
|
|
|
|
config_data.get('split_name', ''),
|
|
|
|
|
|
json.dumps(selected_fields, ensure_ascii=False),
|
|
|
|
|
|
json.dumps(sum_fields, ensure_ascii=False),
|
|
|
|
|
|
json.dumps(time_periods, ensure_ascii=False),
|
2026-07-14 16:30:26 +08:00
|
|
|
|
merge_telecom,
|
2026-07-14 16:05:43 +08:00
|
|
|
|
config_data.get('telecom_vehicle_no', ''),
|
|
|
|
|
|
json.dumps(field_custom_names, ensure_ascii=False) if field_custom_names else None,
|
2026-07-14 16:30:26 +08:00
|
|
|
|
show_monthly_total,
|
2026-07-15 11:23:02 +08:00
|
|
|
|
custom_service_fee_price,
|
|
|
|
|
|
custom_service_fee_name,
|
2026-07-14 16:30:26 +08:00
|
|
|
|
is_active,
|
2026-07-14 16:05:43 +08:00
|
|
|
|
sort_order
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
execute_insert(sql, params)
|
|
|
|
|
|
success_count += 1
|
|
|
|
|
|
except Exception as item_error:
|
|
|
|
|
|
fail_count += 1
|
2026-07-14 16:30:26 +08:00
|
|
|
|
fail_messages.append(f'第{idx+3}行:{str(item_error)}')
|
2026-07-14 16:05:43 +08:00
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': True,
|
|
|
|
|
|
'message': f'导入完成:成功 {success_count} 条,失败 {fail_count} 条',
|
|
|
|
|
|
'data': {
|
|
|
|
|
|
'success_count': success_count,
|
|
|
|
|
|
'fail_count': fail_count,
|
|
|
|
|
|
'fail_messages': fail_messages
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
return jsonify({'success': False, 'message': f'导入失败: {str(e)}'}), 500
|