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

169 lines
7.3 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
import calendar
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
class DepositTotalReport(models.Model):
_inherit = 'property.accounts.receivable'
@api.model
def get_deposit_total_report(self, params):
"""
累计押金报表:
- 数据源:property.accounts.receivable
- 管理费用类型为"押金"
- 非作废记录
- 收款日期(payout_date)≤ 查询月末(累计逻辑,含提前预收)
- 金额:original_amount - discount_amount
- 状态取数逻辑:
- 已收(paid):payout_date 存在且 ≤ 查询月末(累计逻辑,含提前预收)
- 已退(yes):退款日期(has_date) > 查询月末 → 查询时还没退,应包含
退款日期(has_date) <= 查询月末 → 已退,不包含
(已退状态不受 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)
# 查询已退款的押金记录(从property.bills.list中获取)
# 用于:contract 下所有 receivable 的 has_date 可能为空,通过 bills.list 补全
refund_bills = self.env['property.bills.list'].sudo().search([
('management_type_id.name', '=', '押金'),
('actual_date', '!=', False),
])
# 按 contract_id 分组取最早退款日期
refund_date_by_contract = {}
for bill in refund_bills:
if bill.contract_id and bill.actual_date:
cid = bill.contract_id.id
existing = refund_date_by_contract.get(cid)
if not existing or bill.actual_date < existing:
refund_date_by_contract[cid] = bill.actual_date
domain = [
('management_type_id.name', '=', '押金'),
('is_invalid', '!=', True),
('deposit_state', 'in', ['paid', 'yes', 'no']),
]
keyword = params.get("keyword")
if keyword:
domain += ['|', '|',
('contract_id.contract_code', 'ilike', keyword),
('property_name', 'ilike', keyword),
('lessee_id.name', 'ilike', keyword),
]
condition = params.get('condition')
partner_id = _id_list(params.get('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(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', 'in', company_id))
if property_id:
domain.append(('property_ids', 'in', [property_id]))
if lessee_id:
domain.append(('lessee_id', '=', lessee_id))
if contract_id:
domain.append(('contract_id', '=', contract_id))
if management_type_id:
domain.append(('management_type_id', '=', management_type_id))
lines = self.sudo().search(domain, order="contract_id asc")
# 内存过滤:按状态分别处理
# - 已收(paid):收款日期必须存在且 <= 查询月末(累计逻辑,含提前预收)
# - 待退(no):收款日期存在且 <= 查询月末(已收未退,应包含在累计押金中)
# - 已退(yes):退款日期 > 查询月末(查询时还没退,不受 payout_date 约束)
lines = lines.filtered(
lambda l: (
l.deposit_state == 'paid' and l.payout_date and l.payout_date <= month_last
) or (
l.deposit_state == 'no' and l.payout_date and l.payout_date <= month_last
) or (
l.deposit_state == 'yes' and (
(l.has_date and l.has_date > month_last) or
(not l.has_date and l.contract_id and
refund_date_by_contract.get(l.contract_id.id) and
refund_date_by_contract[l.contract_id.id] > month_last)
)
)
)
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):
amount = round((rec.original_amount or 0.0) - (rec.discount_amount or 0.0), 2)
total_amount += amount
contract = rec.contract_id
# 物业穿透
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 ''
# 状态映射:deposit_state 值 → 中文显示名
state_display = {
'unpaid': '未收',
'no_paid': '收部分',
'paid': '已收',
'no': '待退',
'yes': '已退',
}
rows.append({
"序号": idx,
"状态": state_display.get(rec.deposit_state, rec.deposit_state or ""),
"合同编号": {
'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 "",
"金额": amount,
"应收金额": round((rec.original_amount or 0.0) - (rec.discount_amount or 0.0), 2),
"所属公司": rec.company_id.name if rec.company_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}