This commit is contained in:
李鹏宇
2026-06-16 17:59:46 +08:00
parent f6214a99ce
commit a30a53738f
+102 -44
View File
@@ -30,6 +30,7 @@ class LeasedAreaReport(models.Model):
6. 片区管理员从第一个物业的 manage.information.line 取(按查询日期匹配)
7. 支持按物业名称(property_id)、承租方(lessee_id)、合同编号(contract_id)搜索
8. 按物业筛选时,只保留包含该物业的合同,但合同仍显示全部物业信息(物业名称、面积均累加全部物业)
9. 同一物业出现在多个合同时,面积只计算一次(归属到 start_time 最晚的合同)
"""
# ========== 一、参数解析 ==========
@@ -115,37 +116,27 @@ class LeasedAreaReport(models.Model):
if ml.yuthon_property_id:
manage_map[ml.yuthon_property_id.id] = ml
# ========== 六、构建报表行(按合同分组,一个合同一行) ==========
rows = []
total_property_area = 0.0 # 物业已租面积合计(按物业id去重)
total_contract_area = 0.0 # 合同已租面积合计(按物业id去重)
seen_property_ids = set() # 已计入合计的物业id集合
# ========== 六、第一轮遍历:过滤 + 收集有效合同 ==========
# 先把所有过滤条件跑一遍,收集最终出现在报表中的合同列表
# 再基于过滤后的合同做面积归属预扫描,避免被过滤掉的合同抢走归属权
filtered_contracts = [] # [(contract, lines, first_cl, first_property, manage_line)]
for idx, contract in enumerate(contracts, start=1):
for contract in contracts:
lines = lines_by_contract.get(contract.id, [])
if not lines:
continue
# --- 时间过滤:基于 actual_start_time 和 actual_end_time ---
# 规则:
# 1. actual_start_time 为空 → 查不到(数据不完整)
# 2. actual_start_time > 查询月末 → 合同在查询月之后才启动,查不到
# 3. 1号起租:actual_end_time >= 查询月初 → 能查到
# 4. 非1号起租:actual_end_time 往前推一个月 >= 查询月初 → 能查到
# 5. actual_end_time 为空 → 查不到(数据不完整)
if query_date:
# 实际开始时间为空,查不到
if not contract.actual_start_time:
continue
# 条件1:实际开始时间在查询月之后就查不到
if contract.actual_start_time > query_month_last:
continue
# 实际结束时间为空,查不到
if not contract.actual_end_time:
continue
# 条件2:结束时间判断
if contract.actual_start_time.day == 1:
# 1号起租:actual_end_time >= 查询月初就能查到
@@ -156,7 +147,6 @@ class LeasedAreaReport(models.Model):
end_year = contract.actual_end_time.year
end_month = contract.actual_end_time.month
end_day = contract.actual_end_time.day
# 前一个月
if end_month == 1:
pushed_year = end_year - 1
@@ -164,22 +154,18 @@ class LeasedAreaReport(models.Model):
else:
pushed_year = end_year
pushed_month = end_month - 1
# 处理日期(如前一个月没有这么多天,取最后一天)
_, pushed_last_day = calendar.monthrange(pushed_year, pushed_month)
pushed_day = min(end_day, pushed_last_day)
pushed_end = datetime.date(pushed_year, pushed_month, pushed_day)
if pushed_end < query_month_first:
continue
# --- 6.1 合同信息 ---
# --- 合同信息(提前取,后面关键字过滤需要) ---
contract_code = contract.contract_code or ""
contract_record_id = contract.id
lessee_name = contract.lessee_id.name if contract.lessee_id else ""
lessee_record_id = contract.lessee_id.id if contract.lessee_id else False
# --- 6.2 关键字过滤(物业名称、承租方、合同编号) ---
# --- 关键字过滤(物业名称、承租方、合同编号) ---
if keyword:
match = False
kw = keyword.lower()
@@ -199,13 +185,8 @@ class LeasedAreaReport(models.Model):
if not match:
continue
# --- 6.3 权属类型过滤 ---
# 取第一个物业的管理信息做判断
first_cl = lines[0]
first_property = first_cl.address_id
manage_line = manage_map.get(first_property.id) if first_property else None
# --- 权属类型过滤 ---
if condition:
# 遍历该合同下所有物业,只要有一个匹配就保留
matched = False
for cl in lines:
ml = manage_map.get(cl.address_id.id) if cl.address_id else None
@@ -215,7 +196,7 @@ class LeasedAreaReport(models.Model):
if not matched:
continue
# --- 6.4 所属片区/公司过滤(通过管理信息) ---
# --- 所属片区/公司过滤(通过管理信息) ---
if partner_id:
matched = False
for cl in lines:
@@ -235,32 +216,109 @@ class LeasedAreaReport(models.Model):
if not matched:
continue
# --- 6.5 片区管理员(取第一个物业的片区管理员) ---
# 通过所有过滤,加入有效合同列表
first_cl = lines[0]
first_property = first_cl.address_id
manage_line = manage_map.get(first_property.id) if first_property else None
filtered_contracts.append((contract, lines, first_cl, first_property, manage_line))
# ========== 七、预扫描:确定每个物业id的面积计算归属 ==========
# 仅基于过滤后的有效合同做归属判断,避免被过滤掉的合同抢走归属权
# 规则:同一物业出现在多个有效合同时,归属到 actual_start_time 更晚的合同
pid_owner_contract = {} # pid → (contract_id, actual_start_time)
for contract, lines, _, _, _ in filtered_contracts:
if not contract.actual_start_time:
continue
for cl in lines:
if not cl.address_id:
continue
pid = cl.address_id.id
existing = pid_owner_contract.get(pid)
if existing is None:
# 首次见到该物业,记录当前合同为候选
pid_owner_contract[pid] = (contract.id, contract.actual_start_time)
elif contract.actual_start_time > existing[1]:
# 当前合同的 start_time 更晚,更新归属到当前合同
pid_owner_contract[pid] = (contract.id, contract.actual_start_time)
# ========== 八、第二轮遍历:构建报表行 + 计算合计 ==========
rows = []
total_property_area = 0.0 # 物业已租面积合计
total_contract_area = 0.0 # 合同已租面积合计
counted_pids = set() # 已参与合计计算的物业id
for idx, (contract, lines, first_cl, first_property, manage_line) in enumerate(filtered_contracts, start=1):
# --- 8.1 合同信息 ---
contract_code = contract.contract_code or ""
contract_record_id = contract.id
lessee_name = contract.lessee_id.name if contract.lessee_id else ""
lessee_record_id = contract.lessee_id.id if contract.lessee_id else False
# --- 8.2 片区管理员(取第一个物业的片区管理员) ---
admin_display = ""
if manage_line and manage_line.precinct_id:
admin_display = manage_line.precinct_id.display_name or ""
# --- 6.6 物业已租面积:该合同下所有物业 property_area 之和 ---
# 即 contract.total_area,但逐个计算更可靠
# --- 8.3 物业已租面积:该合同下所有物业 property_area 之和 ---
property_area_sum = sum((cl.property_area or 0.0) for cl in lines)
# 合同已租面积
contract_area = contract.total_area or 0.0
# 如果 property_area_sum 和 contract.total_area 差异较大,用 property_area_sum
# 否则以 contract.total_area 为准
property_area = property_area_sum if property_area_sum > 0 else contract_area
# --- 合计数去重:按物业id去重,同一物业id只参与一次合计 ---
# 解析该合同下所有物业id
# --- 8.4 合计数去重:同一物业id只计算一次,归属到 start_time 最晚的合同 ---
# 规则:物业id只出现一次→正常计算;出现多次→只在start_time最晚的合同中计算
# 解析该合同下所有物业id及对应面积
property_ids_in_row = [cl.address_id.id for cl in lines if cl.address_id]
has_new = any(pid not in seen_property_ids for pid in property_ids_in_row)
if has_new:
total_property_area += property_area
total_contract_area += contract_area
for pid in property_ids_in_row:
seen_property_ids.add(pid)
pid_area_map = {cl.address_id.id: cl.property_area or 0.0 for cl in lines if cl.address_id}
# 找出归属于当前合同的物业(pid_owner_contract 中记录的 owner == 当前合同)
owner_pids = [
pid for pid in property_ids_in_row
if pid_owner_contract.get(pid, (None, None))[0] == contract.id
]
# 其中尚未参与过计算的
new_owner_pids = [pid for pid in owner_pids if pid not in counted_pids]
# 不归属当前合同的物业(属于start_time更晚的其他合同,不参与计算但仍展示)
skip_pids = [pid for pid in property_ids_in_row if pid not in owner_pids]
# --- 6.7 物业名称和物业ID:列出该合同下所有物业 ---
# 调试日志
_logger.info(
"[LEASED_AREA_DEBUG] 合同=%s(id=%s), start=%s, 所有物业id=%s, "
"归属本合同id=%s, 新计算id=%s, 跳过id(归属更晚合同)=%s, "
"property_area=%.4f, contract_area=%.4f",
contract_code, contract.id, contract.actual_start_time,
property_ids_in_row, owner_pids, new_owner_pids, skip_pids,
property_area, contract_area,
)
if new_owner_pids and property_area > 0:
# 直接按归属物业的 property_area 逐个累加
add_prop_area = sum(pid_area_map.get(pid, 0.0) for pid in new_owner_pids)
total_property_area += add_prop_area
# 合同已租面积按归属物业面积占该行总物业面积的比例分配
if property_area_sum > 0:
ratio = add_prop_area / property_area_sum
else:
ratio = 1.0
total_contract_area += contract_area * ratio
_logger.info(
"[LEASED_AREA_DEBUG] → 计入合计: add_prop_area=%.4f, ratio=%.4f, "
"累计 property=%.4f, contract=%.4f",
add_prop_area, ratio,
total_property_area, total_contract_area,
)
else:
_logger.info("[LEASED_AREA_DEBUG] → 未计入合计(无新归属物业)")
# 只标记实际参与计算的物业为已处理
for pid in new_owner_pids:
counted_pids.add(pid)
# --- 8.5 物业名称和物业ID:列出该合同下所有物业 ---
property_names = []
property_ids_strs = []
for cl in lines:
@@ -280,7 +338,7 @@ class LeasedAreaReport(models.Model):
else:
property_name_value = display_property_name
# --- 6.8 构建行数据(物业名称、合同编号、承租方支持穿透) ---
# --- 8.6 构建行数据(物业名称、合同编号、承租方支持穿透) ---
# 实际起止时间格式化
actual_start = contract.actual_start_time
if actual_start:
@@ -314,7 +372,7 @@ class LeasedAreaReport(models.Model):
"片区管理员": admin_display,
})
# ========== 七、构建统计汇总数据 ==========
# ========== 九、构建统计汇总数据 ==========
stats = [
{'name': '记录总数', 'amount': len(rows)},
{'name': '物业已租面积合计', 'amount': round(total_property_area, 4), 'display_amount': f"{round(total_property_area, 4):.4f}"},