664 lines
35 KiB
Python
664 lines
35 KiB
Python
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}' |