Merge pull request !12154 from 李鹏宇/dev_wang
This commit is contained in:
李鹏宇
2026-06-16 13:09:32 +00:00
committed by Gitee
26 changed files with 488 additions and 245 deletions
@@ -25,6 +25,7 @@ class CumArrearsReport(models.Model):
date_start = params.get("date_start")
date_end = params.get("date_end")
summary_only = params.get("summary_only", False)
# 查询月范围:query_month_start = 查询日所在月的月初,query_month_end = 查询日所在月的月末
# 注意:date_end 传的是用户选的某一天(如月初1号),不是月末
# 所以 query_month_end 要用该月的最后一天,否则同月收款会被误判为欠收
@@ -101,6 +102,13 @@ class CumArrearsReport(models.Model):
rows = []
total_amount = 0.0
if summary_only:
total_amount = sum(round(data['amount'], 2) for data in rec_map.values())
stats = [
{'name': '记录总数', 'amount': len(rec_map)},
{'name': '累计欠收', 'amount': round(total_amount, 2)},
]
return {"rows": [], "stats": stats}
for idx, (rid, data) in enumerate(rec_map.items(), start=1):
rec = data['rec']
contract = rec.contract_id
@@ -20,6 +20,7 @@ class CumIncomeReport(models.Model):
today = datetime.date.today()
date_start = params.get("date_start")
summary_only = params.get("summary_only", False)
# 取查询日所在年份和月份
if date_start:
query_date = datetime.datetime.fromisoformat(date_start).date()
@@ -70,6 +71,13 @@ class CumIncomeReport(models.Model):
rows = []
total_amount = 0.0
if summary_only:
total_amount = sum(round(line.amount or 0.0, 2) for line in lines)
stats = [
{'name': '记录总数', 'amount': len(lines)},
{'name': '累计收入', 'amount': round(total_amount, 2)},
]
return {"rows": [], "stats": stats}
for idx, line in enumerate(lines, start=1):
rec = line.receivable_id
contract = rec.contract_id if rec else None
@@ -9,6 +9,7 @@ class CurrentMonthReceivedReport(models.Model):
@api.model
def get_current_month_received_report(self, params):
"""归属本月实收报表:按收款行自身的 reconciliation_date(对账日期)筛选已收金额。"""
summary_only = params.get("summary_only", False)
domain = []
keyword = params.get("keyword")
@@ -89,6 +90,11 @@ class CurrentMonthReceivedReport(models.Model):
continue # 这笔归到本月预收,不计入归属本月
filtered_lines.append(line)
if summary_only:
total_amount = sum(line.amount or 0.0 for line in filtered_lines)
stats = [{"name": "实收总额", "amount": round(total_amount, 2)}]
return {"rows": [], "stats": stats}
rows = []
total_amount = 0.0
@@ -21,16 +21,23 @@ class DepositCumArrearsReport(models.Model):
payout_date <= 查询月末 → 查询时点时已收到,不计入欠收
"""
date_start = params.get("date_start")
summary_only = params.get("summary_only", False)
query_date = datetime.datetime.fromisoformat(date_start).date() if date_start else datetime.date.today()
month_first = query_date.replace(day=1)
last_day = calendar.monthrange(query_date.year, query_date.month)[1]
month_last = query_date.replace(day=last_day)
# 放宽 domain,只做基础筛选
# 优化:将内存过滤条件尽量推到 domain 层,减少加载记录数
# - unpaid/no_paid:due_date <= 查询月末(到期未收)
# - paid:payout_date > 查询月末(查询时点尚未收款)—— 把这个条件推到 domain
domain = [
('management_type_id.name', '=', '押金'),
('is_invalid', '!=', True),
('deposit_state', 'in', ['unpaid', 'no_paid', 'paid']),
'|',
('deposit_state', 'in', ['unpaid', 'no_paid']),
'&',
('deposit_state', '=', 'paid'),
('payout_date', '>', month_last),
]
keyword = params.get("keyword")
@@ -62,19 +69,25 @@ class DepositCumArrearsReport(models.Model):
lines = self.sudo().search(domain, order="contract_id asc")
# 内存过滤:按状态分别处理
# - 未收(unpaid)/收部分(no_paid):due_date 存在且 <= 查询月末(到期未收)
# - 已收(paid):payout_date > 查询月末(查询时点上尚未收款)
# 内存过滤:unpaid/no_paid 需要额外判断 due_date <= 查询月末
# paid 的 payout_date > month_last 已在 domain 中处理
lines = lines.filtered(
lambda l: (
l.deposit_state in ('unpaid', 'no_paid') and l.due_date and l.due_date <= month_last
) or (
l.deposit_state == 'paid' and l.payout_date and l.payout_date > month_last
l.deposit_state == 'paid'
)
)
rows = []
total_amount = 0.0
if summary_only:
total_amount = sum(round(rec.submitted or 0.0, 2) for rec in lines)
stats = [
{'name': '记录总数', 'amount': len(lines)},
{'name': '合计欠收', 'amount': round(total_amount, 2)},
]
return {"rows": [], "stats": stats}
for idx, rec in enumerate(lines, start=1):
amount = round(rec.submitted or 0.0, 2)
total_amount += amount
@@ -17,6 +17,7 @@ class DepositMonthReceivableReport(models.Model):
- 非作废记录
"""
date_start = params.get("date_start")
summary_only = params.get("summary_only", False)
query_date = datetime.datetime.fromisoformat(date_start).date() if date_start else datetime.date.today()
month_first = query_date.replace(day=1)
last_day = calendar.monthrange(query_date.year, query_date.month)[1]
@@ -49,6 +50,14 @@ class DepositMonthReceivableReport(models.Model):
lines = self.sudo().search(domain, order="contract_id asc")
if summary_only:
total_amount = sum(round(rec.original_amount or 0.0, 2) for rec in lines)
stats = [
{'name': '记录总数', 'amount': len(lines)},
{'name': '合计金额', 'amount': round(total_amount, 2)},
]
return {"rows": [], "stats": stats}
rows = []
total_amount = 0.0
for idx, rec in enumerate(lines, start=1):
@@ -22,6 +22,7 @@ class DepositMonthReceivedReport(models.Model):
本月实收取 payout_date 在查询月内,避免同一记录出现在两个报表
"""
date_start = params.get("date_start")
summary_only = params.get("summary_only", False)
query_date = datetime.datetime.fromisoformat(date_start).date() if date_start else datetime.date.today()
month_first = query_date.replace(day=1)
last_day = calendar.monthrange(query_date.year, query_date.month)[1]
@@ -75,6 +76,14 @@ class DepositMonthReceivedReport(models.Model):
)
)
if summary_only:
total_amount = sum(round((rec.original_amount or 0.0) - (rec.discount_amount or 0.0), 2) for rec in lines)
stats = [
{'name': '记录总数', 'amount': len(lines)},
{'name': '合计金额', 'amount': round(total_amount, 2)},
]
return {"rows": [], "stats": stats}
rows = []
total_amount = 0.0
for idx, rec in enumerate(lines, start=1):
@@ -19,6 +19,7 @@ class DepositMonthRefundReport(models.Model):
- 金额:original_amount - discount_amount
"""
date_start = params.get("date_start")
summary_only = params.get("summary_only", False)
query_date = datetime.datetime.fromisoformat(date_start).date() if date_start else datetime.date.today()
month_first = query_date.replace(day=1)
last_day = calendar.monthrange(query_date.year, query_date.month)[1]
@@ -64,6 +65,14 @@ class DepositMonthRefundReport(models.Model):
lines = self.sudo().search(domain, order="contract_id asc")
if summary_only:
total_amount = sum(round((rec.original_amount or 0.0) - (rec.discount_amount or 0.0), 2) for rec in lines)
stats = [
{'name': '记录总数', 'amount': len(lines)},
{'name': '合计退款', 'amount': round(total_amount, 2)},
]
return {"rows": [], "stats": stats}
rows = []
total_amount = 0.0
for idx, rec in enumerate(lines, start=1):
@@ -22,6 +22,7 @@ class DepositPreReceivedReport(models.Model):
(已退状态不受 payout_date 约束)
"""
date_start = params.get("date_start")
summary_only = params.get("summary_only", False)
query_date = datetime.datetime.fromisoformat(date_start).date() if date_start else datetime.date.today()
month_first = query_date.replace(day=1)
_, last_day = calendar.monthrange(query_date.year, query_date.month)
@@ -78,6 +79,14 @@ class DepositPreReceivedReport(models.Model):
)
)
if summary_only:
total_amount = sum(round((rec.original_amount or 0.0) - (rec.discount_amount or 0.0), 2) for rec in lines)
stats = [
{'name': '记录总数', 'amount': len(lines)},
{'name': '合计金额', 'amount': round(total_amount, 2)},
]
return {"rows": [], "stats": stats}
rows = []
total_amount = 0.0
for idx, rec in enumerate(lines, start=1):
@@ -23,6 +23,7 @@ class DepositTotalReport(models.Model):
(已退状态不受 payout_date 约束)
"""
date_start = params.get("date_start")
summary_only = params.get("summary_only", False)
query_date = datetime.datetime.fromisoformat(date_start).date() if date_start else datetime.date.today()
month_first = query_date.replace(day=1)
last_day = calendar.monthrange(query_date.year, query_date.month)[1]
@@ -100,6 +101,14 @@ class DepositTotalReport(models.Model):
)
)
if summary_only:
total_amount = sum(round((rec.original_amount or 0.0) - (rec.discount_amount or 0.0), 2) for rec in lines)
stats = [
{'name': '记录总数', 'amount': len(lines)},
{'name': '合计已收', 'amount': round(total_amount, 2)},
]
return {"rows": [], "stats": stats}
rows = []
total_amount = 0.0
for idx, rec in enumerate(lines, start=1):
+25 -21
View File
@@ -47,6 +47,7 @@ class LeasedAreaReport(models.Model):
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
@@ -244,6 +245,7 @@ class LeasedAreaReport(models.Model):
# ========== 八、第二轮遍历:构建报表行 + 计算合计 ==========
rows = []
row_count = 0
total_property_area = 0.0 # 物业已租面积合计
total_contract_area = 0.0 # 合同已租面积合计
counted_pids = set() # 已参与合计计算的物业id
@@ -351,30 +353,32 @@ class LeasedAreaReport(models.Model):
else:
actual_end_str = ""
rows.append({
"序号": idx,
"物业id": display_property_ids,
"物业名称": property_name_value,
"合同编号": {
'model': 'property.lease.contract',
'id': contract_record_id,
'display_name': contract_code,
} if contract_record_id else contract_code or "",
"承租方": {
'model': 'property.tenant.information',
'id': lessee_record_id,
'display_name': lessee_name,
} if lessee_record_id else lessee_name or "",
"实际开始时间": actual_start_str,
"实际结束日期": actual_end_str,
"物业已租面积": round(property_area, 4),
"合同已租面积": round(contract_area, 4),
"片区管理员": admin_display,
})
if not summary_only:
rows.append({
"序号": idx,
"物业id": display_property_ids,
"物业名称": property_name_value,
"合同编号": {
'model': 'property.lease.contract',
'id': contract_record_id,
'display_name': contract_code,
} if contract_record_id else contract_code or "",
"承租方": {
'model': 'property.tenant.information',
'id': lessee_record_id,
'display_name': lessee_name,
} if lessee_record_id else lessee_name or "",
"实际开始时间": actual_start_str,
"实际结束日期": actual_end_str,
"物业已租面积": round(property_area, 4),
"合同已租面积": round(contract_area, 4),
"片区管理员": admin_display,
})
row_count += 1
# ========== 九、构建统计汇总数据 ==========
stats = [
{'name': '记录总数', 'amount': len(rows)},
{'name': '记录总数', 'amount': row_count},
{'name': '物业已租面积合计', 'amount': round(total_property_area, 4), 'display_amount': f"{round(total_property_area, 4):.4f}"},
{'name': '合同已租面积合计', 'amount': round(total_contract_area, 4), 'display_amount': f"{round(total_contract_area, 4):.4f}"},
]
+28 -24
View File
@@ -39,6 +39,7 @@ class ManagementAreaReport(models.Model):
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
@@ -126,6 +127,7 @@ class ManagementAreaReport(models.Model):
# ========== 六、构建报表行 ==========
rows = []
row_count = 0
total_management_area = 0.0
seen_property_ids = set() # 已计入合计的物业id集合
@@ -219,33 +221,35 @@ class ManagementAreaReport(models.Model):
seen_property_ids.add(property_rec.id)
# --- 6.6 构建行数据(物业名称、合同编号、承租方支持穿透) ---
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,
})
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': len(rows)},
{'name': '记录总数', 'amount': row_count},
{'name': '管理面积合计', 'amount': round(total_management_area, 4), 'display_amount': f"{round(total_management_area, 4):.4f}"},
]
@@ -18,6 +18,7 @@ class MonthReceivableReport(models.Model):
"""
# 解析查询月
query_date = datetime.datetime.fromisoformat(params['date_start']).date() if params.get('date_start') else datetime.date.today().replace(day=1)
summary_only = params.get("summary_only", False)
month_first = query_date.replace(day=1)
last_day = calendar.monthrange(query_date.year, query_date.month)[1]
month_last = query_date.replace(day=last_day)
@@ -81,6 +82,11 @@ class MonthReceivableReport(models.Model):
rec_map[rid] = {'rec': line.receivable_id, 'amount': 0.0}
rec_map[rid]['amount'] += line.amount or 0.0
if summary_only:
total_amount = sum(round(data['amount'], 2) for data in rec_map.values())
stats = [{'name': '合计', 'amount': round(total_amount, 2)}]
return {'rows': [], 'stats': stats}
rows = []
total_amount = 0.0
for idx, (rid, data) in enumerate(rec_map.items(), start=1):
@@ -19,6 +19,7 @@ class MonthlyPrecollectionReport(models.Model):
today = datetime.date.today()
date_start = params.get("date_start")
summary_only = params.get("summary_only", False)
query_date = datetime.datetime.fromisoformat(date_start).date() if date_start else today
# 查询月月初和月末
@@ -102,6 +103,14 @@ class MonthlyPrecollectionReport(models.Model):
rec_map[rid] = {'rec': line.receivable_id, 'amount': 0.0}
rec_map[rid]['amount'] += line.amount or 0.0
if summary_only:
total_amount = sum(round(data['amount'], 2) for data in rec_map.values())
stats = [
{'name': '记录总数', 'amount': len(rec_map)},
{'name': '预收总额', 'amount': round(total_amount, 2)},
]
return {"rows": [], "stats": stats}
rows = []
total_amount = 0.0
for idx, (rid, data) in enumerate(rec_map.items(), start=1):
@@ -18,6 +18,8 @@ class ParkingFeeCurrentMonthReceivable(models.Model):
# 关键词
keyword = params.get("keyword")
# summary_only:看板只取 stats 不构建 rows
summary_only = params.get("summary_only", False)
if keyword:
domain += [
'|', '|',
@@ -71,6 +73,10 @@ class ParkingFeeCurrentMonthReceivable(models.Model):
rows = []
total_amount = 0.0
# summary_only:跳过 rows 构建,直接算 stats
if summary_only:
total_amount = sum(rec.original_amount or 0.0 for rec in receivables)
return {"rows": [], "stats": [{"name": "合计", "amount": round(total_amount, 2)}]}
for idx, rec in enumerate(receivables, start=1):
amount = rec.original_amount or 0.0
total_amount += amount
@@ -17,6 +17,8 @@ class ParkingFeeCurrentMonthReport(models.Model):
domain = []
keyword = params.get("keyword")
# summary_only:看板只取 stats 不构建 rows
summary_only = params.get("summary_only", False)
if keyword:
domain += [
'|', '|',
@@ -77,6 +79,10 @@ class ParkingFeeCurrentMonthReport(models.Model):
rows = []
total_amount = 0.0
# summary_only:跳过 rows 构建,直接算 stats
if summary_only:
total_amount = sum(rec.delivered1 or 0.0 for rec in receivables)
return {"rows": [], "stats": [{"name": "合计", "amount": round(total_amount, 2)}]}
for idx, rec in enumerate(receivables, start=1):
amount = rec.delivered1 or 0.0
total_amount += amount
@@ -18,6 +18,8 @@ class ParkingFeeYearlyReport(models.Model):
# 关键词
keyword = params.get("keyword")
# summary_only:看板只取 stats 不构建 rows
summary_only = params.get("summary_only", False)
if keyword:
domain += [
'|', '|',
@@ -82,6 +84,10 @@ class ParkingFeeYearlyReport(models.Model):
rows = []
total_amount = 0.0
# summary_only:跳过 rows 构建,直接算 stats
if summary_only:
total_amount = sum(rec.original_amount or 0.0 for rec in receivables)
return {"rows": [], "stats": [{"name": "合计", "amount": round(total_amount, 2)}]}
for idx, rec in enumerate(receivables, start=1):
amount = rec.original_amount or 0.0
total_amount += amount
+26 -10
View File
@@ -34,6 +34,7 @@ class PenaltyArrearsReport(models.Model):
# ========== 一、参数解析 ==========
date_end = params.get("date_end") # 查询截止日期(如 "2026-01-01 23:59:59")
summary_only = params.get("summary_only", False)
query_date = None # 查询日期 date 对象,初始化为空
if date_end: # 如果有截止日期参数
query_date = datetime.datetime.fromisoformat(date_end).date() # 将字符串转为 date 对象
@@ -169,7 +170,8 @@ class PenaltyArrearsReport(models.Model):
total_uncollected += penalty_uncollected_amt
contract = penalty_rec.contract_id
rows.append({
if not summary_only:
rows.append({
"来源": "A",
"租金ID": rent_rec.id if rent_rec else "",
"租金应收日": rent_due_date_str,
@@ -230,6 +232,21 @@ class PenaltyArrearsReport(models.Model):
# 对这些租金记录,动态计算违约金总额,判断是否有未收
# 先查出涉及的租金记录
rent_ids_b = list(rent_penalty_collected.keys())
# ========== 优化:批量查询所有租金对应的非违约金收款行,消除N+1 ==========
all_modify_lines = self.env['receivable.modify.line'].sudo().search([
('receivable_id', 'in', rent_ids_b),
('reconciliation_date', '!=', False),
('is_penalty', '=', False),
], order='receivable_id, reconciliation_date asc')
# 按 receivable_id 分组
modify_lines_by_rent = {}
for ml in all_modify_lines:
rid = ml.receivable_id.id
if rid not in modify_lines_by_rent:
modify_lines_by_rent[rid] = []
modify_lines_by_rent[rid].append(ml)
# ======================================================================
rent_recs_b = self.sudo().browse(rent_ids_b) if rent_ids_b else self.sudo().browse([])
for rent_rec in rent_recs_b:
@@ -264,11 +281,8 @@ class PenaltyArrearsReport(models.Model):
penalty_rate = _get_penalty_rate(rent_rec.contract_id)
# --- 获取该租金的支付历史(非违约金的 modify lines) ---
modify_lines = self.env['receivable.modify.line'].sudo().search([
('receivable_id', '=', rent_rec.id),
('reconciliation_date', '!=', False),
('is_penalty', '=', False), # 非违约金收款行
], order='reconciliation_date asc')
# 从批量预分组字典中取,消除N+1(第233-245行已批量查询并分组)
modify_lines = modify_lines_by_rent.get(rent_rec.id, [])
# --- 分段计算违约金总额 ---
rent_amount = (rent_rec.original_amount or 0.0) - (rent_rec.discount_amount or 0.0)
@@ -343,8 +357,9 @@ class PenaltyArrearsReport(models.Model):
total_uncollected += penalty_uncollected_amt
contract = rent_rec.contract_id
rows.append({
"来源": "B",
if not summary_only:
rows.append({
"来源": "B",
"租金ID": rent_rec.id,
"租金应收日": due_date.strftime("%Y-%m-%d"),
"租金收款日期": rent_rec.payout_date.strftime("%Y-%m-%d") if rent_rec.payout_date else "",
@@ -475,8 +490,9 @@ class PenaltyArrearsReport(models.Model):
total_uncollected += penalty_uncollected_amt
contract = rent_rec.contract_id
rows.append({
"来源": "C",
if not summary_only:
rows.append({
"来源": "C",
"租金ID": rent_rec.id,
"租金应收日": due_date.strftime("%Y-%m-%d"),
"租金收款日期": rent_rec.payout_date.strftime("%Y-%m-%d") if rent_rec.payout_date else "",
@@ -29,6 +29,7 @@ class PenaltyMonthReceivedReport(models.Model):
# ========== 一、参数解析 ==========
date_start = params.get("date_start") # 查询月开始日期
date_end = params.get("date_end") # 查询月结束日期
summary_only = params.get("summary_only", False)
# 没有时间范围则无数据
if not date_start or not date_end:
@@ -205,38 +206,39 @@ class PenaltyMonthReceivedReport(models.Model):
total_collected += pay_amount
contract = penalty_rec.contract_id
rows.append({
"来源": "A",
"租金ID": rent_rec.id if rent_rec else "",
"租金应收日": rent_due_date_str,
"租金收款日期": rent_payout_date_str,
"租金金额": rent_amount_val,
"租金已收": rent_collected_amt,
"租金未收": rent_uncollected_amt,
"租金状态": rent_status,
"违约金金额": penalty_amount,
"违约金已收": penalty_collected_amt,
"违约金未收": penalty_uncollected_amt,
"违约金状态": penalty_status,
"违约金应收开始": penalty_rec.due_date.strftime("%Y-%m-%d") if penalty_rec.due_date else "",
"实收金额": round(pay_amount, 2),
"收款日期": ml.reconciliation_date.strftime("%Y-%m-%d") if ml.reconciliation_date else "",
"收款备注": ml.remake1 or "",
"收费区间": penalty_rec.charging_range or "",
"备注": penalty_rec.remark or "",
"合同编号": {
'model': 'property.lease.contract',
'id': contract.id,
'display_name': contract.contract_code,
} if contract else "",
"物业名称": _build_property_display(contract, prop_name),
"承租方名称": {
'model': 'property.tenant.information',
'id': penalty_rec.lessee_id.id,
'display_name': penalty_rec.lessee_id.name,
} if penalty_rec.lessee_id else "",
"所属公司": penalty_rec.company_id.name if penalty_rec.company_id else "",
})
if not summary_only:
rows.append({
"来源": "A",
"租金ID": rent_rec.id if rent_rec else "",
"租金应收日": rent_due_date_str,
"租金收款日期": rent_payout_date_str,
"租金金额": rent_amount_val,
"租金已收": rent_collected_amt,
"租金未收": rent_uncollected_amt,
"租金状态": rent_status,
"违约金金额": penalty_amount,
"违约金已收": penalty_collected_amt,
"违约金未收": penalty_uncollected_amt,
"违约金状态": penalty_status,
"违约金应收开始": penalty_rec.due_date.strftime("%Y-%m-%d") if penalty_rec.due_date else "",
"实收金额": round(pay_amount, 2),
"收款日期": ml.reconciliation_date.strftime("%Y-%m-%d") if ml.reconciliation_date else "",
"收款备注": ml.remake1 or "",
"收费区间": penalty_rec.charging_range or "",
"备注": penalty_rec.remark or "",
"合同编号": {
'model': 'property.lease.contract',
'id': contract.id,
'display_name': contract.contract_code,
} if contract else "",
"物业名称": _build_property_display(contract, prop_name),
"承租方名称": {
'model': 'property.tenant.information',
'id': penalty_rec.lessee_id.id,
'display_name': penalty_rec.lessee_id.name,
} if penalty_rec.lessee_id else "",
"所属公司": penalty_rec.company_id.name if penalty_rec.company_id else "",
})
# ========== 三、来源B:receivable.modify.line 中 is_penalty=True 的记录 ==========
# 核心关系:违约金的 due_date(应收开始)= 关联租金的 payout_date(收款日期)
@@ -252,6 +254,36 @@ class PenaltyMonthReceivedReport(models.Model):
]
penalty_modify_lines = self.env['receivable.modify.line'].sudo().search(source_b_domain)
# ========== 优化:批量查询所有潜在匹配的租金,消除N+1 ==========
# 收集所有需要的 penalty_due 日期
penalty_due_set = set()
for pml in penalty_modify_lines:
penalty_rec = pml.receivable_id
if not penalty_rec or not penalty_rec.due_date:
continue
penalty_due = penalty_rec.due_date.date() if hasattr(penalty_rec.due_date, 'date') else penalty_rec.due_date
penalty_due_set.add(penalty_due)
# 批量查询所有潜在匹配的租金
if penalty_due_set:
batch_rent_domain = [('payout_date', 'in', list(penalty_due_set))]
batch_rent_recs = self.sudo().search(batch_rent_domain)
# 按 (payout_date, lessee_id.id, contract_id.id) 分组
rent_by_key = {}
for rent in batch_rent_recs:
key = (
rent.payout_date.date() if hasattr(rent.payout_date, 'date') else rent.payout_date,
rent.lessee_id.id if rent.lessee_id else None,
rent.contract_id.id if rent.contract_id else None,
)
if key not in rent_by_key:
rent_by_key[key] = []
rent_by_key[key].append(rent)
else:
rent_by_key = {}
# =====================================================================
for pml in penalty_modify_lines:
# 取违约金金额(delivered1 字段)
pay_amount = round(pml.delivered1 or 0.0, 2)
@@ -273,17 +305,13 @@ class PenaltyMonthReceivedReport(models.Model):
# 统一转为 date 对象用于搜索
penalty_due = penalty_due_date.date() if hasattr(penalty_due_date, 'date') else penalty_due_date
# 在 property.accounts.receivable 中查找关联租金
# 条件:payout_date = 违约金的 due_date + 同承租方 + 同合同
rent_domain = [
('payout_date', '=', penalty_due), # 租金收款日期 = 违约金应收开始日期
]
if pml.lessee_id:
rent_domain.append(('lessee_id', '=', pml.lessee_id.id)) # 同一承租方
if pml.contract_id:
rent_domain.append(('contract_id', '=', pml.contract_id.id)) # 同一合同
rent_recs = self.sudo().search(rent_domain)
# 从批量预分组字典中查找匹配租金,消除N+1(第262-289行已批量查询并分组)
key = (
penalty_due,
pml.lessee_id.id if pml.lessee_id else None,
pml.contract_id.id if pml.contract_id else None,
)
rent_recs = rent_by_key.get(key, [])
if not rent_recs:
continue
@@ -373,38 +401,39 @@ class PenaltyMonthReceivedReport(models.Model):
lessee_obj = penalty_rec.lessee_id or pml.lessee_id
company_obj = penalty_rec.company_id or pml.company_id
rows.append({
"来源": "B",
"租金ID": rent_receivable.id if rent_receivable else "",
"租金应收日": rent_due_date_str,
"租金收款日期": rent_payout_date_str,
"租金金额": rent_amount_val,
"租金已收": rent_collected_amt,
"租金未收": rent_uncollected_amt,
"租金状态": rent_status,
"违约金金额": penalty_amount,
"违约金已收": penalty_collected_amt,
"违约金未收": penalty_uncollected_amt,
"违约金状态": penalty_status,
"违约金应收开始": penalty_rec.due_date.strftime("%Y-%m-%d") if penalty_rec.due_date else "",
"实收金额": pay_amount,
"收款日期": pml.reconciliation_date.strftime("%Y-%m-%d") if pml.reconciliation_date else "",
"收款备注": pml.remake1 or "",
"收费区间": charging_range,
"备注": penalty_rec.remark or "",
"合同编号": {
'model': 'property.lease.contract',
'id': contract.id,
'display_name': contract.contract_code,
} if contract else "",
"物业名称": _build_property_display(contract, prop_name),
"承租方名称": {
'model': 'property.tenant.information',
'id': lessee_obj.id,
'display_name': lessee_obj.name,
} if lessee_obj else "",
"所属公司": company_obj.name if company_obj else "",
})
if not summary_only:
rows.append({
"来源": "B",
"租金ID": rent_receivable.id if rent_receivable else "",
"租金应收日": rent_due_date_str,
"租金收款日期": rent_payout_date_str,
"租金金额": rent_amount_val,
"租金已收": rent_collected_amt,
"租金未收": rent_uncollected_amt,
"租金状态": rent_status,
"违约金金额": penalty_amount,
"违约金已收": penalty_collected_amt,
"违约金未收": penalty_uncollected_amt,
"违约金状态": penalty_status,
"违约金应收开始": penalty_rec.due_date.strftime("%Y-%m-%d") if penalty_rec.due_date else "",
"实收金额": pay_amount,
"收款日期": pml.reconciliation_date.strftime("%Y-%m-%d") if pml.reconciliation_date else "",
"收款备注": pml.remake1 or "",
"收费区间": charging_range,
"备注": penalty_rec.remark or "",
"合同编号": {
'model': 'property.lease.contract',
'id': contract.id,
'display_name': contract.contract_code,
} if contract else "",
"物业名称": _build_property_display(contract, prop_name),
"承租方名称": {
'model': 'property.tenant.information',
'id': lessee_obj.id,
'display_name': lessee_obj.name,
} if lessee_obj else "",
"所属公司": company_obj.name if company_obj else "",
})
# ========== 四、构建统计汇总数据 ==========
stats = [
+104 -75
View File
@@ -29,6 +29,7 @@ class PenaltyYearCumReport(models.Model):
# ========== 一、参数解析 ==========
date_start = params.get("date_start")
date_end = params.get("date_end")
summary_only = params.get("summary_only", False)
# 没有时间范围则无数据
if not date_start or not date_end:
@@ -207,38 +208,39 @@ class PenaltyYearCumReport(models.Model):
total_collected += pay_amount
contract = penalty_rec.contract_id
rows.append({
"来源": "A",
"租金ID": rent_rec.id if rent_rec else "",
"租金应收日": rent_due_date_str,
"租金收款日期": rent_payout_date_str,
"租金金额": rent_amount_val,
"租金已收": rent_collected_amt,
"租金未收": rent_uncollected_amt,
"租金状态": rent_status,
"违约金金额": penalty_amount,
"违约金已收": penalty_collected_amt,
"违约金未收": penalty_uncollected_amt,
"违约金状态": penalty_status,
"违约金应收开始": penalty_rec.due_date.strftime("%Y-%m-%d") if penalty_rec.due_date else "",
"实收金额": round(pay_amount, 2),
"收款日期": ml.reconciliation_date.strftime("%Y-%m-%d") if ml.reconciliation_date else "",
"收款备注": ml.remake1 or "",
"收费区间": penalty_rec.charging_range or "",
"备注": penalty_rec.remark or "",
"合同编号": {
'model': 'property.lease.contract',
'id': contract.id,
'display_name': contract.contract_code,
} if contract else "",
"物业名称": _build_property_display(contract, prop_name),
"承租方名称": {
'model': 'property.tenant.information',
'id': penalty_rec.lessee_id.id,
'display_name': penalty_rec.lessee_id.name,
} if penalty_rec.lessee_id else "",
"所属公司": penalty_rec.company_id.name if penalty_rec.company_id else "",
})
if not summary_only:
rows.append({
"来源": "A",
"租金ID": rent_rec.id if rent_rec else "",
"租金应收日": rent_due_date_str,
"租金收款日期": rent_payout_date_str,
"租金金额": rent_amount_val,
"租金已收": rent_collected_amt,
"租金未收": rent_uncollected_amt,
"租金状态": rent_status,
"违约金金额": penalty_amount,
"违约金已收": penalty_collected_amt,
"违约金未收": penalty_uncollected_amt,
"违约金状态": penalty_status,
"违约金应收开始": penalty_rec.due_date.strftime("%Y-%m-%d") if penalty_rec.due_date else "",
"实收金额": round(pay_amount, 2),
"收款日期": ml.reconciliation_date.strftime("%Y-%m-%d") if ml.reconciliation_date else "",
"收款备注": ml.remake1 or "",
"收费区间": penalty_rec.charging_range or "",
"备注": penalty_rec.remark or "",
"合同编号": {
'model': 'property.lease.contract',
'id': contract.id,
'display_name': contract.contract_code,
} if contract else "",
"物业名称": _build_property_display(contract, prop_name),
"承租方名称": {
'model': 'property.tenant.information',
'id': penalty_rec.lessee_id.id,
'display_name': penalty_rec.lessee_id.name,
} if penalty_rec.lessee_id else "",
"所属公司": penalty_rec.company_id.name if penalty_rec.company_id else "",
})
# ========== 三、来源B:receivable.modify.line 中 is_penalty=True 的记录 ==========
# 核心关系:违约金的 due_date(应收开始)= 关联租金的 payout_date(收款日期)
@@ -254,6 +256,36 @@ class PenaltyYearCumReport(models.Model):
]
penalty_modify_lines = self.env['receivable.modify.line'].sudo().search(source_b_domain)
# ========== 优化:批量查询所有潜在匹配的租金,消除N+1 ==========
# 收集所有需要的 penalty_due 日期
penalty_due_set = set()
for pml in penalty_modify_lines:
penalty_rec = pml.receivable_id
if not penalty_rec or not penalty_rec.due_date:
continue
penalty_due = penalty_rec.due_date.date() if hasattr(penalty_rec.due_date, 'date') else penalty_rec.due_date
penalty_due_set.add(penalty_due)
# 批量查询所有潜在匹配的租金
if penalty_due_set:
batch_rent_domain = [('payout_date', 'in', list(penalty_due_set))]
batch_rent_recs = self.sudo().search(batch_rent_domain)
# 按 (payout_date, lessee_id.id, contract_id.id) 分组
rent_by_key = {}
for rent in batch_rent_recs:
key = (
rent.payout_date.date() if hasattr(rent.payout_date, 'date') else rent.payout_date,
rent.lessee_id.id if rent.lessee_id else None,
rent.contract_id.id if rent.contract_id else None,
)
if key not in rent_by_key:
rent_by_key[key] = []
rent_by_key[key].append(rent)
else:
rent_by_key = {}
# =====================================================================
for pml in penalty_modify_lines:
# 取违约金金额(delivered1 字段)
pay_amount = round(pml.delivered1 or 0.0, 2)
@@ -275,17 +307,13 @@ class PenaltyYearCumReport(models.Model):
# 统一转为 date 对象用于搜索
penalty_due = penalty_due_date.date() if hasattr(penalty_due_date, 'date') else penalty_due_date
# 在 property.accounts.receivable 中查找关联租金
# 条件:payout_date = 违约金的 due_date + 同承租方 + 同合同
rent_domain = [
('payout_date', '=', penalty_due), # 租金收款日期 = 违约金应收开始日期
]
if pml.lessee_id:
rent_domain.append(('lessee_id', '=', pml.lessee_id.id)) # 同一承租方
if pml.contract_id:
rent_domain.append(('contract_id', '=', pml.contract_id.id)) # 同一合同
rent_recs = self.sudo().search(rent_domain)
# 从批量预分组字典中查找匹配租金,消除N+1(第262-289行已批量查询并分组)
key = (
penalty_due,
pml.lessee_id.id if pml.lessee_id else None,
pml.contract_id.id if pml.contract_id else None,
)
rent_recs = rent_by_key.get(key, [])
if not rent_recs:
continue
@@ -374,38 +402,39 @@ class PenaltyYearCumReport(models.Model):
lessee_obj = penalty_rec.lessee_id or pml.lessee_id
company_obj = penalty_rec.company_id or pml.company_id
rows.append({
"来源": "B",
"租金ID": rent_receivable.id if rent_receivable else "",
"租金应收日": rent_due_date_str,
"租金收款日期": rent_payout_date_str,
"租金金额": rent_amount_val,
"租金已收": rent_collected_amt,
"租金未收": rent_uncollected_amt,
"租金状态": rent_status,
"违约金金额": penalty_amount,
"违约金已收": penalty_collected_amt,
"违约金未收": penalty_uncollected_amt,
"违约金状态": penalty_status,
"违约金应收开始": penalty_rec.due_date.strftime("%Y-%m-%d") if penalty_rec.due_date else "",
"实收金额": pay_amount,
"收款日期": pml.reconciliation_date.strftime("%Y-%m-%d") if pml.reconciliation_date else "",
"收款备注": pml.remake1 or "",
"收费区间": charging_range,
"备注": penalty_rec.remark or "",
"合同编号": {
'model': 'property.lease.contract',
'id': contract.id,
'display_name': contract.contract_code,
} if contract else "",
"物业名称": _build_property_display(contract, prop_name),
"承租方名称": {
'model': 'property.tenant.information',
'id': lessee_obj.id,
'display_name': lessee_obj.name,
} if lessee_obj else "",
"所属公司": company_obj.name if company_obj else "",
})
if not summary_only:
rows.append({
"来源": "B",
"租金ID": rent_receivable.id if rent_receivable else "",
"租金应收日": rent_due_date_str,
"租金收款日期": rent_payout_date_str,
"租金金额": rent_amount_val,
"租金已收": rent_collected_amt,
"租金未收": rent_uncollected_amt,
"租金状态": rent_status,
"违约金金额": penalty_amount,
"违约金已收": penalty_collected_amt,
"违约金未收": penalty_uncollected_amt,
"违约金状态": penalty_status,
"违约金应收开始": penalty_rec.due_date.strftime("%Y-%m-%d") if penalty_rec.due_date else "",
"实收金额": pay_amount,
"收款日期": pml.reconciliation_date.strftime("%Y-%m-%d") if pml.reconciliation_date else "",
"收款备注": pml.remake1 or "",
"收费区间": charging_range,
"备注": penalty_rec.remark or "",
"合同编号": {
'model': 'property.lease.contract',
'id': contract.id,
'display_name': contract.contract_code,
} if contract else "",
"物业名称": _build_property_display(contract, prop_name),
"承租方名称": {
'model': 'property.tenant.information',
'id': lessee_obj.id,
'display_name': lessee_obj.name,
} if lessee_obj else "",
"所属公司": company_obj.name if company_obj else "",
})
# ========== 四、构建统计汇总数据 ==========
stats = [
@@ -24,6 +24,7 @@ class PreAdvanceReport(models.Model):
this_month_last = next_month_first - datetime.timedelta(days=1)
date_start = params.get("date_start")
summary_only = params.get("summary_only", False)
# 应收日期在查询日当月及之后(即未来应收)
cutoff_date = datetime.datetime.fromisoformat(date_start).date() if date_start else this_month_last
# 取查询日所在月的第一天,应收日期 >= 该月第一天
@@ -123,6 +124,14 @@ class PreAdvanceReport(models.Model):
if rec_map[rid]['earliest_pay_date'] is None or pay_date_val < rec_map[rid]['earliest_pay_date']:
rec_map[rid]['earliest_pay_date'] = pay_date_val
if summary_only:
total_amount = sum(round(data['amount'], 2) for data in rec_map.values())
stats = [
{'name': '记录总数', 'amount': len(rec_map)},
{'name': '预收总额', 'amount': round(total_amount, 2)},
]
return {"rows": [], "stats": stats}
rows = []
total_amount = 0.0
for idx, (rid, data) in enumerate(rec_map.items(), start=1):
+14 -2
View File
@@ -30,6 +30,7 @@ class PreArrearsReport(models.Model):
date_start = params.get("date_start")
date_end = params.get("date_end")
summary_only = params.get("summary_only", False)
# 应收日期在查询月及之前(包含查询月)
cutoff_date = datetime.datetime.fromisoformat(date_end).date() if date_end else this_month_first
# 查询月范围(用于判断收款是否在查询月内)
@@ -121,9 +122,13 @@ class PreArrearsReport(models.Model):
covered_ids = list(rec_map.keys())
# 找出所有在 modify.line 中有记录的 receivable_id(不管 payout_date 是什么时候)
all_line_receivable_ids = set(
self.sudo().search([('receivable_id', '!=', False)]).mapped('receivable_id.id')
# 优化:用 read_group 只取 distinct receivable_id,避免全表加载到内存
all_line_receivable_groups = self.sudo().read_group(
[('receivable_id', '!=', False)],
['receivable_id'],
['receivable_id'],
)
all_line_receivable_ids = set(g['receivable_id'][0] for g in all_line_receivable_groups if g['receivable_id'])
# Part 2 只查真正"从未对过账"的 receivable
exclude_ids = all_line_receivable_ids # 包含 covered_ids 和 payout_date 被过滤掉的
@@ -178,6 +183,13 @@ class PreArrearsReport(models.Model):
# ============================================================
rows = []
total_amount = 0.0
if summary_only:
total_amount = sum(round(data['amount'], 2) for data in rec_map.values())
stats = [
{'name': '记录总数', 'amount': len(rec_map)},
{'name': '欠收总额', 'amount': round(total_amount, 2)},
]
return {"rows": [], "stats": stats}
for idx, (rid, data) in enumerate(rec_map.items(), start=1):
rec = data['rec']
contract = rec.contract_id
+15
View File
@@ -44,6 +44,7 @@ class YuthonProperty(models.Model):
@api.model
def get_complete_ledger_report(self, params):
summary_only = params.get("summary_only", False)
domain = [('is_invalid', '!=', True)]
if params.get("keyword"):
keyword = params["keyword"]
@@ -90,6 +91,20 @@ class YuthonProperty(models.Model):
total_subtotal = 0.0
idx = 1
if summary_only:
for cid, data in contract_map.items():
for rec in data['recs']:
type_name = rec.management_type_id.name if rec.management_type_id else ''
col = self._classify_fee_type(type_name)
total_fees[col] += (rec.original_amount or 0.0) - (rec.discount_amount or 0.0)
subtotal = sum((rec.original_amount or 0.0) - (rec.discount_amount or 0.0) for rec in data['recs'])
total_subtotal += subtotal
summary_stats = []
for col in self.FEE_COLUMNS:
summary_stats.append({'name': col, 'amount': round(total_fees[col], 2)})
summary_stats.append({'name': '小计合计', 'amount': round(total_subtotal, 2)})
return {"rows": [], "stats": summary_stats}
for cid, data in contract_map.items():
info_rec = data['info_rec']
fee_row = {col: 0.0 for col in self.FEE_COLUMNS}
+28 -24
View File
@@ -39,6 +39,7 @@ class RentAreaReport(models.Model):
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
@@ -124,6 +125,7 @@ class RentAreaReport(models.Model):
# ========== 六、构建报表行 ==========
rows = []
row_count = 0
total_rent_area = 0.0
seen_property_ids = set() # 已计入合计的物业id集合
@@ -215,33 +217,35 @@ class RentAreaReport(models.Model):
seen_property_ids.add(property_rec.id)
# --- 6.6 构建行数据(物业名称、合同编号、承租方支持穿透) ---
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(rent_area, 4),
"片区管理员": admin_display,
})
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(rent_area, 4),
"片区管理员": admin_display,
})
row_count += 1
# ========== 七、构建统计汇总数据 ==========
stats = [
{'name': '记录总数', 'amount': len(rows)},
{'name': '记录总数', 'amount': row_count},
{'name': '可租面积合计', 'amount': round(total_rent_area, 4), 'display_amount': f"{round(total_rent_area, 4):.4f}"},
]
@@ -12,6 +12,7 @@ class TotalReceivedReport(models.Model):
"""本月实收总额 = 归属本月实收 + 本月预收
取数逻辑:收款日期(reconciliation_date)在查询月内的所有收款记录,金额取 amount
"""
summary_only = params.get("summary_only", False)
domain = [
('reconciliation_date', '!=', False),
('receivable_id.is_invalid', '!=', True),
@@ -61,6 +62,11 @@ class TotalReceivedReport(models.Model):
lines = self.search(domain, order='reconciliation_date asc')
if summary_only:
total_amount = sum(round(line.amount or 0.0, 2) for line in lines)
stats = [{'name': '合计', 'amount': round(total_amount, 2)}]
return {'rows': [], 'stats': stats}
rows = []
total_amount = 0.0
for idx, line in enumerate(lines, start=1):
+8 -4
View File
@@ -53,11 +53,13 @@ class YuthonDashboard(models.Model):
def safe_get(model_name, method_name, stats_index=1, overrides=None):
"""安全调用子报表方法,返回汇总金额
:param overrides: 可选 dict,覆盖 base_params 中的参数
看板只取 stats 不构建 rows,传 summary_only=True 减少遍历开销
"""
try:
params = dict(base_params)
if overrides:
params.update(overrides)
params['summary_only'] = True # 看板只取汇总,跳过 rows 构建
result = getattr(self.env[model_name], method_name)(params)
stats = result.get('stats', [])
if stats and len(stats) > stats_index:
@@ -427,18 +429,20 @@ class YuthonDashboard(models.Model):
已租面积拆为两个卡片:物业已租面积、合同已租面积
"""
try:
# 管理面积:调用管理面积报表
management_result = self.env['management.area.report'].get_management_area_report(base_params)
# 管理面积:调用管理面积报表(summary_only=True 跳过 rows 构建)
area_params = dict(base_params)
area_params['summary_only'] = True
management_result = self.env['management.area.report'].get_management_area_report(area_params)
management_stats = management_result.get('stats', [])
total_management = management_stats[1].get('amount', 0.0) if len(management_stats) > 1 else 0.0
# 可租面积:调用可租面积报表
rent_result = self.env['rent.area.report'].get_rent_area_report(base_params)
rent_result = self.env['rent.area.report'].get_rent_area_report(area_params)
rent_stats = rent_result.get('stats', [])
total_rent = rent_stats[1].get('amount', 0.0) if len(rent_stats) > 1 else 0.0
# 已租面积:调用已租面积报表(返回物业已租面积和合同已租面积两个汇总)
leased_result = self.env['leased.area.report'].get_leased_area_report(base_params)
leased_result = self.env['leased.area.report'].get_leased_area_report(area_params)
leased_stats = leased_result.get('stats', [])
# stats[1] = 物业已租面积合计, stats[2] = 合同已租面积合计
total_property_leased = leased_stats[1].get('amount', 0.0) if len(leased_stats) > 1 else 0.0
@@ -52,10 +52,8 @@ class YuthonDashboard extends Component {
} catch (e) { /* ignore */ }
onMounted(() => {
// 如果已从缓存恢复数据,跳过重新查询
if (!restored) {
this.onSearch();
}
// 首次进入不自动查询,等用户选了查询条件后点击"查询"或切换筛选器时再查
// 如果已从缓存恢复数据,直接展示即可
});
}