973 lines
36 KiB
Python
973 lines
36 KiB
Python
import json
|
|
import os
|
|
import sys
|
|
from typing import Dict, List, Any, Optional, Tuple, Set
|
|
from equipment_calculation.expressioncalculator import ExpressionCalculator
|
|
from equipment_calculation.bcl_utils import (
|
|
ZjQuantityBCLContext,
|
|
ZjProjectBCLContext,
|
|
ZjBillBCLContext,
|
|
init_bcl_calculator,
|
|
BCLVariant,
|
|
calculator,
|
|
create_project_contexts,
|
|
BCLDataSourceItem,
|
|
BCLDataSourceContext,
|
|
create_list_from_node,
|
|
create_node_from_type,
|
|
)
|
|
from equipment_calculation.item_acquisition import (
|
|
get_cost_table_children,
|
|
get_quantity_nodes,
|
|
get_bill_cost_table,
|
|
find_cost_table,
|
|
get_bill_node_by_id,
|
|
load_project_data,
|
|
)
|
|
|
|
# 缓存已计算过的费用
|
|
calculated_fees = {}
|
|
|
|
# 添加一个全局变量和一个缓存字典来存储清单数量
|
|
_BILL_QUANTITY = 1.0
|
|
bill_quantity_cache = {}
|
|
|
|
|
|
def process_DXdata(json_data):
|
|
"""
|
|
处理 projectData 中的线路特征段数据,计算每条 bpBillZhXsTable 记录的加权值,
|
|
并返回一个字典列表,每个字典包含 '特征段' 和对应的 ItemName: 计算结果。
|
|
|
|
:param json_data: 解析后的 JSON 数据(字典格式)
|
|
:return: 字典列表,例如 [{"特征段": "1", "工地运输...": "21.8"}, ...]
|
|
"""
|
|
result_list = []
|
|
|
|
# 遍历每个线路特征段
|
|
for segment in json_data.get("projectData", {}).get("线路特征段", []):
|
|
seg_name = segment.get("Name", "")
|
|
# 提取特征段中的数字,例如 "特征段1" -> "1"
|
|
seg_number = "".join(filter(str.isdigit, seg_name))
|
|
if not seg_number:
|
|
seg_number = "0" # 默认值
|
|
|
|
# 获取 bpBillZhXsTable 列表
|
|
table_list = segment.get("bpBillZhXsTable", [])
|
|
for item in table_list:
|
|
try:
|
|
# 提取字段并转换为浮点数
|
|
KnapScale = float(item.get("KnapScale", 0))
|
|
Knap = float(item.get("Knap", 0))
|
|
HillScale = float(item.get("HillScale", 0))
|
|
Hill = float(item.get("Hill", 0))
|
|
EdelweissScale = float(item.get("EdelweissScale", 0))
|
|
edelweiss = float(item.get("edelweiss", 0))
|
|
MountainScale = float(item.get("MountainScale", 0))
|
|
Mountain = float(item.get("Mountain", 0))
|
|
SloughScale = float(item.get("SloughScale", 0))
|
|
Slough = float(item.get("Slough", 0))
|
|
RiverScale = float(item.get("RiverScale", 0))
|
|
River = float(item.get("River", 0))
|
|
DesertScale = float(item.get("DesertScale", 0))
|
|
Desert = float(item.get("Desert", 0))
|
|
|
|
# 执行计算
|
|
total = (
|
|
(KnapScale * Knap)
|
|
+ (HillScale * Hill)
|
|
+ (EdelweissScale * edelweiss)
|
|
+ (MountainScale * Mountain)
|
|
+ (SloughScale * Slough)
|
|
+ (RiverScale * River)
|
|
+ (DesertScale * Desert)
|
|
) * 0.01
|
|
|
|
# 保留一位小数,格式化为字符串
|
|
calculated_value = f"{total:.1f}"
|
|
|
|
# 获取 ItemName
|
|
item_name = item.get("ItemName", "未知项目")
|
|
|
|
# 构建结果字典
|
|
item_dict = {"特征段": seg_number, item_name: calculated_value}
|
|
|
|
# 添加到结果列表
|
|
result_list.append(item_dict)
|
|
|
|
except (ValueError, TypeError) as e:
|
|
print(f"数据转换错误,跳过该项: {e}")
|
|
continue
|
|
|
|
return result_list
|
|
|
|
|
|
# 在create_list_from_node函数后添加一个包装函数
|
|
def create_list_from_node_with_quantity(node, quantity=None):
|
|
"""创建清单对象并设置数量"""
|
|
from equipment_calculation.bcl_utils import create_list_from_node
|
|
|
|
bill_obj = create_list_from_node(node)
|
|
|
|
# 设置清单数量
|
|
bill_quantity = quantity if quantity is not None else node.get("数量")
|
|
|
|
# 设置到对象属性
|
|
bill_obj.quantity = bill_quantity
|
|
|
|
# 保存到全局变量和缓存
|
|
global _BILL_QUANTITY
|
|
_BILL_QUANTITY = bill_quantity
|
|
|
|
# 如果节点有ID,保存到缓存
|
|
bill_id = node.get("id") or node.get("GUID")
|
|
if bill_id:
|
|
bill_quantity_cache[bill_id] = bill_quantity
|
|
|
|
# 设置环境变量作为后备方案
|
|
os.environ["BILL_QUANTITY"] = str(bill_quantity)
|
|
|
|
print(f"设置清单对象数量: {bill_quantity}")
|
|
print(f"全局变量_BILL_QUANTITY: {_BILL_QUANTITY}")
|
|
if bill_id:
|
|
print(f"缓存清单{bill_id}的数量: {bill_quantity_cache[bill_id]}")
|
|
|
|
return bill_obj
|
|
|
|
|
|
# 递归查找取费表中包含"取费基数"的节点
|
|
def find_fee_base_nodes(node: Dict[str, Any], result: List = None) -> List[Dict[str, Any]]:
|
|
"""
|
|
递归查找取费表中包含"取费基数"的节点或有子节点的费用项
|
|
|
|
Args:
|
|
node: 取费表节点
|
|
result: 结果列表
|
|
|
|
Returns:
|
|
List[Dict[str, Any]]: 包含"取费基数"的节点或有子节点的费用项列表
|
|
"""
|
|
if result is None:
|
|
result = []
|
|
|
|
if isinstance(node, dict):
|
|
# 检查当前节点是否包含"取费基数"字段或有子节点
|
|
has_children = "children" in node and isinstance(node["children"], list) and node["children"]
|
|
has_code = "代码" in node
|
|
|
|
if "取费基数" in node or (has_children and has_code):
|
|
result.append(node)
|
|
|
|
# 递归检查子节点
|
|
for key, value in node.items():
|
|
if key == "children" and isinstance(value, list):
|
|
for child in value:
|
|
find_fee_base_nodes(child, result)
|
|
elif isinstance(value, (dict, list)):
|
|
find_fee_base_nodes(value, result)
|
|
|
|
elif isinstance(node, list):
|
|
for item in node:
|
|
find_fee_base_nodes(item, result)
|
|
|
|
return result
|
|
|
|
|
|
# 查找取费表中的费用项
|
|
def find_fee_item_by_code(cost_table: Dict[str, Any], code: str) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
在取费表中查找指定代码的费用项
|
|
|
|
Args:
|
|
cost_table: 取费表
|
|
code: 费用代码
|
|
|
|
Returns:
|
|
Optional[Dict[str, Any]]: 找到的费用项,如果未找到则返回None
|
|
"""
|
|
|
|
def search_in_node(node):
|
|
if isinstance(node, dict):
|
|
if node.get("代码") == code:
|
|
return node
|
|
|
|
for key, value in node.items():
|
|
if isinstance(value, (dict, list)):
|
|
result = search_in_node(value)
|
|
if result:
|
|
return result
|
|
|
|
elif isinstance(node, list):
|
|
for item in node:
|
|
result = search_in_node(item)
|
|
if result:
|
|
return result
|
|
|
|
return None
|
|
|
|
return search_in_node(cost_table)
|
|
|
|
|
|
# 计算表外变量
|
|
def calculate_external_variable(var_name: str, context: ZjQuantityBCLContext, calculation_strategy=None) -> float:
|
|
"""
|
|
使用BCL计算器计算表外变量
|
|
|
|
Args:
|
|
var_name: 变量名
|
|
context: BCL上下文
|
|
calculation_strategy: 计算策略对象
|
|
|
|
Returns:
|
|
float: 计算结果
|
|
"""
|
|
try:
|
|
# 如果有计算策略,使用计算策略的方法
|
|
if calculation_strategy:
|
|
print(f"使用计算策略 {calculation_strategy.__class__.__name__} 计算表外变量: {var_name}")
|
|
# 直接调用计算策略的方法,而不是递归调用自己
|
|
return calculation_strategy.calculate_external_variable(var_name, context)
|
|
|
|
# 否则使用默认实现
|
|
print(f"使用默认方法计算表外变量: {var_name}")
|
|
result = calculator.calculate(var_name, context)
|
|
|
|
# 检查结果类型,如果是 BCLVariant,尝试转换为浮点数
|
|
if hasattr(result, "__class__") and result.__class__.__name__ == "BCLVariant":
|
|
# 根据错误信息,BCLVariant 格式似乎是 Variant(BCLVariantType.FLOAT, 0.0)
|
|
# 尝试访问 value 属性或使用适当的方法获取值
|
|
try:
|
|
# 尝试不同的方法获取值
|
|
if hasattr(result, "value"):
|
|
return float(result.value)
|
|
elif hasattr(result, "get_value"):
|
|
return float(result.get_value())
|
|
elif hasattr(result, "__getitem__"):
|
|
# 如果 BCLVariant 支持索引访问,尝试获取第二个元素
|
|
return float(result[1])
|
|
else:
|
|
# 尝试直接转换为字符串,然后解析值
|
|
str_result = str(result)
|
|
# 假设格式是 Variant(BCLVariantType.FLOAT, 0.0)
|
|
# 尝试提取括号中的第二个值
|
|
import re
|
|
|
|
match = re.search(r"Variant\([^,]+,\s*([^)]+)\)", str_result)
|
|
if match:
|
|
return float(match.group(1))
|
|
|
|
# 如果上述方法都失败,尝试直接转换
|
|
return float(result)
|
|
except (ValueError, TypeError, AttributeError, IndexError) as e:
|
|
print(f"无法将 BCLVariant 类型的结果转换为浮点数: {result}, 错误: {e}")
|
|
# 打印更多调试信息
|
|
print(f"BCLVariant 类型: {type(result)}")
|
|
print(f"BCLVariant 属性: {dir(result)}")
|
|
return 0.0
|
|
|
|
# 如果不是 BCLVariant,尝试直接转换为浮点数
|
|
return float(result) if result is not None else 0.0
|
|
except Exception as e:
|
|
print(f"计算表外变量 '{var_name}' 时出错: {e}")
|
|
return 0.0
|
|
|
|
|
|
# 计算表内费用
|
|
def calculate_internal_fee(
|
|
fee_code: str,
|
|
cost_table: Dict[str, Any],
|
|
project_node: Dict[str, Any],
|
|
context: ZjQuantityBCLContext,
|
|
in_calculation: set = None,
|
|
calculation_strategy=None,
|
|
) -> float:
|
|
"""
|
|
计算表内费用
|
|
|
|
Args:
|
|
fee_code: 费用代码
|
|
cost_table: 取费表
|
|
project_node: 项目划分节点
|
|
context: BCL上下文
|
|
in_calculation: 正在计算中的费用代码集合,用于检测循环依赖
|
|
calculation_strategy: 计算策略对象
|
|
|
|
Returns:
|
|
float: 计算结果
|
|
"""
|
|
# 初始化正在计算的集合
|
|
if in_calculation is None:
|
|
in_calculation = set()
|
|
|
|
# 检查是否存在循环依赖
|
|
if fee_code in in_calculation:
|
|
print(f"检测到循环依赖: {fee_code}")
|
|
return 0.0
|
|
|
|
# 将当前费用代码添加到正在计算的集合中
|
|
in_calculation.add(fee_code)
|
|
|
|
# 检查缓存中是否已有计算结果
|
|
cache_key = f"{fee_code}_{project_node.get('id', '')}"
|
|
if cache_key in calculated_fees:
|
|
# 从正在计算的集合中移除当前费用代码
|
|
in_calculation.remove(fee_code)
|
|
return calculated_fees[cache_key]
|
|
|
|
# 查找费用项
|
|
fee_item = find_fee_item_by_code(cost_table, fee_code)
|
|
if not fee_item:
|
|
print(f"未找到代码为 '{fee_code}' 的费用项")
|
|
# 从正在计算的集合中移除当前费用代码
|
|
in_calculation.remove(fee_code)
|
|
return 0.0
|
|
|
|
# 获取取费基数和费率
|
|
fee_base = fee_item.get("取费基数")
|
|
fee_rate = fee_item.get("费率(%)") or fee_item.get("费率")
|
|
|
|
# 尝试转换费率为浮点数
|
|
try:
|
|
if fee_rate is None or (isinstance(fee_rate, str) and fee_rate.strip() == ""):
|
|
rate = 1.0
|
|
else:
|
|
rate = float(fee_rate) / 100.0
|
|
except (ValueError, TypeError):
|
|
print(f"无法将费率 '{fee_rate}' 转换为浮点数,使用默认值1.0")
|
|
rate = 1.0
|
|
|
|
# 如果有取费基数,直接计算
|
|
if fee_base:
|
|
base_value = calculate_fee_base(
|
|
fee_base, cost_table, project_node, context, in_calculation, calculation_strategy
|
|
)
|
|
# 确保 base_value 不为 None
|
|
base_value = base_value if base_value is not None else 0.0
|
|
result = base_value * rate
|
|
else:
|
|
# 如果没有取费基数,检查是否有子节点
|
|
children = fee_item.get("children", [])
|
|
if children:
|
|
# 计算所有子节点的费用之和
|
|
result = 0.0
|
|
for child in children:
|
|
child_code = child.get("代码", "")
|
|
if child_code:
|
|
child_result = calculate_internal_fee(
|
|
child_code, cost_table, project_node, context, in_calculation, calculation_strategy
|
|
)
|
|
# 确保 child_result 不为 None
|
|
child_result = child_result if child_result is not None else 0.0
|
|
result += child_result
|
|
else:
|
|
print(f"费用项 '{fee_code}' 没有取费基数且没有子节点")
|
|
result = 0.0
|
|
|
|
# 缓存计算结果
|
|
calculated_fees[cache_key] = result
|
|
|
|
# 从正在计算的集合中移除当前费用代码
|
|
in_calculation.remove(fee_code)
|
|
|
|
return result
|
|
|
|
|
|
# 计算取费基数
|
|
def calculate_fee_base(
|
|
fee_base: str,
|
|
cost_table: Dict[str, Any],
|
|
project_node: Dict[str, Any],
|
|
context: ZjQuantityBCLContext,
|
|
in_calculation: set = None,
|
|
calculation_strategy=None,
|
|
) -> float:
|
|
"""
|
|
计算取费基数
|
|
|
|
Args:
|
|
fee_base: 取费基数表达式
|
|
cost_table: 取费表
|
|
project_node: 项目划分节点
|
|
context: BCL上下文
|
|
in_calculation: 正在计算中的费用代码集合,用于检测循环依赖
|
|
calculation_strategy: 计算策略对象
|
|
|
|
Returns:
|
|
float: 计算结果
|
|
"""
|
|
# 初始化正在计算的集合
|
|
if in_calculation is None:
|
|
in_calculation = set()
|
|
|
|
# 检查是否是直接引用另一个费用项的代码
|
|
if fee_base in calculated_fees:
|
|
return calculated_fees[fee_base]
|
|
|
|
# 创建表达式计算器
|
|
expr_calculator = ExpressionCalculator()
|
|
parse_success = expr_calculator.parse_expression(fee_base)
|
|
if not parse_success:
|
|
print(f"测试表达式解析失败: {expr_calculator.get_last_error()}")
|
|
return None
|
|
|
|
# 获取表达式中的变量
|
|
variables = expr_calculator.variables
|
|
variable_values = {}
|
|
|
|
# 处理每个变量
|
|
for var in variables:
|
|
# 检查是否为表内费用代码
|
|
if var in calculated_fees:
|
|
# 使用缓存的计算结果
|
|
variable_values[var] = calculated_fees[var]
|
|
elif find_fee_item_by_code(cost_table, var):
|
|
# 计算表内费用
|
|
value = calculate_internal_fee(var, cost_table, project_node, context, in_calculation, calculation_strategy)
|
|
# 确保值不为 None
|
|
variable_values[var] = value if value is not None else 0.0
|
|
else:
|
|
# 处理表外变量
|
|
value = calculate_external_variable(var, context, calculation_strategy)
|
|
# 确保值不为 None
|
|
variable_values[var] = value if value is not None else 0.0
|
|
|
|
# 计算最终结果
|
|
try:
|
|
result, value = expr_calculator.evaluate(variable_values)
|
|
# 确保结果不为 None
|
|
return value if result else 0.0
|
|
except Exception as e:
|
|
print(f"计算表达式 '{fee_base}' 时出错: {e}")
|
|
return 0.0
|
|
|
|
|
|
# 计算项目节点的所有费用
|
|
def calculate_all_fees(
|
|
project_node: Dict[str, Any],
|
|
cost_table: Dict[str, Any],
|
|
json_file_path: str = None,
|
|
engineering_type: str = None,
|
|
calculation_strategy=None,
|
|
) -> Dict[str, float]:
|
|
"""
|
|
计算项目节点的所有费用
|
|
|
|
Args:
|
|
project_node: 工程量节点
|
|
cost_table: 取费表
|
|
json_file_path: JSON文件路径,用于获取工程信息
|
|
engineering_type: 工程类型,如"清单工程"或"预算工程"
|
|
calculation_strategy: 计算策略,如果为None则使用默认策略
|
|
|
|
Returns:
|
|
Dict[str, float]: 费用计算结果
|
|
"""
|
|
results = {}
|
|
|
|
# 1. 工程信息上下文 ,在这里暂时先将参数项目划分名称固定
|
|
project_context = create_project_contexts(json_file_path=json_file_path)
|
|
|
|
with open(json_file_path, "r", encoding="utf-8") as file:
|
|
data = json.load(file)
|
|
|
|
processed_data = process_DXdata(data)
|
|
|
|
DXITEM = [BCLDataSourceItem(item) for item in processed_data]
|
|
|
|
# 2. 构建数据源链式上下文
|
|
if engineering_type == "清单工程":
|
|
# 获取清单节点
|
|
bill_id = project_node.get("bill_id")
|
|
bill_name = project_node.get("bill_name")
|
|
bill_node = project_node.get("bill_node")
|
|
if not bill_node:
|
|
bill_node = {
|
|
"id": bill_id,
|
|
"GUID": bill_id,
|
|
"清单名称": bill_name,
|
|
"取费表名称": project_node.get("取费表名称"),
|
|
"清单全码": project_node.get("清单全码", ""),
|
|
"编码": project_node.get("清单编码", ""),
|
|
"单位": project_node.get("单位", ""),
|
|
}
|
|
|
|
# 使用包装函数创建清单对象
|
|
# bill_obj = create_list_from_node_with_quantity(bill_node)
|
|
bill_obj = create_list_from_node(bill_node)
|
|
# 如果清单节点有数量,确保它被保存到bill_obj
|
|
if "数量" in bill_node:
|
|
bill_obj.quantity = float(bill_node["数量"])
|
|
print(f"设置清单对象数量: {bill_obj.quantity}")
|
|
|
|
# 创建工程量对象
|
|
from equipment_calculation.bcl_utils import create_node_from_type
|
|
|
|
project_obj = create_node_from_type(project_node)
|
|
|
|
dxitem_context = BCLDataSourceContext(DXITEM, project_context)
|
|
dxitem_context.variables["@特征段地形系数"] = BCLVariant(DXITEM)
|
|
|
|
# 递归数据源链式上下文
|
|
billItem = BCLDataSourceItem(bill_obj)
|
|
# 设置一个显式的数量属性
|
|
# billItem.bill_quantity = float(bill_node.get("数量", 1.0))
|
|
# print(f"设置清单数据源项数量: {billItem.bill_quantity}")
|
|
|
|
QuantityItem = BCLDataSourceItem(project_obj, billItem)
|
|
bill_context = BCLDataSourceContext([billItem], dxitem_context)
|
|
context = BCLDataSourceContext([QuantityItem], bill_context)
|
|
else:
|
|
# 非清单工程 - 工程信息 -> 工程量节点
|
|
from equipment_calculation.bcl_utils import create_node_from_type
|
|
|
|
project_obj = create_node_from_type(project_node)
|
|
|
|
dxitem_context = BCLDataSourceContext(DXITEM, project_context)
|
|
dxitem_context.variables["@特征段地形系数"] = BCLVariant(DXITEM)
|
|
|
|
QuantityItem = BCLDataSourceItem(project_obj)
|
|
context = BCLDataSourceContext([QuantityItem], dxitem_context)
|
|
|
|
# 打印项目节点信息,用于调试
|
|
node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
|
|
print(f"\n处理项目节点: {node_name}")
|
|
|
|
# 查找包含取费基数的节点
|
|
fee_base_nodes = find_fee_base_nodes(cost_table)
|
|
|
|
# 计算每个费用项
|
|
# 先计算基本费用项,再计算复合费用项
|
|
# 这里简单按照节点在列表中的顺序计算,可能需要更复杂的依赖解析
|
|
for node in fee_base_nodes:
|
|
fee_name = node.get("费用名称", node.get("name", "未知费用"))
|
|
fee_code = node.get("代码", "")
|
|
fee_base = node.get("取费基数", "")
|
|
fee_rate = node.get("费率(%)") or node.get("费率")
|
|
|
|
# 尝试转换费率为浮点数
|
|
try:
|
|
if fee_rate is None or (isinstance(fee_rate, str) and fee_rate.strip() == ""):
|
|
rate = 1.0
|
|
else:
|
|
rate = float(fee_rate) / 100.0
|
|
except (ValueError, TypeError):
|
|
print(f"无法将费率 '{fee_rate}' 转换为浮点数,使用默认值1.0")
|
|
rate = 1.0
|
|
|
|
if fee_base:
|
|
# 计算取费基数
|
|
base_value = calculate_fee_base(fee_base, cost_table, project_node, context, None, calculation_strategy)
|
|
# 应用费率
|
|
result = base_value * rate
|
|
results[fee_name] = result
|
|
|
|
# 缓存计算结果
|
|
if fee_code:
|
|
calculated_fees[fee_code] = result
|
|
elif fee_code:
|
|
# 如果没有取费基数但有代码,尝试计算
|
|
result = calculate_internal_fee(fee_code, cost_table, project_node, context, None, calculation_strategy)
|
|
results[fee_name] = result
|
|
|
|
return results
|
|
|
|
|
|
# 计算工程量取费表
|
|
##原始代码
|
|
# def calculate_quantity_fees(
|
|
# json_file_path: str, project_name: str, adjustment_type: str = None, engineering_type: str = None
|
|
# ) -> str:
|
|
# """
|
|
# 计算工程量取费表
|
|
|
|
# Args:
|
|
# json_file_path: JSON文件路径
|
|
# project_name: 项目名称
|
|
# adjustment_type: 调差类型
|
|
# engineering_type: 工程类型
|
|
# Returns:
|
|
# str: 输出文件路径
|
|
# """
|
|
# # 设置输出目录和文件名
|
|
# output_dir = "计算结果"
|
|
# os.makedirs(output_dir, exist_ok=True)
|
|
# output_file = os.path.join(
|
|
# output_dir, f"{project_name}_{adjustment_type}_{engineering_type}_calculation_results.json"
|
|
# )
|
|
|
|
# # 初始化BCL计算器
|
|
# if not init_bcl_calculator():
|
|
# return None
|
|
|
|
# # 根据工程类型获取取费表和项目节点
|
|
# if engineering_type == "预算工程":
|
|
# # 预算工程 - 获取项目划分节点的取费表
|
|
# cost_table_children = get_cost_table_children(json_file_path, project_name)
|
|
# project_children = get_quantity_nodes(json_file_path, project_name, adjustment_type, engineering_type)
|
|
|
|
# if not cost_table_children or not project_children:
|
|
# print(f"未找到项目 '{project_name}' 的取费表或项目划分节点")
|
|
# return None
|
|
|
|
# elif engineering_type == "清单工程":
|
|
# # 清单工程 - 获取清单节点的取费表
|
|
# project_children = get_quantity_nodes(json_file_path, project_name, adjustment_type, engineering_type)
|
|
# if not project_children:
|
|
# print(f"未找到项目 '{project_name}' 的项目划分节点")
|
|
# return None
|
|
|
|
# # 为每个工程量节点找到对应的清单节点取费表
|
|
# cost_tables = {}
|
|
|
|
# for project_node in project_children:
|
|
# node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
|
|
|
|
# # 获取清单节点信息
|
|
# bill_id = project_node.get("bill_id")
|
|
# fee_table_name = project_node.get("取费表名称")
|
|
|
|
# if not bill_id:
|
|
# print(f"工程量节点 '{node_name}' 缺少清单节点ID")
|
|
# continue
|
|
|
|
# if not fee_table_name:
|
|
# print(f"工程量节点 '{node_name}' 没有取费表名称")
|
|
# continue
|
|
|
|
# # 使用清单节点的取费表名称直接查找取费表
|
|
# if bill_id not in cost_tables:
|
|
# print(f"查找清单节点 '{project_node.get('bill_name', '未命名清单')}' 的取费表: {fee_table_name}")
|
|
|
|
# # 查找取费表
|
|
# with open(json_file_path, "r", encoding="utf-8") as f:
|
|
# data = json.load(f)
|
|
|
|
# project_data = data.get("projectData", {})
|
|
# cost_setting = project_data.get("costSetting", {})
|
|
|
|
# # 查找取费表
|
|
# cost_table = find_cost_table(cost_setting, fee_table_name)
|
|
|
|
# if not cost_table:
|
|
# print(f"未找到取费表 '{fee_table_name}'")
|
|
# continue
|
|
|
|
# cost_table_children = cost_table.get("children", None)
|
|
# if not cost_table_children:
|
|
# print(f"取费表 '{fee_table_name}' 没有子节点")
|
|
# continue
|
|
|
|
# cost_tables[bill_id] = cost_table_children
|
|
# print(f"成功获取清单节点 '{project_node.get('bill_name', '未命名清单')}' 的取费表")
|
|
# else:
|
|
# print(f"不支持的工程类型: {engineering_type}")
|
|
# return None
|
|
|
|
# # 初始化缓存
|
|
# calculated_fees.clear()
|
|
|
|
# # 计算每个项目节点的费用
|
|
# all_results = {}
|
|
|
|
# for project_node in project_children:
|
|
# # 为每个节点清空缓存,确保计算独立
|
|
# calculated_fees.clear()
|
|
|
|
# node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
|
|
|
|
# # 根据工程类型获取对应的取费表
|
|
# if engineering_type == "预算工程":
|
|
# # 预算工程 - 使用项目划分节点的取费表
|
|
# node_results = calculate_all_fees(
|
|
# project_node, {"children": cost_table_children}, json_file_path, engineering_type
|
|
# )
|
|
# else: # 清单工程
|
|
# # 清单工程 - 使用清单节点的取费表
|
|
# parent_id = project_node.get("parent_id")
|
|
# if parent_id and parent_id in cost_tables:
|
|
# node_results = calculate_all_fees(
|
|
# project_node, {"children": cost_tables[parent_id]}, json_file_path, engineering_type
|
|
# )
|
|
# else:
|
|
# print(f"无法获取工程量节点 '{node_name}' 的取费表")
|
|
# continue
|
|
|
|
# all_results[node_name] = node_results
|
|
|
|
# # 输出计算结果
|
|
# print(f"费用计算结果:")
|
|
# for fee_name, fee_value in node_results.items():
|
|
# print(f" {fee_name}: {fee_value}")
|
|
|
|
# # 保存计算结果到JSON文件
|
|
# with open(output_file, "w", encoding="utf-8") as f:
|
|
# json.dump(all_results, f, ensure_ascii=False, indent=2)
|
|
|
|
# print(f"\n计算结果已保存到 {output_file}")
|
|
|
|
# return output_file
|
|
|
|
|
|
def calculate_quantity_fees(
|
|
json_file_path: str,
|
|
project_name: str,
|
|
engineering_type: str = None,
|
|
project_guid: str = None,
|
|
calculation_strategy=None,
|
|
) -> str:
|
|
"""
|
|
计算工程量取费表
|
|
|
|
Args:
|
|
json_file_path: JSON文件路径
|
|
project_name: 项目名称
|
|
engineering_type: 工程类型
|
|
project_guid: 项目GUID,用于区分同名项目
|
|
calculation_strategy: 计算策略,如果为None则使用默认策略
|
|
|
|
Returns:
|
|
str: 输出文件路径
|
|
"""
|
|
# 如果没有提供计算策略,使用默认策略
|
|
if calculation_strategy is None:
|
|
from equipment_calculation.calculation_strategy import DefaultCalculationStrategy
|
|
|
|
calculation_strategy = DefaultCalculationStrategy()
|
|
|
|
# 设置输出目录和文件名
|
|
output_dir = "计算结果"
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
# 获取项目划分节点的GUID
|
|
_, _, _, _, target_node = load_project_data(json_file_path, project_name, project_guid)
|
|
if target_node:
|
|
node_guid = target_node.get("GUID") or target_node.get("guid", "")
|
|
# 如果传入了GUID但与节点的GUID不一致,优先使用传入的GUID
|
|
project_guid = project_guid if project_guid else node_guid
|
|
|
|
# 将文件名中的非法字符替换为下划线
|
|
safe_project_name = (
|
|
project_name.replace("/", "_")
|
|
.replace("\\", "_")
|
|
.replace(":", "_")
|
|
.replace("*", "_")
|
|
.replace("?", "_")
|
|
.replace('"', "_")
|
|
.replace("<", "_")
|
|
.replace(">", "_")
|
|
.replace("|", "_")
|
|
)
|
|
|
|
# 将GUID中的非法字符替换为下划线
|
|
safe_project_guid = ""
|
|
if project_guid:
|
|
safe_project_guid = (
|
|
project_guid.replace("/", "_")
|
|
.replace("\\", "_")
|
|
.replace(":", "_")
|
|
.replace("*", "_")
|
|
.replace("?", "_")
|
|
.replace('"', "_")
|
|
.replace("<", "_")
|
|
.replace(">", "_")
|
|
.replace("|", "_")
|
|
.replace("{", "")
|
|
.replace("}", "")
|
|
)
|
|
# 添加下划线作为分隔符
|
|
safe_project_guid = f"_{safe_project_guid}"
|
|
|
|
output_file = os.path.join(
|
|
output_dir,
|
|
f"{safe_project_name}{safe_project_guid}_{engineering_type}_calculation_results.json",
|
|
)
|
|
|
|
# 在处理清单工程时,保存清单数量供后续使用
|
|
bill_quantities = {}
|
|
|
|
# 根据工程类型获取取费表和项目节点
|
|
if engineering_type == "预算工程":
|
|
# 预算工程 - 获取项目划分节点的取费表,传递project_guid参数
|
|
cost_table_children = get_cost_table_children(json_file_path, project_name, project_guid)
|
|
project_children = get_quantity_nodes(json_file_path, project_name, engineering_type, project_guid)
|
|
|
|
if not cost_table_children or not project_children:
|
|
print(f"未找到项目 '{project_name}' (GUID: {project_guid}) 的取费表或项目划分节点")
|
|
return None
|
|
|
|
elif engineering_type == "清单工程":
|
|
# 清单工程 - 获取清单节点的取费表,传递project_guid参数
|
|
project_children = get_quantity_nodes(json_file_path, project_name, engineering_type, project_guid)
|
|
if not project_children:
|
|
print(f"未找到项目 '{project_name}' (GUID: {project_guid}) 的项目划分节点")
|
|
return None
|
|
|
|
# 为每个工程量节点找到对应的清单节点取费表
|
|
cost_tables = {}
|
|
|
|
for project_node in project_children:
|
|
node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
|
|
|
|
# 获取清单节点信息
|
|
bill_id = project_node.get("bill_id")
|
|
fee_table_name = project_node.get("取费表名称")
|
|
|
|
if not bill_id:
|
|
print(f"工程量节点 '{node_name}' 缺少清单节点ID")
|
|
continue
|
|
|
|
if not fee_table_name:
|
|
print(f"工程量节点 '{node_name}' 没有取费表名称")
|
|
continue
|
|
|
|
# 使用清单节点的取费表名称直接查找取费表
|
|
if bill_id not in cost_tables:
|
|
print(f"查找清单节点 '{project_node.get('bill_name', '未命名清单')}' 的取费表: {fee_table_name}")
|
|
|
|
# 查找取费表
|
|
with open(json_file_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
project_data = data.get("projectData", {})
|
|
cost_setting = project_data.get("costSetting", {})
|
|
|
|
# 查找取费表
|
|
cost_table = find_cost_table(cost_setting, fee_table_name)
|
|
|
|
if not cost_table:
|
|
print(f"未找到取费表 '{fee_table_name}'")
|
|
continue
|
|
|
|
cost_table_children = cost_table.get("children", None)
|
|
if not cost_table_children:
|
|
print(f"取费表 '{fee_table_name}' 没有子节点")
|
|
continue
|
|
|
|
cost_tables[bill_id] = cost_table_children
|
|
print(f"成功获取清单节点 '{project_node.get('bill_name', '未命名清单')}' 的取费表")
|
|
|
|
# 在读取JSON文件后,遍历项目中的清单节点,获取并保存清单数量
|
|
with open(json_file_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
project_data = data.get("projectData", {})
|
|
bill_data = project_data.get("bill", {})
|
|
|
|
# 递归函数获取所有清单节点
|
|
def get_bill_nodes(node, results=None):
|
|
if results is None:
|
|
results = []
|
|
|
|
if isinstance(node, dict):
|
|
# 检查是否是清单节点
|
|
is_bill = (
|
|
node.get("类型") == "8" or node.get("类型") == "清单" or "清单名称" in node or "清单编码" in node
|
|
)
|
|
|
|
if is_bill and ("id" in node or "GUID" in node):
|
|
bill_id = node.get("id") or node.get("GUID")
|
|
if "数量" in node:
|
|
try:
|
|
quantity = float(node["数量"])
|
|
except (TypeError, ValueError):
|
|
pass
|
|
|
|
results.append((bill_id, quantity))
|
|
|
|
# 直接设置到全局变量和缓存
|
|
global _BILL_QUANTITY
|
|
_BILL_QUANTITY = quantity
|
|
bill_quantity_cache[bill_id] = quantity
|
|
|
|
# 设置环境变量
|
|
os.environ["BILL_QUANTITY"] = str(quantity)
|
|
|
|
print(f"设置清单 {bill_id} 的数量为全局变量: {quantity}")
|
|
|
|
# 递归处理子节点
|
|
if "children" in node and isinstance(node["children"], list):
|
|
for child in node["children"]:
|
|
get_bill_nodes(child, results)
|
|
|
|
return results
|
|
|
|
# 获取和设置清单数量
|
|
get_bill_nodes(bill_data)
|
|
|
|
# 如果有计算策略,设置清单数量
|
|
if calculation_strategy and hasattr(calculation_strategy, "set_bill_quantity"):
|
|
calculation_strategy.set_bill_quantity(_BILL_QUANTITY)
|
|
|
|
else:
|
|
print(f"不支持的工程类型: {engineering_type}")
|
|
return None
|
|
|
|
# 在获取 project_children 后添加预处理代码
|
|
if project_children and calculation_strategy and hasattr(calculation_strategy, "preprocess_quantity_fee_node"):
|
|
print(f"对项目节点进行预处理...")
|
|
|
|
# 递归处理所有节点
|
|
def process_node(node):
|
|
calculation_strategy.preprocess_quantity_fee_node(node)
|
|
if "children" in node and isinstance(node["children"], list):
|
|
for child in node["children"]:
|
|
process_node(child)
|
|
|
|
# 处理每个项目节点
|
|
for node in project_children:
|
|
process_node(node)
|
|
|
|
# 初始化缓存
|
|
calculated_fees.clear() # 使用全局缓存,而不是计算策略中的缓存
|
|
|
|
# 计算每个项目节点的费用
|
|
all_results = {}
|
|
|
|
for project_node in project_children:
|
|
# 为每个节点清空缓存,确保计算独立
|
|
calculated_fees.clear()
|
|
|
|
node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
|
|
|
|
# 添加清单数量信息
|
|
if engineering_type == "清单工程":
|
|
bill_id = project_node.get("bill_id")
|
|
if bill_id in bill_quantities:
|
|
project_node["bill_quantity"] = bill_quantities[bill_id]
|
|
print(f"为工程量节点 '{node_name}' 设置清单数量: {bill_quantities[bill_id]}")
|
|
|
|
# 根据工程类型获取对应的取费表
|
|
if engineering_type == "预算工程":
|
|
# 预算工程 - 使用项目划分节点的取费表
|
|
node_results = calculation_strategy.calculate_all_fees(
|
|
project_node, {"children": cost_table_children}, json_file_path, engineering_type
|
|
)
|
|
else: # 清单工程
|
|
# 清单工程 - 使用清单节点的取费表
|
|
parent_id = project_node.get("parent_id")
|
|
if parent_id and parent_id in cost_tables:
|
|
node_results = calculation_strategy.calculate_all_fees(
|
|
project_node, {"children": cost_tables[parent_id]}, json_file_path, engineering_type
|
|
)
|
|
else:
|
|
print(f"无法获取工程量节点 '{node_name}' 的取费表")
|
|
continue
|
|
|
|
all_results[node_name] = node_results
|
|
|
|
# 输出计算结果
|
|
print(f"费用计算结果:")
|
|
for fee_name, fee_value in node_results.items():
|
|
print(f" {fee_name}: {fee_value}")
|
|
|
|
# 保存计算结果到JSON文件
|
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
json.dump(all_results, f, ensure_ascii=False, indent=2)
|
|
|
|
print(f"\n计算结果已保存到 {output_file}")
|
|
|
|
# 如果有计算策略,应用后处理规则
|
|
if calculation_strategy:
|
|
calculation_strategy.post_process_quantity_fees(output_file, project_name)
|
|
|
|
return output_file
|