464 lines
21 KiB
Python
464 lines
21 KiB
Python
# -*- 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',
|
||
}
|
||
|
||
|
||
|
||
|