Files
gzth/yuthon_property_approval/models/property_accounts_receivable_summary.py
T
2026-09-28 18:52:35 +08:00

88 lines
4.5 KiB
Python

# -*- coding: utf-8 -*-
from odoo import fields, models, tools
class PropertyAccountsReceivableSummary(models.Model):
"""应收账单未收汇总(按合同)—— 一行一个合同,聚合各应收类型未收金额。
采用 PostgreSQL 视图 + _auto=False 模型:视图实时按合同聚合,
无需定时任务同步,web 列表 / searchpanel / read_group 全部走标准 ORM。
"""
_name = 'property.accounts.receivable.summary'
_description = '应收账单未收汇总(按合同)'
_auto = False
_order = 'id desc'
contract_id = fields.Many2one('property.lease.contract', string='合同')
property_name = fields.Text(string='物业地址')
admin_id = fields.Many2one('res.users', string='片区管理员')
rent_unpaid = fields.Float(string='租金')
management_unpaid = fields.Float(string='管理费')
water_unpaid = fields.Float(string='水费')
electricity_unpaid = fields.Float(string='电费')
penalty_unpaid = fields.Float(string='违约金')
deposit_unpaid = fields.Float(string='押金')
lightbox_unpaid = fields.Float(string='灯箱')
other_unpaid = fields.Float(string='其他')
# 7 个单独展示的类型(灯箱单独一列);其余类型(广告灯箱费/有偿服务收入/停车费/管子间/
# 更名费/维修费/其他费用/评估费/保险费,以及以后新增的类型)全部归入「其他」
_SUMMARY_TYPES = ['租金', '管理费', '水费', '电费', '违约金', '押金', '灯箱']
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
main_cols = ""
for fname, tname in [
('rent_unpaid', '租金'), ('management_unpaid', '管理费'), ('water_unpaid', '水费'),
('electricity_unpaid', '电费'), ('penalty_unpaid', '违约金'), ('deposit_unpaid', '押金'),
('lightbox_unpaid', '灯箱'),
]:
main_cols += "\n COALESCE(SUM(CASE WHEN t.name = '%s' THEN r.submitted ELSE 0.0 END), 0.0) AS %s," % (tname, fname)
excluded = ", ".join("'%s'" % t for t in self._SUMMARY_TYPES)
self.env.cr.execute("""
CREATE OR REPLACE VIEW %(table)s AS (
SELECT
r.contract_id AS id,
r.contract_id AS contract_id,
c.use_address AS property_name,
c.admin_id AS admin_id,%(main_cols)s
COALESCE(SUM(CASE WHEN t.name NOT IN (%(excluded)s) THEN r.submitted ELSE 0.0 END), 0.0) AS other_unpaid
FROM property_accounts_receivable r
LEFT JOIN property_lease_contract c ON c.id = r.contract_id
LEFT JOIN yuthon_management_type t ON t.id = r.management_type_id
WHERE r.contract_id IS NOT NULL
AND r.deposit_state IN ('unpaid', 'no_paid')
AND COALESCE(r.is_reconciliation, false) = false
AND COALESCE(r.is_invalid, false) = false
GROUP BY r.contract_id, c.use_address, c.admin_id
HAVING COALESCE(SUM(r.submitted), 0) > 0
)
""" % {'table': self._table, 'main_cols': main_cols, 'excluded': excluded})
def action_open_unpaid_detail(self):
"""未收明细:打开该合同下 未收/收部分 且 未对账 且 未作废 的应收账单,
复用 view_property_accounts_receivable_tree,不做分组,按应收类型倒序"""
self.ensure_one()
tree_view = self.env.ref('yuthon_property.view_property_accounts_receivable_tree')
search_view = self.env.ref('yuthon_property_approval.view_property_accounts_receivable_search')
return {
'name': '未收明细 - %s' % (self.contract_id.display_name or ''),
'type': 'ir.actions.act_window',
'res_model': 'property.accounts.receivable',
'view_mode': 'tree',
'view_id': tree_view.id,
# 前端要求 search_view_id 为 [id, name] 列表格式,纯 int 会被忽略而回落到默认 search 视图
'search_view_id': [search_view.id, search_view.name],
'domain': [
('contract_id', '=', self.contract_id.id),
('deposit_state', 'in', ['unpaid', 'no_paid']),
('is_reconciliation', '=', False),
('is_invalid', '=', False),
],
'context': {
'order': 'management_type_id desc',
},
'target': 'current',
}