悠修复自定义实收金额和求和采集功能增强

This commit is contained in:
2026-07-28 14:16:46 +08:00
parent e72acb58ec
commit 4a1b0f71c3
9 changed files with 454 additions and 39 deletions

View File

@@ -27,6 +27,9 @@ sum_data_bp = Blueprint('sum_data', __name__, url_prefix='/api')
# 认证蓝图
from .auth_api import auth_bp
# 全局配置蓝图
from .global_config_api import global_config_api
# 导入各模块的路由
from . import config_api, entity_api, field_api, report_api, init_api, download_api, sum_data_api
@@ -41,3 +44,4 @@ def register_blueprints(app):
app.register_blueprint(download_bp)
app.register_blueprint(sum_data_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(global_config_api)

View File

@@ -0,0 +1,36 @@
from flask import Blueprint, jsonify, request
from lib.db import execute_query, execute_update
import json
global_config_api = Blueprint('global_config_api', __name__)
@global_config_api.route('/api/global-config/sum-data-fields', methods=['GET'])
def get_sum_data_fields():
"""获取求和数据采集字段配置"""
try:
result = execute_query("SELECT sum_data_fields FROM t_daily_report_global_config WHERE id = 1")
if result and result[0]['sum_data_fields']:
fields = json.loads(result[0]['sum_data_fields'])
return jsonify({'success': True, 'data': fields})
return jsonify({'success': True, 'data': []})
except Exception as e:
return jsonify({'success': False, 'message': str(e)}), 500
@global_config_api.route('/api/global-config/sum-data-fields', methods=['POST'])
def save_sum_data_fields():
"""保存求和数据采集字段配置"""
try:
data = request.get_json()
fields = data.get('sum_data_fields', [])
fields_json = json.dumps(fields)
execute_update(
"UPDATE t_daily_report_global_config SET sum_data_fields = %s, update_time = NOW() WHERE id = 1",
(fields_json,)
)
return jsonify({'success': True, 'message': '配置保存成功'})
except Exception as e:
return jsonify({'success': False, 'message': str(e)}), 500

View File

@@ -182,6 +182,33 @@ def init_database_tables():
"total_amount_name VARCHAR(100) NULL COMMENT '实收金额报表表头名称'",
'total_amount_name'
)
# 创建全局配置表
create_global_config_table = """
CREATE TABLE IF NOT EXISTS t_daily_report_global_config (
id BIGINT NOT NULL DEFAULT 1 COMMENT '主键(固定为1)',
sum_data_fields VARCHAR(1000) NULL COMMENT '求和数据采集和显示字段列表(JSON格式)',
create_time DATETIME NULL COMMENT '创建时间',
update_time DATETIME NULL COMMENT '更新时间'
)
UNIQUE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES("replication_num" = "1")
"""
execute_update(create_global_config_table)
_set_table_comment('t_daily_report_global_config', '日报全局配置表')
log_info("[数据库] t_daily_report_global_config 表已就绪", 'db_init')
# 初始化全局配置记录(如果不存在)
try:
exists = execute_query("SELECT COUNT(*) as cnt FROM t_daily_report_global_config WHERE id = 1")
if not exists or exists[0]['cnt'] == 0:
execute_update(
"INSERT INTO t_daily_report_global_config (id, create_time, update_time) VALUES (1, NOW(), NOW())"
)
log_info("[数据库] 初始化全局配置记录成功", 'db_init')
except Exception as e:
log_warning(f"[数据库] 初始化全局配置记录失败: {e}", 'db_init')
# 数据迁移:给 sort_order 为 NULL 的配置记录按 id 顺序赋值
try:

View File

@@ -172,7 +172,7 @@ FIELD_MAPPING = {
# 自定义计算字段(虚拟字段)
'custom_service_fee': '自定义服务费(元)',
'total_amount': '实收金额(元)',
'custom_total_amount': '自定义实收总金额(元)',
# 车辆信息(从 t_car 表关联)
'car_bus_path': '公交线路',

View File

@@ -319,6 +319,16 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
selected_fields = json.loads(config['selected_fields'])
sum_fields = json.loads(config['sum_fields']) if config['sum_fields'] else []
# 根据配置自动添加虚拟字段到选中字段列表
show_custom_service_fee = config.get('show_custom_service_fee', 0) == 1
show_total_amount = config.get('show_total_amount', 0) == 1
if show_custom_service_fee and 'custom_service_fee' not in selected_fields:
selected_fields.append('custom_service_fee')
if show_total_amount and 'custom_total_amount' not in selected_fields:
selected_fields.append('custom_total_amount')
# 设置时间范围
if not start_time or not end_time:
now = get_beijing_time()
@@ -340,17 +350,18 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
report_date = start_time.strftime('%Y-%m-%d')
delete_old_history(config_id, report_date, config['split_type'], config['split_value'])
# 构建查询SQL - 过滤掉虚拟字段(时段电量、自定义服务费、实收金额、车辆信息等)
# 构建查询SQL - 过滤掉虚拟字段(时段电量、自定义服务费、自定义实收总金额、车辆信息等)
# 虚拟字段不是数据库表中的实际字段,需要动态计算或关联查询
VIRTUAL_FIELDS = {'sharp_electricity', 'peak_electricity', 'flat_electricity', 'valley_electricity', 'custom_service_fee', 'total_amount', 'car_bus_path', 'car_sn'}
# 注意:total_amount 是旧版虚拟字段名,为了向后兼容也需要包含在内
VIRTUAL_FIELDS = {'sharp_electricity', 'peak_electricity', 'flat_electricity', 'valley_electricity', 'custom_service_fee', 'custom_total_amount', 'total_amount', 'car_bus_path', 'car_sn'}
db_fields = [f for f in selected_fields if f not in VIRTUAL_FIELDS]
# 确保 charge_degree 总是被查询(用于计算总电量)
if 'charge_degree' not in db_fields:
db_fields.append('charge_degree')
# 如果选择了实收金额字段,需要确保查询 charge_elecfee_amount(电费金额)
if 'total_amount' in selected_fields and 'charge_elecfee_amount' not in db_fields:
# 如果选择了自定义实收总金额字段,需要确保查询 charge_elecfee_amount(电费金额)
if 'custom_total_amount' in selected_fields and 'charge_elecfee_amount' not in db_fields:
db_fields.append('charge_elecfee_amount')
# 确保至少有 order_no 字段用于关联分时数据
@@ -506,6 +517,31 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
log_info(f"[日报生成] 已计算 {len(time_period_electricity)} 条订单的分时段电量", 'report')
# 计算虚拟字段(自定义服务费、自定义实收总金额)并添加到订单数据中
# 这样求和数据才能正确计算这些字段的总和
has_custom_service_fee = config.get('custom_service_fee_price') is not None and str(config.get('custom_service_fee_price', '')).strip() != ''
if has_custom_service_fee:
custom_service_fee_price = float(config['custom_service_fee_price'])
for order in orders:
charge_degree = float(order.get('charge_degree', 0) or 0)
custom_service_fee = round(charge_degree * custom_service_fee_price, 2)
order['custom_service_fee'] = custom_service_fee
# 计算自定义实收总金额(实收电费 + 自定义服务费)
actual_money = float(order.get('charge_elecfee_amount', 0) or 0)
custom_total_amount = round(actual_money + custom_service_fee, 2)
order['custom_total_amount'] = custom_total_amount
# 兼容旧版字段名 total_amount
order['total_amount'] = custom_total_amount
else:
# 如果没有自定义服务费,自定义实收总金额 = 实收电费
for order in orders:
actual_money = float(order.get('charge_elecfee_amount', 0) or 0)
custom_total_amount = round(actual_money, 2)
order['custom_total_amount'] = custom_total_amount
# 兼容旧版字段名 total_amount
order['total_amount'] = custom_total_amount
# 计算求和(保留三位小数)
sum_results = {}
for field in sum_fields:
@@ -523,6 +559,27 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
sum_results['charge_degree'] = round(total_charge_degree, 3)
log_info(f"[日报生成] 强制采集充电电量: {sum_results['charge_degree']} kWh", 'report')
# 强制采集实收电费(charge_elecfee_amount),用于计算自定义实收总金额的求和
if 'charge_elecfee_amount' not in sum_results and orders:
has_charge_elecfee_amount = any('charge_elecfee_amount' in order for order in orders)
if has_charge_elecfee_amount:
total_charge_elecfee_amount = sum(float(order.get('charge_elecfee_amount', 0) or 0) for order in orders)
sum_results['charge_elecfee_amount'] = round(total_charge_elecfee_amount, 3)
# 强制采集自定义服务费(custom_service_fee),直接使用订单中已计算好的值求和
if 'custom_service_fee' not in sum_results and orders:
has_custom_service_fee_field = any('custom_service_fee' in order for order in orders)
if has_custom_service_fee_field:
total_custom_service_fee = sum(float(order.get('custom_service_fee', 0) or 0) for order in orders)
sum_results['custom_service_fee'] = round(total_custom_service_fee, 2)
# 强制采集自定义实收总金额(custom_total_amount),直接使用订单中已计算好的值求和
if 'custom_total_amount' not in sum_results and orders:
has_custom_total_amount = any('custom_total_amount' in order for order in orders)
if has_custom_total_amount:
total_custom_total_amount = sum(float(order.get('custom_total_amount', 0) or 0) for order in orders)
sum_results['custom_total_amount'] = round(total_custom_total_amount, 2)
# 构建ID到名称的映射
id_name_maps = build_id_to_name_map(orders, selected_fields)
@@ -772,11 +829,11 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
display_name = f'自定义服务费({custom_service_fee_price}元/kWh)'
else:
display_name = '自定义服务费'
elif field == 'total_amount':
elif field == 'custom_total_amount' or field == 'total_amount':
if total_amount_name and str(total_amount_name).strip():
display_name = str(total_amount_name).strip()
else:
display_name = '实收金额'
display_name = '自定义实收总金额'
elif field in field_custom_names and field_custom_names[field]:
display_name = field_custom_names[field]
elif field in id_name_maps and id_name_maps[field]:
@@ -810,19 +867,11 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
cell = ws.cell(row=row_idx, column=col_idx)
if field == 'custom_service_fee':
if has_custom_service_fee:
charge_degree = float(order.get('charge_degree', 0) or 0)
value = round(charge_degree * float(custom_service_fee_price), 2)
else:
value = ''
elif field == 'total_amount':
actual_money = float(order.get('charge_elecfee_amount', 0) or 0)
if has_custom_service_fee:
charge_degree = float(order.get('charge_degree', 0) or 0)
custom_fee = round(charge_degree * float(custom_service_fee_price), 2)
value = round(actual_money + custom_fee, 2)
else:
value = round(actual_money, 2)
# 直接使用订单数据中已计算好的值
value = order.get('custom_service_fee', '')
elif field == 'custom_total_amount' or field == 'total_amount':
# 直接使用订单数据中已计算好的值(兼容旧版字段名 total_amount)
value = order.get('custom_total_amount', order.get('total_amount', ''))
else:
value = order.get(field, '')
@@ -842,7 +891,7 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
cell.border = thin_border
# 数字字段靠右对齐,其他居中或靠左
if field in numeric_fields or field in ['custom_service_fee', 'total_amount']:
if field in numeric_fields or field in ['custom_service_fee', 'custom_total_amount', 'total_amount']:
cell.alignment = Alignment(horizontal='right', vertical='center')
if isinstance(value, (int, float)):
cell.number_format = '0.00'
@@ -873,21 +922,30 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
cell.border = thin_border
if field == 'custom_service_fee':
if has_custom_service_fee:
# 直接使用订单中已计算好的值求和,确保与数据行一致
if 'custom_service_fee' in sum_results:
value = sum_results['custom_service_fee']
elif has_custom_service_fee:
total_charge_degree = sum_results.get('charge_degree', 0)
value = round(float(total_charge_degree) * float(custom_service_fee_price), 2)
cell.value = value
cell.font = sum_font
cell.alignment = Alignment(horizontal='right', vertical='center')
cell.number_format = '0.00'
elif field == 'total_amount':
total_actual_money = sum_results.get('charge_elecfee_amount', 0)
if has_custom_service_fee:
total_charge_degree = sum_results.get('charge_degree', 0)
total_custom_fee = round(float(total_charge_degree) * float(custom_service_fee_price), 2)
value = round(float(total_actual_money) + total_custom_fee, 2)
else:
value = round(float(total_actual_money), 2)
value = ''
cell.value = value
cell.font = sum_font
cell.alignment = Alignment(horizontal='right', vertical='center')
cell.number_format = '0.00'
elif field == 'custom_total_amount' or field == 'total_amount':
# 直接使用订单中已计算好的值求和,确保与数据行一致
if 'custom_total_amount' in sum_results:
value = sum_results['custom_total_amount']
else:
total_actual_money = sum_results.get('charge_elecfee_amount', 0)
if has_custom_service_fee:
total_charge_degree = sum_results.get('charge_degree', 0)
total_custom_fee = round(float(total_charge_degree) * float(custom_service_fee_price), 2)
value = round(float(total_actual_money) + total_custom_fee, 2)
else:
value = round(float(total_actual_money), 2)
cell.value = value
cell.font = sum_font
cell.alignment = Alignment(horizontal='right', vertical='center')
@@ -1022,7 +1080,7 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
col_widths[col_idx] = min(max_length + 2, 50)
# 标记数值类型字段(求和类列)
if field in numeric_fields or field in ['custom_service_fee', 'total_amount']:
if field in numeric_fields or field in ['custom_service_fee', 'custom_total_amount', 'total_amount']:
numeric_cols.append(col_idx)
# 计算数值类型列的最大宽度,使所有求和类列宽度一致

View File

@@ -51,6 +51,17 @@ def collect_sum_data_from_history(history_id=None, start_date=None, end_date=Non
skipped_count = 0
config_cache = {} # 缓存配置的排序号和自定义服务费单价
# 获取全局配置的求和数据采集字段
sum_data_fields = None
try:
global_config_result = execute_query(
'SELECT sum_data_fields FROM t_daily_report_global_config WHERE id = 1 LIMIT 1'
)
if global_config_result and global_config_result[0].get('sum_data_fields'):
sum_data_fields = json.loads(global_config_result[0]['sum_data_fields'])
except Exception:
pass
for history in history_list:
sum_results = history.get('sum_results')
@@ -115,6 +126,13 @@ def collect_sum_data_from_history(history_id=None, start_date=None, end_date=Non
(history['report_date'], history['config_id'], history['split_value'])
)
# 根据配置过滤采集字段
# 如果配置了 sum_data_fields,则只采集这些字段(charge_degree 始终采集)
if sum_data_fields and isinstance(sum_data_fields, list):
filtered_fields = set(sum_data_fields)
filtered_fields.add('charge_degree') # 充电电量始终采集
sum_results = {k: v for k, v in sum_results.items() if k in filtered_fields}
# 逐条插入求和字段数据
for field_key, field_value in sum_results.items():
sum_data_id = int(datetime.now().timestamp() * 1000000) + collected_count
@@ -179,6 +197,30 @@ def collect_sum_data_from_history(history_id=None, start_date=None, end_date=Non
)
execute_insert(sql, params)
collected_count += 1
# 计算自定义实收总金额(实收电费 + 自定义服务费)
# 实收电费从 sum_results 中获取(charge_elecfee_amount)
charge_elecfee_amount = float(sum_results.get('charge_elecfee_amount', 0) or 0)
custom_total_amount = round(charge_elecfee_amount + custom_service_fee, 2)
sum_data_id = int(datetime.now().timestamp() * 1000000) + collected_count
params = (
sum_data_id,
history['report_date'],
history['config_id'],
history.get('config_name', ''),
history.get('split_type', ''),
history.get('split_value', ''),
history.get('split_name', ''),
'custom_total_amount',
'自定义实收总金额',
custom_total_amount,
history.get('total_orders', 0),
sort_order,
'auto',
f'自动计算:实收电费{charge_elecfee_amount}元 + 自定义服务费{custom_service_fee}元'
)
execute_insert(sql, params)
collected_count += 1
except Exception as fee_error:
log_error(f"[采集] 计算自定义服务费失败: {fee_error}", 'sum_data')