Files
ylt_diy/lib/api/config_api.py

765 lines
35 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
配置管理 API
"""
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
@config_bp.route('/config', methods=['GET'])
def get_configs():
"""获取所有配置"""
try:
log_info('[配置API] 获取配置列表', 'api')
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 {}
config['field_custom_names'] = json.loads(config['field_custom_names']) if config.get('field_custom_names') 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
@config_bp.route('/config', methods=['POST'])
def create_config():
"""创建配置"""
try:
log_info('[配置API] 创建配置', 'api')
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', {})
show_monthly_total = 1 if data.get('show_monthly_total') else 0
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')
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')
# 找平电量开关默认开启1=开启0=关闭)
# 当数据中的'未在前端传递时默认为1开启保持与历史配置的兼容性
balance_electricity = data.get('balance_electricity')
if balance_electricity is None:
balance_electricity = 1
else:
balance_electricity = 1 if balance_electricity else 0
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
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, 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, balance_electricity,
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, 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),
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,
show_monthly_total,
custom_service_fee_price,
custom_service_fee_name if custom_service_fee_name else None,
show_custom_service_fee,
show_total_amount,
total_amount_name if total_amount_name else None,
balance_electricity,
sort_order
)
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
@config_bp.route('/config/<int:config_id>', methods=['PUT'])
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')
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', {})
show_monthly_total = 1 if data.get('show_monthly_total') else 0
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')
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')
# 找平电量开关默认开启1=开启0=关闭)
balance_electricity = data.get('balance_electricity')
if balance_electricity is None:
balance_electricity = 1
else:
balance_electricity = 1 if balance_electricity else 0
# 查询原配置,检查拆分是否变化
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,
selected_fields = %s, sum_fields = %s, time_periods = %s,
merge_telecom = %s, telecom_vehicle_no = %s, field_custom_names = %s,
show_monthly_total = %s, custom_service_fee_price = %s, custom_service_fee_name = %s,
show_custom_service_fee = %s, show_total_amount = %s, total_amount_name = %s,
balance_electricity = %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),
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,
show_monthly_total,
custom_service_fee_price,
custom_service_fee_name if custom_service_fee_name else None,
show_custom_service_fee,
show_total_amount,
total_amount_name if total_amount_name else None,
balance_electricity,
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:
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
@config_bp.route('/config/<int:config_id>', methods=['DELETE'])
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
@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
(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, balance_electricity, 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, %s, NOW(), NOW())''',
(
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),
original.get('show_monthly_total', 0),
original.get('custom_service_fee_price'),
original.get('custom_service_fee_name'),
original.get('show_custom_service_fee', 0),
original.get('show_total_amount', 0),
original.get('total_amount_name'),
# 找平电量复制原配置的设置若原配置无此字段NULL则默认开启(1)
original.get('balance_electricity') if original.get('balance_electricity') is not None else 1,
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
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'
)
log_info(f"[配置API] 所有配置数量: {len(all_configs)}", 'api')
# 找到当前配置的索引
current_index = None
for i, c in enumerate(all_configs):
if c['id'] == config_id:
current_index = i
break
if current_index is None:
log_warning(f"[配置API] 未找到配置ID: {config_id}", 'api')
return jsonify({'success': False, 'message': '配置不存在'}), 404
log_info(f"[配置API] 当前配置索引: {current_index}", 'api')
# 计算目标索引
if direction == 'up':
target_index = current_index - 1
elif direction == 'down':
target_index = current_index + 1
else:
return jsonify({'success': False, 'message': '无效的移动方向'}), 400
# 边界检查
if target_index < 0 or target_index >= len(all_configs):
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]
# 全量重排:按新顺序给每个配置重新赋值 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'])
)
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
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=是'},
{'key': 'custom_service_fee_price', 'name': '自定义服务费单价(元/kWh)', 'required': False, 'comment': '设置后按充电电量×单价计算自定义服务费'},
{'key': 'custom_service_fee_name', 'name': '自定义服务费表头名称', 'required': False, 'comment': '报表中显示的列名,留空则使用默认名称'},
{'key': 'is_active', 'name': '是否启用', 'required': False, 'comment': '0=禁用, 1=启用'},
{'key': 'sort_order', 'name': '排序号', 'required': False, 'comment': '数字越小越靠前'},
]
@config_bp.route('/config/export', methods=['GET'])
def export_configs():
"""导出所有配置为Excel文件"""
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
configs = execute_query(
'SELECT * FROM t_daily_report_config ORDER BY sort_order ASC, id ASC'
)
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)}"
return response
except Exception as e:
import traceback
traceback.print_exc()
return jsonify({'success': False, 'message': f'导出失败: {str(e)}'}), 500
@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',
'custom_service_fee_price': '0.8',
'custom_service_fee_name': '服务费(自定义)',
'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
@config_bp.route('/config/import', methods=['POST'])
def import_configs():
"""从Excel文件导入配置"""
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
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
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:
config_name = config_data.get('config_name', '').strip()
split_type = config_data.get('split_type', '').strip()
split_value = config_data.get('split_value', '').strip()
if not config_name:
fail_count += 1
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}行:缺少拆分值')
continue
new_id = int(datetime.now().timestamp() * 1000) + idx
new_name = f"{config_name}_导入"
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
current_max_sort += 1
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
merge_telecom = config_data.get('merge_telecom', '0').strip()
merge_telecom = 1 if merge_telecom in ('1', '', 'true', 'True') else 0
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
is_active = config_data.get('is_active', '0').strip()
is_active = 1 if is_active in ('1', '', 'true', 'True') else 0
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
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,
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())
"""
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),
merge_telecom,
config_data.get('telecom_vehicle_no', ''),
json.dumps(field_custom_names, ensure_ascii=False) if field_custom_names else None,
show_monthly_total,
custom_service_fee_price,
custom_service_fee_name,
is_active,
sort_order
)
execute_insert(sql, params)
success_count += 1
except Exception as item_error:
fail_count += 1
fail_messages.append(f'{idx+3}行:{str(item_error)}')
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