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

171 lines
6.9 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 calendar
from odoo import api, models
from .report_utils import _id_list # 导入工具函数:将参数转换为列表格式,支持多选筛选
class MonthlyPrecollectionReport(models.Model):
_inherit = 'receivable.modify.line'
@api.model
def get_monthly_precollection_report(self, params):
"""
本月预收报表(数据源:receivable.modify.line):
- 收款日期(reconciliation_date)在查询月内
- 收款日期早于应收日期(reconciliation_date < due_date)且跨月
- 金额取收款记录的 amount
- 按应收账单聚合,每张账单只出一行
"""
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
# 查询月月初和月末
month_start = query_date.replace(day=1)
last_day = calendar.monthrange(query_date.year, query_date.month)[1]
month_end = query_date.replace(day=last_day)
domain = [
('reconciliation_date', '>=', month_start),
('reconciliation_date', '<=', month_end),
('receivable_id.is_invalid', '!=', True),
]
keyword = params.get("keyword")
if keyword:
domain += [
'|', '|',
('receivable_id.contract_id.contract_code', 'ilike', keyword),
('receivable_id.property_name', 'ilike', keyword),
('receivable_id.lessee_id.name', 'ilike', keyword),
]
condition = params.get('condition')
partner_id = _id_list(params.get('partner_id')) # 将partner_id转换为列表,用于多选筛选
company_id = _id_list(params.get('company_id'))
management_type_id = params.get('management_type_id')
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('receivable_id.precinct_id', 'in', partner_id)) # 使用in操作符支持多选片区筛选
if company_id:
domain.append(('receivable_id.company_id', 'in', company_id))
if property_id:
domain.append(('receivable_id.property_ids', 'in', [property_id]))
if lessee_id:
domain.append(('receivable_id.lessee_id', '=', lessee_id))
if contract_id:
domain.append(('receivable_id.contract_id', '=', contract_id))
if management_type_id:
domain.append(('receivable_id.management_type_id', '=', management_type_id))
else:
domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金']))
lines = self.sudo().search(domain, order="receivable_id asc, reconciliation_date asc")
# --- 显式过滤:正确处理 datetime/date 类型比较 ---
# 预收条件:收款日期早于应收日期且跨月
filtered_lines = []
for line in lines:
# 取收款日期(统一转为 date)
pay_date = line.reconciliation_date
if not pay_date:
continue
if hasattr(pay_date, 'date'): # datetime → date
pay_date = pay_date.date()
# 取应收日期
due = line.receivable_id.due_date if line.receivable_id else None
if not due:
continue
# 条件:提前收款(收款日期 < 应收日期)
if not (pay_date < due):
continue
# 条件:跨月(不在同一年同一月)
if pay_date.year == due.year and pay_date.month == due.month:
continue
filtered_lines.append(line)
# 按应收账单聚合(只累加满足预收条件的 line.amount)
rec_map = {}
for line in filtered_lines:
rid = line.receivable_id.id
if rid not in rec_map:
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):
rec = data['rec']
contract = rec.contract_id
amount = round(data['amount'], 2)
total_amount += amount
# 物业穿透
property_val = ''
if contract and contract.lease_ids and contract.lease_ids[0].address_id:
prop = contract.lease_ids[0].address_id
property_val = {
'model': 'yuthon.property',
'id': prop.id,
'display_name': rec.property_name or prop.name,
}
elif rec:
property_val = rec.property_name or ''
# 应收金额 = original_amount - discount_amount
receivable_amount = round((rec.original_amount or 0.0) - (rec.discount_amount or 0.0), 2)
rows.append({
"序号": idx,
"合同编号": {
'model': 'property.lease.contract',
'id': contract.id,
'display_name': contract.contract_code,
} if contract else "",
"物业名称": property_val,
"承租方名称": {
'model': 'property.tenant.information',
'id': rec.lessee_id.id,
'display_name': rec.lessee_id.name,
} if rec.lessee_id else "",
"收款类型": rec.management_type_id.name if rec.management_type_id else "",
"应收开始": rec.due_date.strftime('%Y-%m-%d') if rec.due_date else '',
"应收结束": rec.due_date_end.strftime('%Y-%m-%d') if rec.due_date_end else '',
"金额": amount,
"应收金额": receivable_amount,
"所属公司": contract.lessor_id.name if contract and contract.lessor_id else "",
"管理员": {
'model': 'res.users',
'id': rec.admin_id.id,
'display_name': rec.admin_id.name,
} if rec.admin_id else "",
})
stats = [
{'name': '记录总数', 'amount': len(rows)},
{'name': '预收总额', 'amount': round(total_amount, 2)},
]
return {"rows": rows, "stats": stats}