Files
gzth/yuthon_report/models/management_area_report.py
2026-06-17 18:01:58 +08:00

261 lines
12 KiB
Python
Raw Permalink 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.
import datetime
import logging
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
_logger = logging.getLogger(__name__)
class ManagementAreaReport(models.Model):
_name = 'management.area.report'
_description = '管理面积报表'
_auto = False
@api.model
def get_management_area_report(self, params):
"""管理面积报表数据源。
核心逻辑:
1. 数据源为 manage.information.line(管理信息明细行)
2. 日期过滤:生效日期 <= 查询日期 <= 失效日期(失效日期为空表示至今有效)
3. 管理面积直接取 manage.information.line.management_area
4. 产权证号、产权地址从 title.information.line 取(也按日期范围匹配)
5. 片区管理员取 precinct_id.display_name(格式:片区名称+管理员姓名)
6. 合同编号、承租方从 property.lease.contract.line 反查合同获取
7. 支持按物业名称(property_id)、承租方(lessee_id)、合同编号(contract_id)搜索
"""
# ========== 一、参数解析 ==========
date_start = params.get("date_start")
date_end = params.get("date_end")
# 单日期模式,优先取 date_start
query_date_str = date_start or date_end
keyword = params.get("keyword")
condition = params.get('condition')
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(params.get('company_id'))
# 前端侧边栏下拉选择器传入的 ID
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
summary_only = params.get("summary_only", False)
# ========== 二、查询日期解析 ==========
query_date = None
if query_date_str:
try:
# 截断时间后缀,如 "2024-11-01 00:00:00" -> "2024-11-01"
query_date = datetime.datetime.fromisoformat(query_date_str.split(' ')[0]).date()
except Exception:
_logger.warning("[MANAGEMENT_AREA] 日期解析失败: %s", query_date_str)
pass
# 没有查询日期时不做时间过滤(返回所有记录)
domain = [('yuthon_property_id', '!=', False)]
# 日期过滤:生效日期 <= 查询日期,且(失效日期 >= 查询日期 或 失效日期为空)
if query_date:
domain += [
('start_time', '<=', query_date),
'|', ('end_date', '>=', query_date), ('end_date', '=', False),
]
# 其他筛选条件
if condition:
domain.append(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', 'in', company_id))
# 物业名称下拉选择器:直接过滤 yuthon_property_id
if property_id:
domain.append(('yuthon_property_id', '=', int(property_id)))
# ========== 三、查询管理信息明细行 ==========
manage_lines = self.env['manage.information.line'].sudo().search(domain)
# ========== 四、批量预取合同信息 ==========
# 避免每条记录单独查询,一次性查出所有物业对应的合同
# 关联路径:manage.information.line → yuthon_property_id → yuthon.property
# ← property.lease.contract.line(address_id) → lease_id → property.lease.contract
property_ids = list({line.yuthon_property_id.id for line in manage_lines if line.yuthon_property_id})
# 合同映射:property_id → 合同信息(contract_code, lessee_name, contract_id)
contract_map = {}
if property_ids:
# 搜索所有关联的合同明细行
contract_lines = self.env['property.lease.contract.line'].sudo().search([
('address_id', 'in', property_ids),
])
for cl in contract_lines:
contract = cl.lease_id
if not contract:
continue
pid = cl.address_id.id
# 同一物业可能有多份合同,用列表存储
if pid not in contract_map:
contract_map[pid] = []
contract_map[pid].append({
'contract_id': contract.id,
'contract_code': contract.contract_code or "",
'lessee_id': contract.lessee_id.id if contract.lessee_id else False,
'lessee_name': contract.lessee_id.name if contract.lessee_id else "",
'actual_start_time': contract.actual_start_time,
'actual_end_time': contract.actual_end_time,
'is_contract': contract.is_contract,
})
# ========== 五、承租方/合同编号过滤 ==========
# 前端传入 lessee_id 或 contract_id 时,需要根据合同信息过滤
# 找出符合条件的物业 ID 集合,用于过滤 manage_lines
if lessee_id or contract_id:
allowed_property_ids = set()
for pid, contracts in contract_map.items():
for c in contracts:
# 承租方匹配
if lessee_id and c['lessee_id'] != int(lessee_id):
continue
# 合同编号匹配
if contract_id and c['contract_id'] != int(contract_id):
continue
allowed_property_ids.add(pid)
break
# 过滤 manage_lines,只保留符合条件的物业
manage_lines = manage_lines.filtered(
lambda l: l.yuthon_property_id.id in allowed_property_ids
)
# ========== 六、构建报表行 ==========
rows = []
row_count = 0
total_management_area = 0.0
seen_property_ids = set() # 已计入合计的物业id集合
for idx, line in enumerate(manage_lines, start=1):
property_rec = line.yuthon_property_id
if not property_rec:
continue
# --- 6.1 取合同信息(查询日期范围内生效的合同) ---
contract_code = ""
contract_record_id = False
lessee_name = ""
lessee_record_id = False
contracts_list = contract_map.get(property_rec.id, [])
if contracts_list:
# 优先取查询日期范围内生效的合同
active_contracts = contracts_list
if query_date:
active_contracts = [
c for c in contracts_list
if c['actual_start_time'] and c['actual_start_time'] <= query_date
and (not c['actual_end_time'] or c['actual_end_time'] >= query_date)
]
# 如果没有过滤出有效合同,回退到所有合同
if not active_contracts:
active_contracts = contracts_list
# 收集所有合同的编号和承租方(去重,保持顺序)
code_list = []
lessee_list = []
id_list = []
lessee_id_list = []
for c in active_contracts:
if c['contract_code'] and c['contract_code'] not in code_list:
code_list.append(c['contract_code'])
if c['lessee_name'] and c['lessee_name'] not in lessee_list:
lessee_list.append(c['lessee_name'])
id_list.append(c['contract_id'])
if c['lessee_id']:
lessee_id_list.append(c['lessee_id'])
contract_code = "、".join(code_list)
lessee_name = "、".join(lessee_list)
contract_record_id = id_list[0] if id_list else False
lessee_record_id = lessee_id_list[0] if lessee_id_list else False
# --- 6.2 关键字过滤(物业名称、承租方、合同编号) ---
if keyword:
match = False
kw = keyword.lower()
# 物业名称
if property_rec.name and kw in property_rec.name.lower():
match = True
# 承租方
if lessee_name and kw in lessee_name.lower():
match = True
# 合同编号
if contract_code and kw in contract_code.lower():
match = True
if not match:
continue
# --- 6.3 取产权信息(产权证号、产权地址) ---
# 产权信息也有生效/失效日期,按查询日期范围匹配
title_certificate = ""
property_address = ""
if property_rec.title_information_ids:
valid_titles = property_rec.title_information_ids
if query_date:
valid_titles = valid_titles.filtered(
lambda t: t.start_time and t.start_time <= query_date and
(not t.end_date or t.end_date >= query_date)
)
if valid_titles:
# 按生效日期降序取最新的
latest_title = valid_titles.sorted('start_time', reverse=True)[0]
title_certificate = latest_title.title_certificate or ""
if latest_title.property_message_id:
property_address = latest_title.property_message_id.name or ""
# --- 6.4 片区管理员 ---
# precinct_id.display_name 格式为 "片区名称管理员姓名",如 "二D梁嘉辉"
admin_display = ""
if line.precinct_id:
admin_display = line.precinct_id.display_name or ""
# --- 6.5 累计统计(按物业id去重,同一物业id只参与一次合计) ---
management_area = line.management_area or 0.0
if property_rec.id not in seen_property_ids:
total_management_area += management_area
seen_property_ids.add(property_rec.id)
# --- 6.6 构建行数据(物业名称、合同编号、承租方支持穿透) ---
if not summary_only:
rows.append({
"序号": idx,
"物业id": property_rec.id,
"物业名称": {
'model': 'yuthon.property',
'id': property_rec.id,
'display_name': property_rec.name or "",
} if property_rec.name else "",
"合同编号": {
'model': 'property.lease.contract',
'id': contract_record_id,
'display_name': contract_code,
} if contract_record_id and "、" not in contract_code else contract_code or "",
"承租方": {
'model': 'property.tenant.information',
'id': lessee_record_id,
'display_name': lessee_name,
} if lessee_record_id and "、" not in lessee_name else lessee_name or "",
"产权证号": title_certificate,
"产权地址": property_address,
"管理面积": round(management_area, 4),
"片区管理员": admin_display,
})
row_count += 1
# ========== 七、构建统计汇总数据 ==========
stats = [
{'name': '记录总数', 'amount': row_count},
{'name': '管理面积合计', 'amount': round(total_management_area, 4), 'display_amount': f"{round(total_management_area, 4):.4f}"},
]
_logger.info("[MANAGEMENT_AREA] query_date=%s, lines=%d, rows=%d, total_area=%.4f",
query_date, len(manage_lines), len(rows), total_management_area)
return {"rows": rows, "stats": stats}