Files
gzth/yuthon_property/models/property_bank_statements.py
T
2026-09-02 12:01:32 +08:00

464 lines
21 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.
# -*- coding: utf-8 -*-
from dateutil.relativedelta import relativedelta
from odoo import models, fields, api, exceptions, _
from odoo.exceptions import ValidationError, UserError
from datetime import datetime, timedelta, date
import logging
from odoo.osv import expression
_logger = logging.getLogger(__name__)
class PropertyBankStatements(models.Model):
_name = 'property.bank.statements'
_description = '银行流水'
def unlink(self):
for rec in self:
if rec.bank_state in ('reconciled', 'registered'):
raise UserError(_('已对账或挂账的银行流水记录不允许删除!'))
return super(PropertyBankStatements, self).unlink()
name = fields.Char('对方账号')
serial_number = fields.Char('交易流水号')
sequence_number = fields.Char('顺序号')
your_own_account = fields.Char('本方账号')
your_own_unit = fields.Char('账号单位')
name_account = fields.Char('对方户名')
bank_branch = fields.Char('对方开户机构')
bank_number = fields.Char('对方行号')
bank_name = fields.Char('对方行名')
account_code = fields.Char('账户明细编号')
journal_date = fields.Date('记账日期')
purpose = fields.Char('用途')
message = fields.Char('回单个性化信息')
number = fields.Char('凭证号')
extract = fields.Char('摘要')
start_time = fields.Datetime('交易时间')
contract_account = fields.Char(string="合同累计应收")
contract_penalty = fields.Char(string="合同累计违约金")
original_amount = fields.Float(string='交易金额')
lending_direction = fields.Selection([('borrow', '借'), ('loan', '贷')], string='借贷方向')
yes_amount = fields.Float(string='已对')
no_amount = fields.Float(string='未对', compute='_compute_no_amount')
reconciliation_time = fields.Datetime('对账时间')
voucher_type = fields.Selection([('electronic_transfer', '电子转账凭证'), ('telegraphic_transfer', '电汇凭证')], string='凭证种类')
remake = fields.Text('备注')
marking = fields.Text(string='标注')
registered = fields.Float('挂账')
is_reconciliation = fields.Boolean('是否对账')
workflow_record_id = fields.Many2one('soong.workflow.record', string="流程")
company_select = fields.Selection([('jhtz', '天河投资'), ('thwg', '天河物管'), ('thcj', '天河城建')])
property_ids = fields.Many2many('yuthon.property', string='物业地址')
bank_state = fields.Selection([('not_reconciled', '未对账'), ('reconciled', '已对账'), ('registered', '挂账'), ('other', '其他')], string='状态', default='not_reconciled')
company_id = fields.Many2one('res.company', string='公司')
change_amount = fields.Float(string='修改金额')
receivable_modify_ids = fields.One2many('receivable.modify.line', 'bank_id', string='修改应收单')
def open_record(self):
return {
"type": "ir.actions.act_window",
"res_model": self.workflow_record_id.res_model,
"res_id": self.workflow_record_id.res_id,
"name": self.workflow_record_id.name,
"view_mode": "form",
}
def update_company_id(self):
records = self.env['property.bank.statements'].search([])
for record in records:
if record.your_own_unit in ['天河投资', '天投城建']:
record.company_id = self.env['res.company'].search([('name', '=', '集团本部')], limit=1)
elif record.your_own_unit in ['天河物管']:
record.company_id = self.env['res.company'].browse(3)
elif record.your_own_unit in ['天河粮储']:
record.company_id = self.env['res.company'].search([('name', '=', '天河粮储')], limit=1)
def update_your_own_unit(self):
selected_records = self.env["property.bank.statements"].browse(
self._context.get('active_ids', self._context.get('active_id'))
)
for record in selected_records:
company_config = self.env['yuthon.property.company'].sudo().search(
[('rent_account', '=', record.your_own_account)], limit=1
)
if company_config and company_config.referred:
record.your_own_unit = company_config.referred
def update_record(self):
selected_records = self.env["property.bank.statements"].browse(
self._context.get('active_ids', self._context.get('active_id'))
)
duplicate_groups = {}
for record in selected_records:
time_key = record.start_time.strftime('%Y-%m-%d %H:%M:%S') if record.start_time else 'no_time'
amount_key = round(record.original_amount, 2)
group_key = (time_key, amount_key)
if group_key not in duplicate_groups:
duplicate_groups[group_key] = self.env["property.bank.statements"]
duplicate_groups[group_key] |= record
deleted_count = 0
kept_records = self.env["property.bank.statements"]
for group_key, group_records in duplicate_groups.items():
if len(group_records) == 1:
kept_records |= group_records
continue
reconciled_records = group_records.filtered(lambda r: r.bank_state == 'reconciled')
if reconciled_records:
keep_record = reconciled_records[0]
kept_records |= keep_record
delete_records = group_records - keep_record
else:
keep_record = group_records[0]
kept_records |= keep_record
delete_records = group_records
if delete_records:
delete_records.unlink()
deleted_count += len(delete_records)
@api.onchange('extract')
def _onchange_extract_set_bank_state(self):
for rec in self:
if rec.extract and '结息' in rec.extract:
rec.bank_state = 'other'
@api.onchange('remake')
def _onchange_remake_set_bank_state(self):
for rec in self:
if rec.remake and '党费' in rec.remake:
rec.bank_state = 'other'
@api.depends('yes_amount', 'original_amount')
def _compute_no_amount(self):
for rec in self:
rec.no_amount = max((rec.original_amount - rec.yes_amount), 0)
def update_lending_direction(self):
bank_ids = self.env["property.bank.statements"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for bank_id in bank_ids:
change_amount = 0
for receivable_modify_id in bank_id.receivable_modify_ids:
change_amount += receivable_modify_id.amount
bank_id.change_amount = change_amount
bank_id.yes_amount = change_amount
def other(self):
bank_ids = self.env["property.bank.statements"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for rec in bank_ids:
rec.bank_state = 'other'
receivable_ids = self.env['property.accounts.receivable'].search([('lessee_id.name', '=', rec.name_account)])
property_list = []
for property in rec.property_ids:
property_list.append(property.name)
property_names = ",".join(property_list)
for receivable in receivable_ids:
receivable_log = f'当前登录人{self.env.user.name}于{fields.Datetime.now()}将物业为 {property_names} 的银行对账单状态修改为其他'
original_log = receivable.receivable_log or ''
if original_log:
new_log = f"{original_log}\n{receivable_log}"
else:
new_log = receivable_log
receivable.write({'receivable_log': new_log})
def handle_contract_penalty(self):
'''
处理合同违约金
:param record: property.bank.statements 记录
:return:
'''
record = self.env["property.bank.statements"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for contract_id in record.contract_ids:
receivable_id = self.env['property.accounts.receivable'].search([('contract_id', '=', contract_id.id)])
if receivable_id:
dt = datetime.strptime(str(record.start_time), '%Y-%m-%d %H:%M:%S').date() # 流水交易时间
due = datetime.strptime(str(receivable_id.due_date), '%Y-%m-%d').date() + relativedelta(months=1, day=1) # 应收日期
# 判断是否违约
if receivable_id.submitted and not receivable_id.is_reconciliation or not receivable_id.is_suspend:
original_amount = (due - dt).days * receivable_id.original_amount * float(receivable_id.contract_id.percentage_liquidated_damages.name)
self.env['property.penalty.list'].create({
'contract_id': contract_id.id,
'property_id': contract_id.lease_ids[0].address_id.id,
'lessee_id': contract_id.lessee_id.id,
'original_amount': original_amount
})
receivable_id.write({'penalty':original_amount})
receivable_id.is_reconciliation = True
def disposable(self):
bank_ids = self.env["property.bank.statements"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for rec in bank_ids:
penalty_type = self.env['yuthon.management.type'].search(
[('name', '=', '违约金')],
limit=1)
bank_lines = self.env['property.bank.line'].search([
('bank', '=', rec.name),
('tenant_id.name', '!=', False),
])
if bank_lines:
tenant_id = bank_lines[0].tenant_id.id
start_date = (rec.start_time + timedelta(hours=8)).date()
accounts = self.env['property.accounts.receivable'].search([
('payout_date', '=', start_date),
('lessee_id', '=', tenant_id),
('contract_id', 'in', rec.contract_ids.ids),
('management_type_id', '!=', penalty_type.id),
])
if accounts:
rec.reconciliation_time = accounts[0].reconciliation_time
class ReceivableModifyLine(models.Model):
_name = 'receivable.modify.line'
_description = '修改应收账单明细'
bank_id = fields.Many2one('property.bank.statements', string='关联银行对账单')
# company_id = fields.Many2one('res.company', related='bank_id.company_id', string='公司', store=True)
original_amount = fields.Float(related='bank_id.original_amount', string='流水金额', store=False)
payout_date = fields.Date(related='receivable_id.payout_date', string='最终收款日期')
no_amount = fields.Float(compute="_compute_no_amount", string='未对')
is_reconciliation = fields.Boolean(related='receivable_id.is_reconciliation', string='已对')
receivable_id = fields.Many2one('property.accounts.receivable', string='应收账单id')
contract_id = fields.Many2one('property.lease.contract', string='关联租赁合同', compute='_compute_contract_id')
management_type_id = fields.Many2one('yuthon.management.type', related='receivable_id.management_type_id', string='应收类型', store=True)
due_date = fields.Date(related='receivable_id.due_date', string='应收开始', store=True)
due_date_end = fields.Date(related='receivable_id.due_date_end', string='应收结束', store=True)
property_name = fields.Text(string='物业名称', compute='_compute_property_name', store=False, search='_search_property_name')
delivered1 = fields.Float(string='当期金额', compute='_compute_delivered1')
receivable_number = fields.Integer(
string='应收账款ID',
related='receivable_id.id',
readonly=True,
)
@api.depends('receivable_id', 'receivable_id.original_amount',
'receivable_id.management_type_id', 'amount', 'reconciliation_date')
def _compute_delivered1(self):
"""按 receivable_id 分组,收款日期升序,逐行累减计算当期金额。
仅租金类型生效,非租金取 original_amount。
"""
for rec in self:
if not rec.receivable_id:
rec.delivered1 = 0
continue
if rec.receivable_id.management_type_id.name not in ['租金', '违约金']:
rec.delivered1 = rec.receivable_id.original_amount
continue
all_lines = self.search([('receivable_id', '=', rec.receivable_id.id)])
all_lines |= rec # 确保当前记录包含在内(处理新建未保存的记录)
sorted_lines = all_lines.sorted(
key=lambda l: (
0 if l.reconciliation_date else 1,
l.reconciliation_date or date.max,
l.id if isinstance(l.id, int) else 0,
)
)
cumsum = 0.0
for line in sorted_lines:
if line == rec:
remaining = rec.receivable_id.original_amount - cumsum
rec.delivered1 = rec.receivable_id.original_amount if remaining <= 0 else remaining
break
cumsum += line.amount
else:
# 兜底:确保 delivered1 总是被赋值
rec.delivered1 = 0
@api.onchange('reconciliation_date')
def _onchange_reconciliation_date(self):
"""修改核销日期时,根据分组情况同步更新应收款的付款日期"""
for record in self:
if not record.receivable_id or not record.reconciliation_date:
continue
receivable = record.receivable_id
# 查询同一 receivable_id 下的其他修改明细行(排除当前记录)
domain = [('receivable_id', '=', receivable.id), ('management_type_id', '=', record.management_type_id.id), ('id', '!=', record.id.origin)]
existing_lines = self.env['receivable.modify.line'].search(domain)
# 当前组内记录总数(含当前记录)
group_count = len(existing_lines) + 1
if group_count == 1:
# 一组只有一条记录:直接同步 payout_date
receivable.payout_date = record.reconciliation_date
elif group_count == 2:
# 一组有两条记录:仅当修改前的 reconciliation_date
# 与 receivable 的 payout_date 相同时才同步
old_reconciliation_date = False
if record.id.origin:
old_reconciliation_date = self.env['receivable.modify.line'].browse(
record.id.origin
).reconciliation_date
if old_reconciliation_date == receivable.payout_date:
receivable.payout_date = record.reconciliation_date
remake = fields.Text(related='receivable_id.remark', string='备注', store=False)
# lessee_id = fields.Many2one(
# "property.tenant.information",
# string="承租方",
# compute='_compute_lessee_id_company',
# store=False,
# search='_search_lessee_id',
# )
@api.depends('receivable_id')
def _compute_contract_id(self):
for rec in self:
rec.contract_id = False
if rec.receivable_id:
rec.contract_id = rec.receivable_id.contract_id
@api.depends('receivable_id')
def _compute_lessee_id_company(self):
for rec in self:
lessee = False
if rec.receivable_id:
lessee = getattr(rec.receivable_id, 'lessee_id', False)
if not lessee:
contract = getattr(rec.receivable_id, 'contract_id', False)
if contract:
lessee = getattr(contract, 'lessee_id', False)
rec.lessee_id = lessee or False
is_write = fields.Boolean()
is_deposit = fields.Boolean(string='是否押金', compute='_compute_is_deposit', store=True)
lessee_id = fields.Many2one('property.tenant.information', string='承租方', compute='_compute_related_fields',
store=False, index=True, search='_search_lessee_id')
company_id = fields.Many2one('yuthon.property.company', string='公司', compute='_compute_related_fields',
store=False, index=True)
precinct_id = fields.Many2one('yuthon.precinct.message', string='管理员', compute='_compute_related_fields',
store=False, index=True)
@api.depends('receivable_id')
def _compute_related_fields(self):
for rec in self:
rec.lessee_id = False
rec.company_id = False
rec.precinct_id = False
if rec.receivable_id:
rec.lessee_id = getattr(rec.receivable_id, 'lessee_id', False)
rec.company_id = getattr(rec.receivable_id, 'company_id', False)
rec.precinct_id = getattr(rec.receivable_id, 'precinct_id', False)
@api.depends('delivered1', 'amount')
def _compute_no_amount(self):
for rec in self:
rec.no_amount = rec.delivered1 - rec.amount
@api.depends('receivable_id')
def _compute_property_name(self):
for rec in self:
if rec.receivable_id:
rec.property_name = rec.receivable_id.property_name or ''
else:
rec.property_name = ''
@api.depends('receivable_id.management_type_id')
def _compute_is_deposit(self):
for rec in self:
rec.is_deposit = False
if rec.receivable_id and rec.receivable_id.management_type_id:
rec.is_deposit = rec.receivable_id.management_type_id.name == '押金'
def _search_property_name(self, operator, value):
contract_ids = self.env['property.lease.contract'].search([('property_name', operator, value)]).ids
receivable_ids = self.env['property.accounts.receivable'].search([('contract_id', 'in', contract_ids)]).ids
return [('receivable_id', 'in', receivable_ids)]
def _search_lessee_id(self, operator, value):
receivable_model = self.env['property.accounts.receivable']
if 'lessee_id' in receivable_model._fields:
receivable_ids = receivable_model.search([('lessee_id', operator, value)]).ids
return [('receivable_id', 'in', receivable_ids)]
return [('id', '=', 0)]
reconciliation_date = fields.Date(string='收款日期')
operation_date = fields.Date(string='操作日期')
is_penalty = fields.Boolean(string='是否为违约金')
not_collected = fields.Float(related='receivable_id.submitted', string='未收', store=False)
amount = fields.Float(string='已对金额')
has_date = fields.Date(related='receivable_id.has_date', string='退款日期', store=False)
remake1 = fields.Char(string='核销备注')
penalty_pay_line_ids = fields.Many2many(
'penalty.pay.line',
string='押金收款明细',
compute='_compute_penalty_pay_lines',
store=False,
)
@api.depends('receivable_id')
def _compute_penalty_pay_lines(self):
for rec in self:
if rec.receivable_id:
wizards = self.env['penalty.line.wizard'].search(
[('receivable_id', '=', rec.receivable_id.id)]
)
rec.penalty_pay_line_ids = wizards.mapped('pay_line_ids')
else:
rec.penalty_pay_line_ids = self.env['penalty.pay.line']
def open_penalty_pay_lines(self):
"""打开押金收款明细弹窗"""
self.ensure_one()
wizards = self.env['penalty.line.wizard'].search(
[('receivable_id', '=', self.receivable_id.id)]
)
pay_line_ids = wizards.mapped('pay_line_ids').ids
return {
'type': 'ir.actions.act_window',
'name': '押金收款明细',
'res_model': 'penalty.pay.line',
'view_mode': 'tree',
'domain': [('id', 'in', pay_line_ids)],
'target': 'new',
}
def open_receivable(self):
"""打开应收账单"""
self.ensure_one()
if self.receivable_id:
return {
'type': 'ir.actions.act_window',
'name': '应收账单',
'res_model': 'property.accounts.receivable',
'res_id': self.receivable_id.id,
'view_mode': 'form',
'target': 'current',
}
def open_bank_statement(self):
"""打开银行流水"""
self.ensure_one()
if self.bank_id:
return {
'type': 'ir.actions.act_window',
'name': '银行流水',
'res_model': 'property.bank.statements',
'res_id': self.bank_id.id,
'view_mode': 'form',
'target': 'current',
}