Files
gzth/yuthon_report/models/pre_advance_report.py
T
2026-07-09 18:33:53 +08:00

200 lines
9.0 KiB
Python
Raw 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
from odoo import api, models
from .report_utils import _id_list # 导入工具函数:将参数转换为列表格式,支持多选筛选
class PreAdvanceReport(models.Model):
_inherit = 'receivable.modify.line'
@api.model
def get_pre_advance_report(self, params):
"""
前期预收报表(数据源:receivable.modify.line):
- 收款日期(reconciliation_date)早于应收日期(due_date)
- 收款日期和应收日期不在同一年同一月(跨月才算预收)
- 应收日期限制在查询月当月(如查1月,只显示应收日期在1月的记录)
- 金额取收款记录的 amount
- 按应收账单聚合,每张账单只出一行
"""
today = datetime.date.today()
if today.month == 12:
next_month_first = today.replace(year=today.year + 1, month=1, day=1)
else:
next_month_first = today.replace(month=today.month + 1, day=1)
this_month_last = next_month_first - datetime.timedelta(days=1)
date_start = params.get("date_start")
summary_only = params.get("summary_only", False)
# 应收日期限制在查询月当月(即1月份查就只查1月份的应收)
cutoff_date = datetime.datetime.fromisoformat(date_start).date() if date_start else this_month_last
# 取查询日所在月的第一天和最后一天
cutoff_month_first = cutoff_date.replace(day=1)
if cutoff_month_first.month == 12:
next_month_first = cutoff_month_first.replace(year=cutoff_month_first.year + 1, month=1, day=1)
else:
next_month_first = cutoff_month_first.replace(month=cutoff_month_first.month + 1, day=1)
cutoff_month_last = next_month_first - datetime.timedelta(days=1)
# 核心逻辑:前期预收 = 截止到查询日之前已提前收取的当月应收款
# 条件1:due_date 在查询月当月
# 条件2:存在收款记录(后续再精确过滤)
# 注意:不在此处限制 reconciliation_date,因为需要先查出所有相关记录再做聚合
domain = [
('reconciliation_date', '!=', False),
('receivable_id.due_date', '>=', cutoff_month_first), # 应收月 >= 查询月月初
('receivable_id.due_date', '<=', cutoff_month_last), # 应收月 <= 查询月月末
('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 类型比较 ---
# 预收条件(三个条件必须同时满足):
# 条件1:reconciliation_date(收款日期)< due_date(应收日期),即提前收款
# 条件2:不在同一年同一月(跨月才算预收)
# 条件3:reconciliation_date < cutoff_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
# 条件1:提前收款
if not (pay_date < due):
continue
# 条件2:跨月(不在同一年同一月)
if pay_date.year == due.year and pay_date.month == due.month:
continue
# 条件3:收款在查询日之前
if not (pay_date < cutoff_date):
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, 'earliest_pay_date': None}
rec_map[rid]['amount'] += line.amount or 0.0
# 收款日期:取 reconciliation_date
pay_date = line.reconciliation_date
if pay_date:
pay_date_val = pay_date.date() if hasattr(pay_date, 'date') else pay_date
# 记录最早的收款日期
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):
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)
# 收款日期:取最早收款日期
earliest_pay_date = data.get('earliest_pay_date')
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,
"收款日期": earliest_pay_date.strftime('%Y-%m-%d') if earliest_pay_date 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}