增加t自定义服务费单价

This commit is contained in:
2026-07-15 11:23:02 +08:00
parent 9c3c1e26cc
commit 96cca34de0
6 changed files with 245 additions and 28 deletions

View File

@@ -43,6 +43,13 @@ def create_config():
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')
if not config_name or not split_type or not split_value:
return jsonify({'success': False, 'message': '缺少必填字段'}), 400
@@ -61,8 +68,8 @@ def create_config():
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, is_active, sort_order, create_time, update_time)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 1, %s, NOW(), NOW())
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, 1, %s, NOW(), NOW())
"""
params = (
@@ -78,6 +85,8 @@ def create_config():
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,
sort_order
)
@@ -108,13 +117,21 @@ def update_config(config_id):
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')
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, update_time = NOW()
show_monthly_total = %s, custom_service_fee_price = %s, custom_service_fee_name = %s,
update_time = NOW()
WHERE id = %s
"""
@@ -130,6 +147,8 @@ def update_config(config_id):
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,
config_id
)
@@ -172,8 +191,8 @@ def copy_config(config_id):
# 插入新配置
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, is_active, sort_order, create_time, update_time)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())''',
(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())''',
(
new_id,
new_name,
@@ -187,6 +206,8 @@ def copy_config(config_id):
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['is_active'],
sort_order
)
@@ -286,6 +307,8 @@ EXCEL_COLUMNS = [
{'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': '数字越小越靠前'},
]
@@ -432,6 +455,8 @@ def download_template():
'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',
}
@@ -598,12 +623,25 @@ def import_configs():
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, is_active, sort_order, create_time, update_time)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())
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 = (
@@ -619,6 +657,8 @@ def import_configs():
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
)

View File

@@ -21,6 +21,9 @@ def init_database_tables():
merge_telecom TINYINT NULL COMMENT '是否合并特来电数据',
telecom_vehicle_no VARCHAR(500) NULL COMMENT '特来电车量自编号',
field_custom_names VARCHAR(2000) NULL COMMENT '字段自定义报表名头',
show_monthly_total TINYINT NULL COMMENT '是否显示当月充电量总计',
custom_service_fee_price DECIMAL(10,4) NULL COMMENT '自定义服务费单价(元/kWh)',
custom_service_fee_name VARCHAR(100) NULL COMMENT '自定义服务费报表表头名称',
is_active TINYINT NULL COMMENT '是否启用',
sort_order INT NULL COMMENT '排序顺序',
create_time DATETIME NULL COMMENT '创建时间',
@@ -118,7 +121,31 @@ def init_database_tables():
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}")
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)' "
)
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 '自定义服务费报表表头名称' "
)
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:

View File

@@ -475,7 +475,9 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
show_monthly_total=config.get('show_monthly_total', 0) == 1,
config_id=config['id'],
report_date=start_time.strftime('%Y-%m-%d'),
sort_order=config.get('sort_order')
sort_order=config.get('sort_order'),
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}")
@@ -565,7 +567,8 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
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):
show_monthly_total=False, config_id=None, report_date=None, sort_order=None,
custom_service_fee_price=None, custom_service_fee_name=None):
"""
生成Excel文件
@@ -697,6 +700,21 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
cell.alignment = header_alignment
cell.border = thin_border
# 自定义服务费列表头(如果设置了单价)
has_custom_service_fee = custom_service_fee_price is not None and custom_service_fee_price != ''
if has_custom_service_fee:
custom_fee_col = len(selected_fields) + 2
cell = ws.cell(row=header_row, column=custom_fee_col)
if custom_service_fee_name and str(custom_service_fee_name).strip():
header_name = str(custom_service_fee_name).strip()
else:
header_name = f'自定义服务费({custom_service_fee_price}元/kWh)'
cell.value = header_name
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_alignment
cell.border = thin_border
ws.row_dimensions[header_row].height = 25
# 写入数据
@@ -747,6 +765,19 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
if is_even:
cell.fill = even_row_fill
# 自定义服务费列(如果设置了单价)
if has_custom_service_fee:
custom_fee_col = len(selected_fields) + 2
cell = ws.cell(row=row_idx, column=custom_fee_col)
charge_degree = float(order.get('charge_degree', 0) or 0)
custom_fee = round(charge_degree * float(custom_service_fee_price), 2)
cell.value = custom_fee
cell.border = thin_border
cell.alignment = Alignment(horizontal='right', vertical='center')
cell.number_format = '0.00'
if is_even:
cell.fill = even_row_fill
ws.row_dimensions[row_idx].height = 22
# 写入求和行
@@ -769,6 +800,19 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
cell.alignment = Alignment(horizontal='right', vertical='center')
cell.number_format = '0.000'
# 自定义服务费合计
if has_custom_service_fee:
custom_fee_col = len(selected_fields) + 2
cell = ws.cell(row=sum_row, column=custom_fee_col)
cell.fill = sum_fill
cell.border = thin_border
total_charge_degree = sum_results.get('charge_degree', 0)
total_custom_fee = round(float(total_charge_degree) * float(custom_service_fee_price), 2)
cell.value = total_custom_fee
cell.font = sum_font
cell.alignment = Alignment(horizontal='right', vertical='center')
cell.number_format = '0.00'
ws.row_dimensions[sum_row].height = 25
# 写入当月充电量累计总计行(从求和采集表查询,对账后数据)
@@ -807,7 +851,10 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
ws.cell(row=monthly_row, column=1).border = thin_border
# 其他列也填充背景色
for col_idx in range(2, len(selected_fields) + 2):
total_cols = len(selected_fields) + 2
if has_custom_service_fee:
total_cols += 1
for col_idx in range(2, total_cols):
cell = ws.cell(row=monthly_row, column=col_idx)
cell.fill = monthly_fill
cell.border = thin_border
@@ -848,6 +895,18 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results,
col_letter = get_column_letter(col_idx)
ws.column_dimensions[col_letter].width = min(max_length + 2, 50)
# 自定义服务费列宽度
if has_custom_service_fee:
custom_fee_col = len(selected_fields) + 2
if custom_service_fee_name and str(custom_service_fee_name).strip():
header_name = str(custom_service_fee_name).strip()
else:
header_name = f'自定义服务费({custom_service_fee_price}元/kWh)'
cn_count = sum(1 for c in str(header_name) if '\u4e00' <= c <= '\u9fff')
max_length = len(str(header_name)) + cn_count
col_letter = get_column_letter(custom_fee_col)
ws.column_dimensions[col_letter].width = min(max_length + 2, 50)
# 冻结首行
ws.freeze_panes = 'A2'

View File

@@ -36,7 +36,7 @@ def collect_sum_data_from_history(history_id=None):
collected_count = 0
skipped_count = 0
config_sort_cache = {} # 缓存配置的排序号
config_cache = {} # 缓存配置的排序号和自定义服务费单价
for history in history_list:
sum_results = history.get('sum_results')
@@ -47,23 +47,36 @@ def collect_sum_data_from_history(history_id=None):
elif sum_results is None:
sum_results = {}
# 获取配置的排序号(缓存)
# 获取配置信息(排序号、自定义服务费单价)(缓存)
config_id = history.get('config_id')
sort_order = None
custom_service_fee_price = None
if config_id:
if config_id in config_sort_cache:
sort_order = config_sort_cache[config_id]
if config_id in config_cache:
sort_order = config_cache[config_id].get('sort_order')
custom_service_fee_price = config_cache[config_id].get('custom_service_fee_price')
custom_service_fee_name = config_cache[config_id].get('custom_service_fee_name')
else:
try:
cfg_result = execute_query(
'SELECT sort_order FROM t_daily_report_config WHERE id = %s LIMIT 1',
'SELECT sort_order, custom_service_fee_price, custom_service_fee_name FROM t_daily_report_config WHERE id = %s LIMIT 1',
(config_id,)
)
if cfg_result and cfg_result[0]['sort_order'] is not None:
sort_order = cfg_result[0]['sort_order']
config_sort_cache[config_id] = sort_order
if cfg_result:
sort_order = cfg_result[0].get('sort_order')
custom_service_fee_price = cfg_result[0].get('custom_service_fee_price')
custom_service_fee_name = cfg_result[0].get('custom_service_fee_name')
config_cache[config_id] = {
'sort_order': sort_order,
'custom_service_fee_price': custom_service_fee_price,
'custom_service_fee_name': custom_service_fee_name
}
except Exception:
config_sort_cache[config_id] = None
config_cache[config_id] = {
'sort_order': None,
'custom_service_fee_price': None,
'custom_service_fee_name': None
}
# 强制确保有充电电量charge_degree
if 'charge_degree' not in sum_results or sum_results['charge_degree'] is None:
@@ -117,6 +130,44 @@ def collect_sum_data_from_history(history_id=None):
)
execute_insert(sql, params)
collected_count += 1
# 如果配置了自定义服务费单价,自动计算并采集自定义服务费
if custom_service_fee_price is not None and custom_service_fee_price != '' and sum_results.get('charge_degree') is not None:
try:
charge_degree = float(sum_results['charge_degree'])
custom_service_fee = round(charge_degree * float(custom_service_fee_price), 2)
if custom_service_fee_name and str(custom_service_fee_name).strip():
fee_display_name = str(custom_service_fee_name).strip()
else:
fee_display_name = f'自定义服务费({custom_service_fee_price}元/kWh)'
sum_data_id = int(datetime.now().timestamp() * 1000000) + collected_count
sql = '''
INSERT INTO t_daily_report_sum_data
(id, report_date, config_id, config_name, split_type, split_value, split_name,
sum_field_key, sum_field_name, sum_value, total_orders, sort_order, data_source, remark,
create_time, update_time)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())
'''
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_service_fee',
fee_display_name,
custom_service_fee,
history.get('total_orders', 0),
sort_order,
'auto',
f'自动计算:充电电量{charge_degree}kWh × 单价{custom_service_fee_price}元/kWh'
)
execute_insert(sql, params)
collected_count += 1
except Exception as fee_error:
print(f"[采集] 计算自定义服务费失败: {fee_error}")
return {
'success': True,

View File

@@ -87,6 +87,14 @@ function showCreateModal() {
const showMonthlyTotalCheckbox = document.getElementById('show-monthly-total');
if (showMonthlyTotalCheckbox) showMonthlyTotalCheckbox.checked = false;
// 重置自定义服务费单价
const customServiceFeePriceInput = document.getElementById('custom-service-fee-price');
if (customServiceFeePriceInput) customServiceFeePriceInput.value = '';
// 重置自定义服务费表头名称
const customServiceFeeNameInput = document.getElementById('custom-service-fee-name');
if (customServiceFeeNameInput) customServiceFeeNameInput.value = '';
document.getElementById('config-modal').classList.add('active');
}
@@ -479,6 +487,14 @@ async function saveConfig() {
// 获取月度总计开关
const showMonthlyTotal = document.getElementById('show-monthly-total')?.checked || false;
// 获取自定义服务费单价
const customServiceFeePriceInput = document.getElementById('custom-service-fee-price');
const customServiceFeePrice = customServiceFeePriceInput?.value !== '' ? parseFloat(customServiceFeePriceInput.value) : null;
// 获取自定义服务费表头名称
const customServiceFeeNameInput = document.getElementById('custom-service-fee-name');
const customServiceFeeName = customServiceFeeNameInput?.value.trim() || null;
if (!configName || !splitType || !splitValue) {
alert('请填写所有必填字段');
@@ -519,7 +535,9 @@ async function saveConfig() {
merge_telecom: mergeTelecom,
telecom_vehicle_no: telecomVehicleNo,
field_custom_names: fieldCustomNamesData,
show_monthly_total: showMonthlyTotal
show_monthly_total: showMonthlyTotal,
custom_service_fee_price: customServiceFeePrice,
custom_service_fee_name: customServiceFeeName
};
try {
@@ -600,6 +618,18 @@ async function editConfig(configId) {
showMonthlyTotalCheckbox.checked = config.show_monthly_total === 1 || config.show_monthly_total === true;
}
// 加载自定义服务费单价
const customServiceFeePriceInput = document.getElementById('custom-service-fee-price');
if (customServiceFeePriceInput) {
customServiceFeePriceInput.value = config.custom_service_fee_price !== null && config.custom_service_fee_price !== undefined ? config.custom_service_fee_price : '';
}
// 加载自定义服务费表头名称
const customServiceFeeNameInput = document.getElementById('custom-service-fee-name');
if (customServiceFeeNameInput) {
customServiceFeeNameInput.value = config.custom_service_fee_name || '';
}
document.getElementById('config-modal').classList.add('active');
}
}

View File

@@ -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=2026071405">
<link rel="stylesheet" href="/public/static/css/style.css?v=2026071502">
</head>
<body>
<div class="container">
@@ -241,6 +241,16 @@
<input type="text" id="telecom-vehicle-no" class="form-control" placeholder="例如:车 001,车 002">
</div>
<div class="form-group">
<label>自定义服务费单价(元/kWh</label>
<input type="number" id="custom-service-fee-price" class="form-control" step="0.0001" placeholder="留空则不启用,设置后按充电电量×单价计算自定义服务费" style="width: 200px;">
</div>
<div class="form-group">
<label>自定义服务费表头名称:</label>
<input type="text" id="custom-service-fee-name" class="form-control" placeholder="留空则使用默认名称(如:自定义服务费(0.8元/kWh)" style="width: 300px;">
</div>
<div class="form-group">
<label>分时段电量统计:</label>
<div id="time-period-editor">
@@ -340,11 +350,11 @@
</div>
<!-- JavaScript 文件 -->
<script src="/public/static/js/common.js?v=2026071405"></script>
<script src="/public/static/js/config.js?v=2026071405"></script>
<script src="/public/static/js/entity.js?v=2026071405"></script>
<script src="/public/static/js/report.js?v=2026071405"></script>
<script src="/public/static/js/history.js?v=2026071405"></script>
<script src="/public/static/js/sum_data.js?v=2026071405"></script>
<script src="/public/static/js/common.js?v=2026071502"></script>
<script src="/public/static/js/config.js?v=2026071502"></script>
<script src="/public/static/js/entity.js?v=2026071502"></script>
<script src="/public/static/js/report.js?v=2026071502"></script>
<script src="/public/static/js/history.js?v=2026071502"></script>
<script src="/public/static/js/sum_data.js?v=2026071502"></script>
</body>
</html>