Files
gzth/yuthon_property_approval/wizard/property_reconciliation_wizard.py
2026-09-24 19:38:13 +08:00

664 lines
35 KiB
Python
Raw Permalink 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.
from odoo import models, fields, api, _, SUPERUSER_ID
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError, ValidationError
import re
import logging
_logger = logging.getLogger(__name__)
class PropertyReconciliationWizard(models.Model):
_name = 'property.reconciliation.wizard'
_description = '对账弹窗'
receivable_total = fields.Float(string='应收金额', compute="_compute_receivable_total", store=True)
bank_branch = fields.Char('银行名称')
warning_msg = fields.Char(string='警告')
bank_id = fields.Many2one('property.bank.statements', string='银行流水')
lessee_ids = fields.Many2many('property.tenant.information', string='承租方')
receivables_type_ids = fields.Many2many('yuthon.management.type', string="应收类型")
name_account = fields.Char( string='对方户名')
account = fields.Char(string='账号')
contract_ids = fields.Many2many('property.lease.contract', string='合同')
amount_been_reconciled = fields.Float(string='已对金额', store=True)
amount_water_been = fields.Float(string='已对流水金额', store=True)
amount_water = fields.Float(string='未对流水金额', store=True)
amount_reconciled = fields.Float(string='未核销', compute='_compute_receivable_total')
actual_total = fields.Float(string='实收金额')
actual_date = fields.Date(string='实收日期')
initial_total = fields.Float(string='初始金额', default=0.0)
rent = fields.Float(string='未收总租金', compute="_compute_rent_total")
penalty = fields.Float(string='未收总违约金', compute="_compute_penalty_total")
remake = fields.Text(string='摘要')
reconciliation_line_ids = fields.One2many('property.reconciliation.line', 'reconciliation_id', string='对账明细')
@api.onchange('bank_id')
def _onchange_bank_id(self):
for rec in self:
rec.lessee_ids = rec.bank_id.lessee_ids.ids
rec.name_account = rec.bank_id.name_account
rec.remake = rec.bank_id.remake
rec.amount_water_been = rec.bank_id.yes_amount
rec.amount_water = rec.bank_id.no_amount
@api.depends('reconciliation_line_ids')
def _compute_receivable_total(self):
for rec in self:
rec.receivable_total = sum(line.rent for line in rec.reconciliation_line_ids)
rec.amount_reconciled = max(0 , rec.bank_id.no_amount - sum(line.amount for line in self.reconciliation_line_ids))
@api.depends('reconciliation_line_ids.collection_type', 'reconciliation_line_ids.rent')
def _compute_rent_total(self):
"""计算所有类型为租金的明细行的未收总额"""
for wizard in self:
# 筛选出收款类型为租金的明细行,并累加其未收金额
rent_total = sum(
line.rent or 0.0
for line in wizard.reconciliation_line_ids
if line.collection_type == 'rent'
)
wizard.rent = rent_total
@api.depends('reconciliation_line_ids.collection_type', 'reconciliation_line_ids.rent')
def _compute_penalty_total(self):
"""计算所有类型为违约金的明细行的未收总额"""
for wizard in self:
penalty_total = sum(
line.rent or 0.0
for line in wizard.reconciliation_line_ids
if line.collection_type == 'penalty'
)
wizard.penalty = penalty_total
@api.onchange('contract_ids')
def onchange_contract_ids(self):
for rec in self:
bank_contract_ids = rec.bank_id.contract_ids.ids if rec.bank_id else []
current_contract_ids = rec.contract_ids.ids
to_delete = []
for line in rec.reconciliation_line_ids:
if hasattr(line.contract_id.id, 'origin'):
line_contract_id = line.contract_id.id.origin
else:
line_contract_id = line.contract_id.id
if line_contract_id not in current_contract_ids:
to_delete.append((2, line.id, 0))
rec.reconciliation_line_ids = to_delete
new_contracts = self.env['property.lease.contract'].browse(current_contract_ids)
new_vals, _,= rec.bank_id._get_reconciliation_vals(rec.bank_id, new_contracts, is_equity=True)
rec.reconciliation_line_ids = [(5, 0, 0)]
rec.reconciliation_line_ids = new_vals
rec.compute_reconciliation_line_ids()
@api.onchange('receivables_type_ids')
def onchange_receivables_type_ids(self):
for rec in self:
selected_type_names = rec.receivables_type_ids.mapped('name')
if not selected_type_names:
rec.reconciliation_line_ids = [(5, 0, 0)]
continue
# 收款类型与名称的映射(包含违约金,但后续会排除)
type_name_map = {
'deposit': '押金',
'rent': '租金',
'water_fee': '水费',
'lightbox': '灯箱',
'guanzijian': '管子间',
'rename_fee': '更名费',
'electricity_fee': '电费',
'penalty': '违约金',
'management_fee': '管理费',
'repair_fee': '维修费',
'fee': '人工费/日光灯款/代垫用料费',
}
allowed_by_selection = [
coll_type for coll_type, name in type_name_map.items()
if name in selected_type_names
]
to_delete = []
for line in rec.reconciliation_line_ids:
if line.collection_type not in allowed_by_selection:
to_delete.append((2, line.id, 0))
if to_delete:
rec.reconciliation_line_ids = to_delete
@api.onchange('actual_total')
def compute_reconciliation_line_ids(self):
type_name_map = {
'deposit': '押金',
'rent': '租金',
'water_fee': '水费',
'lightbox': '灯箱',
'guanzijian': '管子间',
'electricity_fee': '电费',
'rename_fee': '更名费',
'penalty': '违约金',
'management_fee': '管理费',
'repair_fee': '维修费',
'fee': '人工费/日光灯款/代垫用料费',
}
new_lines = []
# 违约金生成
for contract_id in self.contract_ids:
contract_penalties = []
if not contract_id.lease_ids:
continue
receivable_penalties = self.env['property.accounts.receivable'].search([
('contract_id', '=', contract_id.id.origin),
('is_reconciliation', '=', False),
('is_invalid', '=', False),
('management_type_id.name', '=', '违约金')
])
for penalty in receivable_penalties:
source_receivable = penalty.source_receivable_id or penalty._resolve_source_receivable()
contract_penalties.append({
'contract_id': penalty.contract_id.id,
'rent_month': 0,
'due_date': penalty.due_date,
'rent': penalty.submitted,
'amount': 0,
'collection_type': 'penalty',
'remake': penalty.remark,
'source_receivable_id': source_receivable.id if source_receivable else False,
})
contract_receivables_ids = self.env['property.accounts.receivable'].search([
('contract_id', '=', contract_id.id.origin),
('is_reconciliation', '=', False),
('is_invalid', '=', False),
('management_type_id.name', 'not in', ['违约金', '押金']),
])
for contract_receivables in contract_receivables_ids:
if contract_receivables:
if not contract_receivables.is_liquidated_damages:
continue
# 计算应收日期的下月1号
if not contract_receivables.due_date:
raise UserError(f'合同{contract_id.name}类型为{contract_receivables.management_type_id.name}的应收日期为空,请先填写应收日期')
next_month_first = (fields.Date.from_string(contract_receivables.due_date) +
relativedelta(months=1)).replace(day=1)
# 检查是否产生违约
if fields.Date.from_string(self.actual_date) >= next_month_first:
# 计算违约天数和违约金
late_days = (fields.Date.from_string(self.actual_date) - next_month_first).days + 1
# 找到符合月份的租金
penalty_amount = late_days * round(
contract_receivables.submitted * float(contract_receivables.penalty_ratio_value), 2
)
start_date_str = fields.Date.to_string(next_month_first)
end_date_str = self.actual_date
# 处理新产生的违约金
if penalty_amount > 0:
contract_penalties.append({
'contract_id': contract_id.id,
'rent_month': 0,
'due_date': self.actual_date,
'rent': penalty_amount,
'amount': 0,
'collection_type': 'penalty',
'remake': f'违约金({start_date_str}至{end_date_str},共{late_days}天)',
'source_receivable_id': contract_receivables.id,
})
# 查找应收是否有未交全的租金
unpaid_rent_recs = self.env['property.accounts.receivable'].search([
('management_type_id.name', 'not in', ['违约金', '押金']),
('contract_id', '=', contract_id.id.origin),
('is_invalid', '=', False),
('submitted', '!=', 0),
('payout_date', '!=', None),
])
if unpaid_rent_recs:
for rent in unpaid_rent_recs:
if not rent.is_liquidated_damages: # 跳过不生成违约金的租金
continue
if rent.payout_date <= rent.due_date: # 跳过预收的租金
continue
first_day = (rent.payout_date + timedelta(days=1))
last_month_rent = rent.submitted
penalty_days = self.actual_date - first_day
penalty_amount = (penalty_days.days + 1) * round(
last_month_rent * float(rent.penalty_ratio_value), 2
) if contract_id else 0.0
if penalty_amount > 0:
new_lines.append((0, 0, {
'contract_id': contract_id.id if contract_id else None,
'rent_month': 0,
'due_date': fields.Date.to_string(first_day),
'rent': penalty_amount,
'amount': 0,
'collection_type': 'penalty',
'remake': f'违约金({fields.Date.to_string(first_day)}至{fields.Date.to_string(self.actual_date)},共{penalty_days.days + 1}天)',
'source_receivable_id': rent.id,
}))
# 3. 添加到总列表
for vals in contract_penalties:
new_lines.append((0, 0, vals))
if new_lines:
keep_lines = [
(4, line.id) for line in self.reconciliation_line_ids
if line.collection_type != 'penalty'
]
self.reconciliation_line_ids = keep_lines + new_lines
all_penalties = all(line.collection_type == 'penalty' for line in self.reconciliation_line_ids)
if all_penalties and self.reconciliation_line_ids:
total = sum(line.rent for line in self.reconciliation_line_ids)
self.receivable_total = total
self.create_reconciliation_vals()
line_types = []
for line in self.reconciliation_line_ids:
if line.collection_type not in line_types:
line_types.append(line.collection_type)
type_names = [type_name_map.get(t) for t in line_types if type_name_map.get(t)]
receivable_types = self.env['yuthon.management.type'].search([
('name', 'in', type_names)
])
self.receivables_type_ids = receivable_types
def create_reconciliation_vals(self):
"""
按已生成的明细行 分配金额(优先处理押金)
:return:
"""
remaining_amount = self.bank_id.no_amount
penalty_lines = [line for line in self.reconciliation_line_ids if line.collection_type == "penalty"] # 违约金行
rent_lines = [line for line in self.reconciliation_line_ids if line.collection_type == "rent"] # 租金行
type_priority = {
'deposit': 1, # 押金优先
'rent': 2, # 其次租金
'other': 3, # 其他类型
'penalty': 4
}
all_lines = sorted(
self.reconciliation_line_ids,
key=lambda line: (
line.due_date.strftime('%Y-%m') if line.due_date else '',
type_priority.get(line.collection_type, 3)
)
)
for line in all_lines:
if remaining_amount <= 0:
break
if line.collection_type == "penalty":
continue
# 分配当前行金额
line.amount = 0
need_amount = round(line.rent or 0, 2)
assign_amount = min(need_amount, remaining_amount)
line.amount = assign_amount
remaining_amount -= assign_amount
if assign_amount > 0 and round(line.amount or 0, 2)>0:
line.onchange_remake()
if remaining_amount > 0:
penalty_lines = [line for line in all_lines if line.collection_type == "penalty"]
for line in penalty_lines:
if remaining_amount <= 0:
break
line.amount = 0
need_amount = line.rent or 0
assign_amount = min(need_amount, remaining_amount)
line.amount = assign_amount
remaining_amount -= assign_amount
if assign_amount > 0 and round(line.amount or 0, 2) > 0:
line.onchange_remake()
for line in penalty_lines:
line.color = 'blue'
for line in rent_lines:
line.color = 'success'
def _get_precinct_admin_employees(self, properties):
"""取物业对应的片区管理员(hr.employee):
优先用物业上的「片区管理员」反查员工,取不到时退回物业「管理信息明细」里片区配置的管理员。
只保留配置了企业微信 userid 的员工。
"""
employees = self.env['hr.employee'].sudo()
users = properties.mapped('admin_id')
if users:
employees = employees.search([('user_id', 'in', users.ids)])
if not employees:
employees = properties.mapped('manage_information_ids.precinct_id.employee_id')
return employees.filtered(lambda emp: emp.wecom_userid)
def _notify_registered(self, reconciled_amount):
"""对账变成「挂账」时,给对应片区管理员发企业微信提醒"""
self.ensure_one()
bank = self.bank_id
contracts = self.contract_ids
properties = contracts.mapped('lease_ids.address_id') or bank.property_ids
employees = self._get_precinct_admin_employees(properties)
if not employees:
_logger.info('挂账提醒:银行流水 %s 未找到可通知的片区管理员(物业 %s)', bank.id, properties.ids)
return
lessee_names = '、'.join(sorted(set(contracts.mapped('lessee_id.name')))) or (bank.name_account or '')
contract_codes = '、'.join(sorted(set(contracts.mapped('contract_code')))) or '-'
description = (
'承租方:%s<br/>'
'合同:%s<br/>'
'交易金额:%.2f<br/>'
'对账金额:%.2f<br/>'
'未对金额:%.2f<br/>'
'请核对'
) % (lessee_names, contract_codes, bank.original_amount or 0.0,
reconciled_amount or 0.0, bank.registered or 0.0)
url = 'https://oa.thtzjt.com/web#id=%s&model=property.bank.statements&view_type=form&cids=1' % bank.id
for employee in employees:
try:
self.sudo().env['wecom.apps'].sync_send_message_textcard(
category='oa', user_id=employee.wecom_userid,
title='银行对账挂账提醒', description=description,
url=url, btntxt='查看对账')
except Exception:
_logger.exception('挂账提醒发送失败:银行流水 %s → 员工 %s', bank.id, employee.name)
def confirm(self):
collection_type_mapping = {
'deposit': '押金',
'rename_fee': '更名费',
'rent': '租金',
'lightbox': '灯箱',
'guanzijian': '管子间',
'water_fee': '水费',
'electricity_fee': '电费',
'penalty': '违约金',
'management_fee': '管理费',
'repair_fee': '维修费',
'other': '其他'
}
for rec in self:
was_registered = rec.bank_id.bank_state == 'registered'
bank_lines = self.env['property.bank.line'].search([
('bank', '=', rec.bank_id.name),
('tenant_id.name', '!=', False)
])
if not bank_lines:
information = self.env['property.tenant.information'].search(
[('name', '=', rec.bank_id.name_account)],
limit=1)
if information:
bank_lines = self.env['property.bank.line'].create({
'tenant_id': information.id,
'bank': rec.bank_id.name,
'user': information.name,
'name': rec.bank_id.bank_branch,
})
remaining_amount = 0
for line in rec.reconciliation_line_ids:
remaining_amount += line.amount
if round(rec.amount_water, 2) > round(remaining_amount, 2):
rec.bank_id.registered = rec.amount_water - remaining_amount
rec.bank_id.bank_state = 'registered'
else:
rec.bank_id.registered = 0
rec.bank_id.bank_state = 'reconciled'
rec.bank_id.reconciliation_time = fields.Datetime.now() - timedelta(hours=8)
yes_amount = 0
for line in rec.reconciliation_line_ids:
# 跳过金额为0的行
if (round(line.amount or 0, 2) == 0 and line.collection_type != 'penalty'):
continue
first_day = line.due_date.replace(day=1)
last_day = (first_day + relativedelta(months=1)) - relativedelta(days=1)
if line.due_date >= self.actual_date and line.collection_type == 'deposit':
domain = [
('management_type_id.name', '=', collection_type_mapping.get(line.collection_type)),
('is_reconciliation', '=', False),
('is_suspend', '=', False),
]
else:
domain = [
('management_type_id.name', '=', collection_type_mapping.get(line.collection_type)),
('is_reconciliation', '=', False),
('is_suspend', '=', False),
('due_date', '>=', first_day),
('due_date', '<=', last_day)
]
if line.contract_id:
domain.append(('contract_id', '=', line.contract_id.id))
receivable_ids = self.env['property.accounts.receivable'].search(domain)
for receivable in receivable_ids:
if round(line.rent or 0, 2) == round(line.amount or 0, 2):
is_reconciliation = True
else:
is_reconciliation = False
if receivable and receivable.remark:
final_remark = f"{receivable.remark}\n{line.remake}"
else:
final_remark = line.remake
if line.collection_type != 'penalty':
receivable.delivered1 = receivable.delivered1 + line.amount
if receivable.original_amount == round(receivable.delivered1, 2) + round(receivable.discount_amount, 2) :
deposit_state = 'paid'
else:
deposit_state = 'no_paid'
receivable.write({
'is_reconciliation': is_reconciliation,
'payout_date': rec.actual_date,
'deposit_state': deposit_state,
'remark': final_remark,
'reconciliation_time': fields.Datetime.now(),
'receivable_log': f'当前登录人{rec.env.user.name}于{fields.Datetime.now()}对户名为{rec.bank_id.name_account}进行对账操作,对账应收金额为{rec.receivable_total},实际金额为{rec.amount_water}'
})
receivable.contract_id.update_contact_state()
self.env['receivable.modify.line'].create({
'bank_id': rec.bank_id.id,
'not_collected': line.rent,
'reconciliation_date': rec.actual_date if line.amount != 0 else None,
'receivable_id': receivable.id,
'is_write': True if line.amount != 0 else False,
'operation_date': fields.Date.today(),
'is_penalty': False,
'remake1': line.remake,
'amount': line.amount,
})
management_type_id = self.env['yuthon.management.type'].search([('name', '=', '违约金')], limit=1)
if line.collection_type == 'penalty' and management_type_id:
penalty_date = line.due_date
first_day_of_last_month = penalty_date.replace(day=1) - relativedelta(months=2)
# 上个月最后一天:当前月第一天减1天
last_day_of_last_month = penalty_date.replace(day=1) - relativedelta(days=1)
is_create = self.env['property.reconciliation.line'].search([
('reconciliation_id', '=', self.id),
('contract_id', '=', line.contract_id.id ),
('due_date', '>=', first_day_of_last_month),
('due_date', '<=', last_day_of_last_month),
('collection_type', '!=', 'penalty'),
('amount', '>', 0)
])
# 用备注的的开始日期和结束日期
date_pattern = r'(\d{4}-\d{2}-\d{2})[至-](\d{4}-\d{2}-\d{2})'
date_match = re.search(date_pattern, line.remake or '')
extract_start_date = date_match.group(1) if date_match else line.due_date
extract_end_date = date_match.group(2) if date_match else None
if extract_start_date and isinstance(extract_start_date, str):
extract_start_date = datetime.strptime(extract_start_date, "%Y-%m-%d").date()
if extract_end_date and isinstance(extract_end_date, str):
extract_end_date = datetime.strptime(extract_end_date, "%Y-%m-%d").date()
check_domain = [
('due_date', '=', line.due_date),
('contract_id', '=', line.contract_id.id),
('submitted', '=', line.rent),
('is_invalid', '=', False),
('management_type_id', '=', management_type_id.id),
]
existing_record = self.env['property.accounts.receivable'].search(check_domain)
source_receivable = line.source_receivable_id
if not source_receivable and existing_record:
source_receivable = existing_record[:1].source_receivable_id or existing_record[:1]._resolve_source_receivable()
is_reconciliation = round(line.amount, 2) == round(line.rent, 2)
if not existing_record and is_create:
receivable_id = self.env['property.accounts.receivable'].create({
'is_reconciliation': is_reconciliation,
'due_date': extract_end_date or line.due_date,
'due_date_end': None,
'penalty': source_receivable.submitted if source_receivable else 0,
'management_type_id': management_type_id.id,
'contract_id': line.contract_id.id,
'delivered1': line.amount,
'payout_date': rec.actual_date if (line.rent == line.amount and line.rent != 0) else None,
'original_amount': line.rent,
'remark': line.remake,
'reconciliation_time': fields.Datetime.now(),
'charging_range': f"{extract_start_date} 到 {extract_end_date}共{(extract_end_date - extract_start_date).days + 1}" if extract_start_date and extract_end_date else False,
'source_receivable_id': source_receivable.id if source_receivable else False,
})
else:
receivable_id = existing_record[:1]
if receivable_id and source_receivable and not receivable_id.source_receivable_id:
receivable_id.write({'source_receivable_id': source_receivable.id})
if receivable_id and line.amount:
if receivable_id.original_amount == round(receivable_id.delivered1, 2) + line.amount + round(receivable_id.discount_amount, 2):
deposit_state = 'paid'
else:
deposit_state = 'no_paid'
deposit_id = self.env['property.accounts.receivable'].search(
[('contract_id', '=', receivable_id.contract_id.id),
('workflow_record_id', '!=', False),
('management_type_id.name', '=', '押金'),
]
)
if deposit_id:
deposit_id.deposit_state = 'yes'
deposit_id.contract_id.contact_state = 'settle'
deposit_id.has_date = fields.Datetime.now()
deposit_id.contract_id.compute_is_has()
receivable_id.write({
'delivered1': receivable_id.delivered1 + line.amount,
'due_date': extract_end_date or line.due_date,
'deposit_state' : deposit_state,
'due_date_end': None,
'is_reconciliation': is_reconciliation,
'payout_date': rec.actual_date if (line.rent == line.amount and line.rent != 0) else None,
'remark': line.remake,
'bank_id': rec.bank_id.id if line.amount != 0 else None,
'reconciliation_time': fields.Datetime.now(),
})
if receivable_id:
self.env['receivable.modify.line'].create({
'bank_id': rec.bank_id.id,
'remake1': line.remake,
'not_collected': line.rent,
'is_write': True if line.amount != 0 else False,
'is_penalty': True,
'reconciliation_date': rec.actual_date if line.amount != 0 else None,
'operation_date': fields.Date.today(),
'receivable_id': receivable_id.id,
'amount': line.amount,
})
# 删除多余的违约金数据
if len(existing_record) > 1:
existing_record[1:].unlink()
rec.amount_been_reconciled += line.amount
yes_amount += line.amount
rec.bank_id.yes_amount = rec.bank_id.yes_amount + yes_amount
rec.bank_id.change_amount = sum(line.amount for line in rec.reconciliation_line_ids)
# 本次对账后状态变为「挂账」→ 给对应片区管理员发提醒(原本已是挂账的不重复发)
if rec.bank_id.bank_state == 'registered' and not was_registered:
rec._notify_registered(remaining_amount)
all_contracts = self.contract_ids
remaining_contract_ids = []
for contract in all_contracts:
unreconciled_receivables = self.env["property.accounts.receivable"].search([
('contract_id', '=', contract.id),
('is_reconciliation', '=', False),
('is_invalid', '=', False),
], limit=1)
if unreconciled_receivables:
remaining_contract_ids.append(contract.id)
class PropertyReconciliationLine(models.Model):
_name = 'property.reconciliation.line'
_description = '对账明细'
_order = 'sequence, due_date'
reconciliation_id = fields.Many2one('property.reconciliation.wizard', string='关联对账单弹窗')
sequence = fields.Integer(string='顺序')
property_name = fields.Text(string='物业', compute="_compute_property_name")
property_ids = fields.Many2many('yuthon.property', string='物业', compute="_compute_property_ids", store=True)
contract_id = fields.Many2one('property.lease.contract', string='合同编号')
rent_month = fields.Float(string='当期金额')
due_date = fields.Date(string='应收日期')
rent = fields.Float(string='未收')
source_receivable_id = fields.Many2one('property.accounts.receivable', string='原始应收')
amount = fields.Float(string='金额', store=True)
collection_type = fields.Selection([('deposit', '押金'), ('rent', '租金'), ('water_fee', '水费'), ('lightbox', '灯箱'), ('guanzijian', '管子间'),
('electricity_fee', '电费'), ('penalty', '违约金'), ('management_fee', '管理费'), ('repair_fee', '维修费'), ('fee', '人工费/日光灯款/代垫用料费'), ('rename_fee', '更名费'), ('service', '有偿服务收入')], string='收款类型', store=True)
remake = fields.Text(string='备注')
color = fields.Selection([('success', '绿'), ('blue', '蓝')], string='颜色')
@api.depends('contract_id')
def _compute_contract_name(self):
for record in self:
if record.contract_id and record.contract_id.version:
record.contract_name = record.contract_id.contract_code + record.contract_id.version
@api.depends('contract_id')
def _compute_property_name(self):
for record in self:
record.property_name = record.contract_id.property_name
@api.depends('contract_id', 'contract_id.lease_ids', 'contract_id.lease_ids.address_id')
def _compute_property_ids(self):
for record in self:
property_ids = self.env['yuthon.property']
if record.contract_id and record.contract_id.lease_ids:
property_ids = record.contract_id.lease_ids.mapped('address_id')
record.property_ids = property_ids
@api.onchange('amount', 'due_date', 'rent', 'collection_type', 'reconciliation_id.actual_date')
def onchange_remake(self):
for line in self:
line.remake = ""
actual_date = fields.Date.to_date(line.reconciliation_id.bank_id.start_time + relativedelta(hours=8))
if round(line.amount or 0, 2) == 0 and line.collection_type != 'penalty':
line.remake = ""
return
if line.amount and line.collection_type != 'penalty' and actual_date and line.due_date:
due_date = line.due_date
actual_year, actual_month = actual_date.year, actual_date.month
due_year, due_month = due_date.year, due_date.month
due_date_str = due_date.strftime('%Y-%m')
actual_date_str = actual_date.strftime('%Y-%m-%d')
rent = round(line.rent or 0, 2)
rent_month = round(line.rent_month or 0, 2)
amount = round(line.amount or 0, 2)
debt_amount = round(rent - amount, 2)
same_month = (due_month == actual_month) and (due_year == actual_year)
no_remark = same_month and rent == rent_month == amount
if no_remark:
line.remake = ""
continue
debt_str = f',欠{debt_amount}' if debt_amount != 0 else ''
if same_month:
# 条件1:rent_month≠rent 且 rent_month≠amount 且 amount≠0 → 补交
if rent_month != rent and rent != amount and amount != 0:
line.remake = f'补交{due_date_str}'
# 条件2:rent == amount → 补齐
elif rent_month != rent and rent == amount and amount != 0:
line.remake = f'补齐{due_date_str}'
elif rent_month == rent and rent > amount and amount != 0:
line.remake = f'交{due_date_str}欠 {debt_amount}'
continue
if (due_month > actual_month and due_year == actual_year) or (due_year > actual_year):
line.remake = f'{actual_date_str}预{due_date_str}{debt_str}'
elif (due_month < actual_month and due_year == actual_year) or (due_year < actual_year):
line.remake = f'{actual_date_str}补{due_date.strftime("%Y-%m-%d")}{debt_str}'