1819 lines
84 KiB
Python
1819 lines
84 KiB
Python
import logging
|
||
from datetime import datetime, timedelta, date
|
||
from odoo.tools.float_utils import float_round
|
||
from numpy.ma.core import default_filler
|
||
from odoo import models, fields, api, exceptions, _
|
||
from odoo.exceptions import UserError, ValidationError
|
||
from dateutil.relativedelta import relativedelta
|
||
import calendar
|
||
import re
|
||
|
||
|
||
class PropertyMeterWater(models.Model):
|
||
_inherit = 'property.meter.water'
|
||
|
||
contract_id = fields.Many2one('property.lease.contract', string="合同", compute='compute_contract_id')
|
||
|
||
@api.depends('yuthon_property_ids')
|
||
def compute_contract_id(self):
|
||
for record in self:
|
||
if record.yuthon_property_ids:
|
||
lease_contract = self.env['property.lease.contract.line'].search(
|
||
[('address_id', '=', record.yuthon_property_ids[0].id)], limit=1)
|
||
if lease_contract:
|
||
record.contract_id = lease_contract.lease_id.id
|
||
else:
|
||
record.contract_id = False
|
||
else:
|
||
record.contract_id = False
|
||
|
||
class PropertyMeterArchives(models.Model):
|
||
_inherit = 'property.meter.archives'
|
||
|
||
contract_id = fields.Many2one('property.lease.contract', string="合同", compute='compute_contract_id')
|
||
|
||
@api.depends('yuthon_property_ids')
|
||
def compute_contract_id(self):
|
||
for record in self:
|
||
if record.yuthon_property_ids:
|
||
lease_contract = self.env['property.lease.contract.line'].search(
|
||
[('address_id', '=', record.yuthon_property_ids[0].id)], limit=1)
|
||
if lease_contract:
|
||
record.contract_id = lease_contract.lease_id.id
|
||
else:
|
||
record.contract_id = False
|
||
else:
|
||
record.contract_id = False
|
||
|
||
|
||
class MeterArchivesLine(models.Model):
|
||
_inherit = 'meter.archives.line'
|
||
|
||
contract_id = fields.Many2one('property.lease.contract', string="合同")
|
||
lessee_id = fields.Many2one(related="contract_id.lessee_id", string="承租方", store=True)
|
||
contract_name = fields.Char(string='合同编号', store=True, index=True, compute="_compute_name")
|
||
lessee_name = fields.Char(string='承租方名称', store=True, index=True, compute="_compute_name")
|
||
|
||
@api.depends('yuthon_property_ids', 'archives_id', 'contract_id', 'lessee_id')
|
||
def _compute_name(self):
|
||
for name in self:
|
||
if name.contract_id:
|
||
name.contract_name = name.contract_id.name
|
||
else:
|
||
name.contract_name = False
|
||
if name.lessee_id:
|
||
name.lessee_name = name.lessee_id.name
|
||
else:
|
||
name.lessee_name = False
|
||
|
||
def update_contract(self):
|
||
archives_ids = self.env["meter.archives.line"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
for record in archives_ids:
|
||
record.lessee_id = record.contract_id.lessee_id.id
|
||
|
||
class MeterWaterLine(models.Model):
|
||
_inherit = 'meter.water.line'
|
||
|
||
contract_id = fields.Many2one('property.lease.contract', string="合同")
|
||
lessee_id = fields.Many2one(related="contract_id.lessee_id", string="承租方")
|
||
contract_name = fields.Char(string='合同编号', store=True, index=True, compute="_compute_name")
|
||
lessee_name = fields.Char(string='承租方名称', store=True, index=True, compute="_compute_name")
|
||
|
||
@api.depends('yuthon_property_ids', 'water_id', 'contract_id', 'lessee_id')
|
||
def _compute_name(self):
|
||
for name in self:
|
||
if name.contract_id:
|
||
name.contract_name = name.contract_id.name
|
||
if name.lessee_id:
|
||
name.lessee_name = name.lessee_id.name
|
||
|
||
def update_contract(self):
|
||
water_ids = self.env["meter.water.line"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
for record in water_ids:
|
||
if record.yuthon_property_ids:
|
||
contract_line = self.env['property.lease.contract.line'].search([
|
||
('address_id', '=', record.yuthon_property_ids[0].id),
|
||
('lease_id.actual_start_time', '<=',record.meter_date),
|
||
('lease_id.actual_end_time', '>=',record.meter_date)
|
||
], limit=1)
|
||
if contract_line:
|
||
record.contract_id = contract_line.lease_id.id
|
||
|
||
|
||
class YuthonProperty(models.Model):
|
||
_inherit = 'yuthon.property'
|
||
|
||
property_contract_ids = fields.One2many('property.contract.line', 'yuthon_property_id', string="关联物业合同明细行")
|
||
|
||
@api.depends('property_contract_ids')
|
||
def _compute_lessee_id(self):
|
||
today = date.today()
|
||
for record in self:
|
||
contract_lines = record.property_contract_ids
|
||
valid_lines = contract_lines.filtered(
|
||
lambda line:
|
||
line.start_time and
|
||
line.start_time <= today and
|
||
(not line.end_date or line.end_date >= today)
|
||
)
|
||
record.lessee_id = valid_lines[0].lessee_id if valid_lines else False
|
||
|
||
def get_yuthon_property_time(self):
|
||
unique_property_ids = set()
|
||
for record in self.search([('property_state', '=', 'to'), ('property_contract_ids', '=', False)]):
|
||
contracts = self.env['property.evaluation.plan'].search([])
|
||
for contract in contracts:
|
||
if contract.property_id and contract.property_id.id not in unique_property_ids:
|
||
unique_property_ids.add(contract.property_id.id)
|
||
if record.id not in unique_property_ids:
|
||
record.put_assessment_approval()
|
||
else:
|
||
continue
|
||
|
||
def update_lessee_id(self):
|
||
property_ids = self.env["yuthon.property"].browse(
|
||
self._context.get('active_ids', [self._context.get('active_id')])
|
||
)
|
||
current_date = date.today()
|
||
for rec in property_ids:
|
||
valid_contracts = rec.property_contract_ids.filtered(lambda c:
|
||
isinstance(c.start_time,
|
||
date) and c.start_time <= current_date and
|
||
(not c.end_date or (isinstance(c.end_date,
|
||
date) and c.end_date > current_date))
|
||
)
|
||
|
||
if valid_contracts:
|
||
latest_contract = valid_contracts.sorted(key=lambda c: c.start_time, reverse=True)[0]
|
||
rec.lessee_id = latest_contract.lessee_id
|
||
else:
|
||
rec.lessee_id = False
|
||
|
||
@api.onchange('property_contract_ids')
|
||
def _onchange_property_contract_ids(self):
|
||
if self.property_contract_ids:
|
||
self.lessee_id = self.property_contract_ids[-1].lessee_id
|
||
|
||
|
||
# 银行流水
|
||
class PropertyBankStatements(models.Model):
|
||
_inherit = 'property.bank.statements'
|
||
|
||
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
|
||
contract_ids = fields.Many2many(
|
||
'property.lease.contract',
|
||
'property_bank_stmt_contract_rel',
|
||
'stmt_id',
|
||
'contract_id',
|
||
string='合同',
|
||
compute = '_compute_property_employee_id',
|
||
store=True,
|
||
)
|
||
|
||
contract_ids1 = fields.Many2many(
|
||
'property.lease.contract',
|
||
'property_bank_stmt_contract1_rel',
|
||
'stmt_id',
|
||
'contract_id',
|
||
string='已对合同'
|
||
)
|
||
property_name = fields.Text(string="物业名称", compute="compute_property_name", store=True)
|
||
accounts_id = fields.Many2one('property.accounts.approval', string="关联生成应收审批单")
|
||
lessee_ids = fields.Many2many('property.tenant.information', string='承租方', compute="_compute_property_employee_id", store=True)
|
||
admin_id = fields.Many2one('res.users', string="片区管理员", compute="_compute_property_employee_id", store=True)
|
||
|
||
def write_off_details(self):
|
||
self.ensure_one()
|
||
modify_lines = self.env['receivable.modify.line'].search([
|
||
('bank_id', '=', self.id)
|
||
])
|
||
receivable_ids = modify_lines.mapped('receivable_id').filtered(lambda r: r.id)
|
||
return {
|
||
'type': 'ir.actions.act_window',
|
||
'res_model': 'property.accounts.receivable',
|
||
'view_mode': 'tree',
|
||
'view_id': self.env.ref('yuthon_property.view_property_accounts_receivable_tree').id,
|
||
'domain': [('id', 'in', receivable_ids.ids)],
|
||
}
|
||
|
||
@api.depends('name')
|
||
def _compute_property_employee_id(self):
|
||
for record in self:
|
||
lessee_ids = []
|
||
if record.name:
|
||
bank_lines = self.env['property.bank.line'].search([('bank', '=', record.name)])
|
||
for line in bank_lines:
|
||
tenant = line.tenant_id
|
||
# 租户存在 且 承租方明细行不为空
|
||
if tenant and tenant.property_contract_ids:
|
||
lessee_ids.append(tenant.id)
|
||
# 去重
|
||
lessee_ids = list(set(lessee_ids))
|
||
record.lessee_ids = [(6, 0, lessee_ids)]
|
||
|
||
# 2. 重构核心逻辑:按日期筛选出租方变更行,匹配所属单元
|
||
if record.lessee_ids:
|
||
contract_set = self.env['property.lease.contract']
|
||
# 获取当前记录的日期(用于判断出租方变更行的生效期)
|
||
target_date = record.start_time.date() if record.start_time else fields.Date.today()
|
||
|
||
# ====================== 提取目标年月 ======================
|
||
target_year = target_date.year
|
||
target_month = target_date.month
|
||
|
||
for lessee in record.lessee_ids:
|
||
# 从承租方的租户行提取合同
|
||
tenant_lines = lessee.property_contract_ids.filtered(
|
||
lambda line: line.lease_contract_id is not False
|
||
)
|
||
|
||
for tenant_line in tenant_lines:
|
||
contract = tenant_line.lease_contract_id
|
||
if not contract:
|
||
continue
|
||
|
||
# 按【年月】判断合同是否有效
|
||
is_contract_valid = False
|
||
if contract.actual_start_time:
|
||
start_year = contract.actual_start_time.year
|
||
start_month = contract.actual_start_time.month
|
||
# 合同开始年月 <= 目标年月
|
||
if (start_year < target_year) or (
|
||
start_year == target_year and start_month <= target_month):
|
||
# 合同结束时间判断:未设置 或 结束年月 >= 目标年月
|
||
if not contract.actual_end_time:
|
||
is_contract_valid = True
|
||
else:
|
||
end_year = contract.actual_end_time.year
|
||
end_month = contract.actual_end_time.month
|
||
if (end_year > target_year) or (
|
||
end_year == target_year and end_month >= target_month):
|
||
is_contract_valid = True
|
||
|
||
if not is_contract_valid:
|
||
continue
|
||
|
||
# 1. 筛选合同的出租方变更行:生效日期≤目标日期,失效日期≥目标日期/未设置
|
||
valid_lessor_lines = contract.lessor_line.filtered(
|
||
lambda l:
|
||
l.start_date <= target_date and
|
||
(not l.end_date or l.end_date >= target_date)
|
||
)
|
||
# 2. 确定最终的出租方referred值
|
||
if valid_lessor_lines:
|
||
actual_lessor_referred = valid_lessor_lines[0].lessor_id.referred
|
||
else:
|
||
actual_lessor_referred = contract.lessor_id.referred if contract.lessor_id else False
|
||
if actual_lessor_referred == record.your_own_unit:
|
||
if contract.contact_state != 'settle':
|
||
contract_set |= contract
|
||
|
||
record.contract_ids = contract_set
|
||
record.admin_id = record.contract_ids[0].admin_id if record.contract_ids else False
|
||
else:
|
||
record.admin_id = False
|
||
record.contract_ids = [(6, 0, [])]
|
||
|
||
def update_property_employee_id(self):
|
||
property_ids = self.env["property.bank.statements"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
for rec in property_ids:
|
||
rec._compute_property_employee_id()
|
||
|
||
@api.depends('contract_ids')
|
||
def compute_property_name(self):
|
||
for rec in self:
|
||
rec.property_name = ""
|
||
if rec.contract_ids:
|
||
for contract in rec.contract_ids:
|
||
if contract.property_name:
|
||
rec.property_name += contract.property_name + "\n"
|
||
|
||
def batch_revocation(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.revocation()
|
||
|
||
def test_one(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.start_time = rec.start_time - timedelta(hours=8)
|
||
|
||
def contract_context(self):
|
||
# 总感觉关联有问题,流水应该是取物业关联的当期合同,有个时间情况没考虑到
|
||
bank_ids = self.env["property.bank.statements"].browse(self._context.get('active_ids', self._context.get('active_id')))
|
||
for property in bank_ids.search([('admin_id', '!=', False)]):
|
||
for pro in property.property_ids:
|
||
# 内部逻辑的关联还有很大问题
|
||
if len(pro.property_contract_ids) > 0:
|
||
if len(pro.property_contract_ids.lease_contract_id) > 0:
|
||
property.write({'contract_ids': [(4, pro.property_contract_ids[-1].lease_contract_id.id)]})
|
||
property.write({'lessee_ids': [(4, pro.property_contract_ids[-1].lessee_id.id)]})
|
||
|
||
def revocation(self):
|
||
self.ensure_one()
|
||
for modify in self.receivable_modify_ids:
|
||
if modify.is_write:
|
||
new_delivered = modify.receivable_id.delivered1 - modify.amount
|
||
modify.receivable_id.delivered1 = max(new_delivered, 0)
|
||
modify.receivable_id.is_reconciliation = False
|
||
|
||
if modify.receivable_id.delivered1 > 0:
|
||
modify.receivable_id.deposit_state = 'no_paid'
|
||
else:
|
||
modify.receivable_id.deposit_state = 'unpaid'
|
||
|
||
modify.receivable_id.reconciliation_time = False
|
||
modify.receivable_id.payout_date = False
|
||
if not modify.is_penalty:
|
||
modify.receivable_id.remark = None
|
||
else:
|
||
if modify.is_penalty:
|
||
modify.receivable_id.unlink()
|
||
|
||
if self.receivable_modify_ids:
|
||
self.receivable_modify_ids.unlink()
|
||
|
||
if self.bank_state != 'registered':
|
||
self.yes_amount = self.original_amount - self.change_amount
|
||
else:
|
||
self.yes_amount = 0
|
||
|
||
self.bank_state = 'not_reconciled'
|
||
self.registered = 0
|
||
self.reconciliation_time = None
|
||
self._filter_contracts_based_on_receivables()
|
||
|
||
def _filter_contracts_based_on_receivables(self):
|
||
"""还原合同"""
|
||
for stmt in self:
|
||
if not stmt.contract_ids1:
|
||
continue
|
||
new_contract_ids = stmt.contract_ids.ids + stmt.contract_ids1.ids
|
||
stmt.contract_ids = [(6, 0, new_contract_ids)]
|
||
|
||
def binding_information(self):
|
||
return {
|
||
'name': '信息绑定弹窗',
|
||
'type': 'ir.actions.act_window',
|
||
'view_mode': 'form',
|
||
'res_model': 'binding.information.wizard',
|
||
'target': 'new',
|
||
'context': {
|
||
'default_lessee_ids': [(6, 0, self.lessee_ids.ids)] if self.lessee_ids else False,
|
||
'default_bank_id': self.id,
|
||
'default_your_own_unit': self.your_own_unit,
|
||
}
|
||
}
|
||
|
||
def unbind(self):
|
||
statements_ids = self.env["property.bank.statements"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id'))
|
||
)
|
||
for statement in statements_ids:
|
||
if statement.name:
|
||
bank_account = self.env['property.bank.line'].search([
|
||
('bank', '=', statement.name)
|
||
])
|
||
if bank_account.tenant_id:
|
||
# 获取需要删除的合同明细行
|
||
lines_to_delete = self.env['property.tenant.line'].search([
|
||
('tenant_id', '=', bank_account.tenant_id.id),
|
||
('lease_contract_id', 'in', statement.contract_ids.ids)
|
||
])
|
||
# 删除合同明细行
|
||
if lines_to_delete:
|
||
lines_to_delete.unlink()
|
||
statement.contract_ids = [(5,)]
|
||
|
||
def _get_reconciliation_vals(self, record, contract_ids, is_equity=False):
|
||
if record.name:
|
||
collection_type_map = {
|
||
'rent': '租金',
|
||
'deposit': '押金',
|
||
'management_fee': '管理费',
|
||
'lightbox': '灯箱',
|
||
'guanzijian': '管子间',
|
||
'water_fee': '水费',
|
||
'electricity_fee': '电费',
|
||
'rename_fee': '更名费',
|
||
'service': '有偿服务收入',
|
||
'fee': '人工费/日光灯款/代垫用料费',
|
||
}
|
||
used_type_names = set()
|
||
total_rent_month = 0
|
||
base_domain = [
|
||
('contract_id', 'in', contract_ids.ids),
|
||
('is_invalid', '=', False),
|
||
('is_reconciliation', '=', False),
|
||
]
|
||
if not is_equity:
|
||
base_domain.append(('contract_id.lessor_id.referred', '=', self.your_own_unit))
|
||
|
||
def _search_receivable(management_type_name):
|
||
domain = base_domain + [('management_type_id.name', '=', management_type_name)]
|
||
return self.env['property.accounts.receivable'].search(domain)
|
||
|
||
service_ids = _search_receivable('有偿服务收入')
|
||
rent_ids = _search_receivable('租金')
|
||
rename_fee_ids = _search_receivable('更名费')
|
||
deposit_ids = _search_receivable('押金')
|
||
management_ids = _search_receivable('管理费')
|
||
lightbox_ids = _search_receivable('灯箱')
|
||
guanzi_ids = _search_receivable('管子间')
|
||
water_ids = _search_receivable('水费')
|
||
electricity_ids = _search_receivable('电费')
|
||
fee_ids = _search_receivable('人工费/日光灯款/代垫用料费')
|
||
|
||
def create_reconciliation_vals(ids, collection_type_val, sequence_val):
|
||
"""
|
||
:param ids:费用记录集
|
||
:param collection_type_val: 费用类型(如 'rent', 'deposit')
|
||
:return: 格式化后的费用行列表
|
||
"""
|
||
nonlocal total_rent_month
|
||
vals_list = []
|
||
current_sequence = sequence_val
|
||
type_name = collection_type_map.get(collection_type_val, '')
|
||
# # 特殊处理:押金类型需要去重相同合同的记录
|
||
# if collection_type_val == 'deposit':
|
||
# # 使用字典来跟踪已处理的合同ID
|
||
# processed_contracts = set()
|
||
# for sub_record in ids:
|
||
# contract_id = sub_record.contract_id.id if sub_record.contract_id else False
|
||
# original_amount = sub_record.original_amount if sub_record.original_amount else 0
|
||
# submitted = sub_record.submitted if sub_record.submitted else 0
|
||
# # 跳过已处理的合同
|
||
# if contract_id and contract_id in processed_contracts:
|
||
# continue
|
||
# # 获取合同的实际结束日期作为到期日
|
||
# due_date = False
|
||
# if contract_id:
|
||
# due_date = sub_record.contract_id.actual_start_time
|
||
# # 构建对账行数据
|
||
# vals = {
|
||
# 'contract_id': contract_id,
|
||
# 'rent_month': original_amount,
|
||
# 'rent': submitted,
|
||
# 'collection_type': collection_type_val,
|
||
# 'sequence': current_sequence,
|
||
# 'due_date': due_date, # 押金类型使用合同实际开始日期
|
||
# }
|
||
# total_rent_month += original_amount # 累加总金额
|
||
# vals_list.append((0, 0, vals))
|
||
# # 标记此合同已处理
|
||
# if contract_id:
|
||
# processed_contracts.add(contract_id)
|
||
#
|
||
# current_sequence += 10
|
||
# else:
|
||
# # 非押金类型:正常处理每条记录
|
||
for sub_record in ids:
|
||
contract_id = sub_record.contract_id.id if sub_record.contract_id else False
|
||
original_amount = sub_record.original_amount if sub_record.original_amount else 0
|
||
submitted = sub_record.submitted if sub_record.submitted else 0
|
||
due_date = sub_record.due_date if sub_record.due_date else False
|
||
vals = {
|
||
'contract_id': contract_id,
|
||
'rent_month': original_amount,
|
||
'rent': submitted,
|
||
'collection_type': collection_type_val,
|
||
'sequence': current_sequence,
|
||
'due_date': due_date,
|
||
}
|
||
total_rent_month += original_amount # 累加总金额
|
||
vals_list.append((0, 0, vals))
|
||
|
||
current_sequence += 10
|
||
|
||
if vals_list and type_name:
|
||
used_type_names.add(type_name)
|
||
|
||
return vals_list, current_sequence
|
||
|
||
all_vals = []
|
||
service_vals, next_seq = create_reconciliation_vals(service_ids, 'service', 1)
|
||
rent_vals, next_seq = create_reconciliation_vals(rent_ids, 'rent', 1)
|
||
deposit_vals, next_seq = create_reconciliation_vals(deposit_ids, 'deposit', 100)
|
||
management_vals, next_seq = create_reconciliation_vals(management_ids, 'management_fee', 200)
|
||
rename_vals, next_seq = create_reconciliation_vals(rename_fee_ids, 'rename_fee', 210)
|
||
lightbox_vals, next_seq = create_reconciliation_vals(lightbox_ids, 'lightbox', 220)
|
||
guanzi_vals, next_seq = create_reconciliation_vals(guanzi_ids, 'guanzijian', 230)
|
||
water_vals, next_seq = create_reconciliation_vals(water_ids, 'water_fee', 300)
|
||
electricity_vals, next_seq = create_reconciliation_vals(electricity_ids, 'electricity_fee', 400)
|
||
fee_vals, next_seq = create_reconciliation_vals(fee_ids, 'fee', 500)
|
||
|
||
all_vals.extend(rent_vals)
|
||
all_vals.extend(deposit_vals)
|
||
all_vals.extend(lightbox_vals)
|
||
all_vals.extend(guanzi_vals)
|
||
all_vals.extend(service_vals)
|
||
all_vals.extend(management_vals)
|
||
all_vals.extend(water_vals)
|
||
all_vals.extend(electricity_vals)
|
||
all_vals.extend(fee_vals)
|
||
all_vals.extend(rename_vals)
|
||
|
||
# 按 due_date 正序排列,最早日期在最上面
|
||
all_vals.sort(key=lambda x: (x[2].get('due_date') or date.max, x[2].get('contract_id') or 0))
|
||
# 重新设置 sequence,确保按正序从 10 开始递增
|
||
for idx, val in enumerate(all_vals, start=1):
|
||
val[2]['sequence'] = idx * 10
|
||
|
||
return all_vals, total_rent_month,
|
||
return [], 0
|
||
|
||
def _get_context_and_action(self, record, all_vals, total_rent_month):
|
||
"""
|
||
:param record: 银行收款记录
|
||
:param all_vals: 应收费用列表
|
||
:param total_rent_month: 应收总金额
|
||
:return:
|
||
"""
|
||
start_time = self.start_time + relativedelta(hours=8)
|
||
warning_msg = self._check_unreconciled_statements(start_time)
|
||
context = {
|
||
"default_actual_date": fields.Date.to_date(start_time),
|
||
"default_bank_id": self.id,
|
||
"default_bank_branch": self.bank_branch,
|
||
"default_account": record.name,
|
||
"default_contract_ids": [(6, 0, self.contract_ids.ids)],
|
||
"default_actual_total": record.original_amount,
|
||
"default_reconciliation_line_ids": all_vals, # 租金,应收日期排序、管理费、电、水、押金
|
||
'default_amount_reconciled': self.no_amount,
|
||
"default_receivable_total": total_rent_month,
|
||
"default_warning_msg":warning_msg,
|
||
"dialog_size": "extra-large",
|
||
"default_order": "due_date desc",
|
||
}
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "对账弹窗",
|
||
"res_model": "property.reconciliation.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
"dialog_size": "extra-large",
|
||
"context": context,
|
||
}
|
||
|
||
def _check_unreconciled_statements(self, current_transaction_time):
|
||
"""
|
||
检查所有承租方对应的银行账号在property.bank.statements中是否有未对账流水
|
||
:param current_transaction_time: 当前交易时间(带时区处理后)
|
||
"""
|
||
if not self.lessee_ids:
|
||
return
|
||
bank_accounts = self.env['property.bank.line'].search([
|
||
('tenant_id', 'in', self.lessee_ids.ids)
|
||
]).mapped('bank')
|
||
bank_accounts = [acc for acc in bank_accounts if acc] # 过滤空值
|
||
if not bank_accounts:
|
||
return
|
||
unreconciled = {}
|
||
for account in bank_accounts:
|
||
# 查询条件:同账号 + 交易时间早于当前 + 未对账
|
||
statements = self.env['property.bank.statements'].search([
|
||
('name', '=', account),
|
||
('start_time', '<', current_transaction_time - timedelta(hours=8)),
|
||
('your_own_unit', '=', self.your_own_unit),
|
||
('lending_direction', '=', self.lending_direction),
|
||
('bank_state', '=', 'not_reconciled'),
|
||
('id', '!=', self.id),
|
||
])
|
||
if statements:
|
||
unreconciled[account] = len(statements)
|
||
if unreconciled:
|
||
warning_msg = _("⚠️ 检测到未对账的历史流水,请优先处理(可忽略)")
|
||
else:
|
||
warning_msg = False
|
||
return warning_msg
|
||
|
||
def button_reconciliation(self):
|
||
for record in self:
|
||
all_vals, total_rent_month = self._get_reconciliation_vals(record, record.contract_ids)
|
||
return self._get_context_and_action(record, all_vals, total_rent_month)
|
||
|
||
def put_button(self):
|
||
form_id = self.env.ref('yuthon_property_approval.view_property_accounts_approval_form').id
|
||
bank_list = []
|
||
bank_list.append((0, 0, {
|
||
'start_time': self.start_time,
|
||
'company_id': self.company_id.id if self.company_id else False,
|
||
'name': self.name,
|
||
'bank_name': self.bank_name,
|
||
'name_account': self.name_account,
|
||
'purpose': self.purpose,
|
||
'journal_date': self.journal_date,
|
||
'bank_branch': self.bank_branch,
|
||
'extract': self.extract,
|
||
'account_code': self.account_code,
|
||
'is_put': self.is_put,
|
||
'remake': self.remake,
|
||
'original_amount': self.original_amount,
|
||
'contract_account': self.contract_account,
|
||
'contract_penalty': self.contract_penalty,
|
||
'property_ids': [(6, 0, self.property_ids.ids)] if self.property_ids else [],
|
||
'contract_ids': [(6, 0, self.contract_ids.ids)] if self.contract_ids else [],
|
||
'bank_number': self.bank_number,
|
||
'message': self.message,
|
||
'number': self.number,
|
||
'voucher_type': self.voucher_type
|
||
}))
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "应收账单生成审批单",
|
||
"res_model": "property.accounts.approval",
|
||
"views": [[form_id, "form"]],
|
||
"context": {
|
||
"default_bank_ids": bank_list,}}
|
||
|
||
class PropertyBankLine(models.Model):
|
||
_inherit = 'property.bank.line'
|
||
|
||
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
|
||
|
||
|
||
# 应收账单
|
||
class PropertyAccountsReceivable(models.Model):
|
||
_inherit = 'property.accounts.receivable'
|
||
_rec_name = 'contract_id'
|
||
|
||
property_ids = fields.Many2many('yuthon.property', string='物业', compute='_compute_property_ids', store=True)
|
||
contract_accounts_id = fields.Many2one('contract.accounts.receivable', string="合同")
|
||
property_name = fields.Text(string='物业', compute="_compute_property_name")
|
||
is_penalty = fields.Boolean(string='是否违约金', default=True, compute = '_compute_is_penalty', store=True)
|
||
management_area = fields.Float(related='contract_id.total_area', string='面积')
|
||
title_deed = fields.Char(string="产权证号", compute='_compute_title_deed')
|
||
lessee_id = fields.Many2one('property.tenant.information', string='承租方', store=True, compute='_compute_lessee_id')
|
||
company_id = fields.Many2one('yuthon.property.company', string="出租方公司", store=True, compute='_compute_lessee_id')
|
||
# company_id1 = fields.Many2one(related='contract_id.admin_id.company_id', string="管理员公司", store=True)
|
||
company_id2 = fields.Many2one("res.company", string="公司", compute='_compute_company_id2', store=True)
|
||
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
|
||
rent_id = fields.Char(string='租金id') # 没效果
|
||
source_receivable_id = fields.Many2one('property.accounts.receivable', string='关联id', index=True, copy=False)
|
||
contract_sort = fields.Selection([('property', '物业'), ('guanggao', '广告位'),('parking', '停车场'), ('guanzi', '管子间'), ('room', '机房'), ('base_station','基站'), ('other', '其他')],
|
||
related='contract_id.contract_sort', string="资产类型", index=True)
|
||
admin_id = fields.Many2one(related='contract_id.admin_id', string="片区管理员", store=True)
|
||
tenure_type = fields.Selection([('self', '自有'), ('agency', '代管')], string='权属类型', compute="compute_tenure_type", store=True)
|
||
tenure_type_label = fields.Char(string='权属类型(文本)', compute='_compute_tenure_type_label', store=True)
|
||
|
||
is_refunded = fields.Boolean(string='是否已退款', compute='_compute_is_refunded', store=True)
|
||
penalty = fields.Float(string="违约金基数")
|
||
penalty_date = fields.Date(string="违约金日期")
|
||
penalty_ratio_value = fields.Float(string="违约金比例", digits=(16, 6)) # 从合同 percentage_liquidated_damages.name 带入,千分比需6位精度
|
||
invoice_type = fields.Selection(
|
||
[('ordinary_invoice', '普通发票'), ('vat_invoice', '增值税发票')], string='发票类型', default='ordinary_invoice')
|
||
# 报表新增字段
|
||
rent_owed = fields.Float(string="欠租金额")
|
||
per_day = fields.Float(string="每天违约金(元/天)")
|
||
|
||
last_month_table = fields.Float(string="上月表数")
|
||
this_month_table = fields.Float(string="本月表数")
|
||
ratio = fields.Float(string="倍率")
|
||
billable_quantity = fields.Float(string="计费数量")
|
||
pre_collection = fields.Float(string="预收金额")
|
||
|
||
actual_coping = fields.Float(string="实际应付", compute='_compute_actual_coping', store=True)
|
||
charging_range = fields.Text(string="收费区间")
|
||
|
||
precinct_id = fields.Many2one('yuthon.precinct.message', string="所属片区", compute='_compute_precinct_id', store=True)
|
||
# 核对人:应收账单创建时自动记录当前登录人
|
||
verifier_id = fields.Many2one('res.users', string="核对人", default=lambda self: self.env.user)
|
||
|
||
@api.model_create_multi
|
||
def create(self, vals_list):
|
||
"""创建应收账单时,核对人自动填当前登录人(未显式指定时)"""
|
||
for vals in vals_list:
|
||
if not vals.get('verifier_id'):
|
||
vals['verifier_id'] = self.env.user.id
|
||
return super().create(vals_list)
|
||
|
||
@api.depends('contract_id', 'contract_id.lease_ids', 'contract_id.lease_ids.address_id',
|
||
'contract_id.lease_ids.address_id.manage_information_ids', 'due_date')
|
||
def _compute_precinct_id(self):
|
||
"""根据合同第一条物业明细的管理信息,按应收开始日期匹配对应管理明细行,取其所属片区"""
|
||
for rec in self:
|
||
rec.precinct_id = False
|
||
if not rec.contract_id or not rec.contract_id.lease_ids:
|
||
continue
|
||
address = rec.contract_id.lease_ids[0].address_id
|
||
if not address or not address.manage_information_ids:
|
||
continue
|
||
due_date = rec.due_date
|
||
if not due_date:
|
||
continue
|
||
matched = address.manage_information_ids.filtered(
|
||
lambda m: m.start_time and m.start_time <= due_date and (not m.end_date or m.end_date >= due_date)
|
||
)
|
||
if matched:
|
||
rec.precinct_id = matched[0].precinct_id
|
||
else:
|
||
past = address.manage_information_ids.filtered(lambda m: m.start_time and m.start_time <= due_date)
|
||
if past:
|
||
rec.precinct_id = past.sorted('start_time', reverse=True)[0].precinct_id
|
||
|
||
def action_precinct_id(self):
|
||
record_ids = self.env["property.accounts.receivable"].browse(
|
||
self._context.get("active_ids", self._context.get("active_id"))
|
||
)
|
||
for rec in record_ids:
|
||
rec._compute_precinct_id()
|
||
|
||
def action_fix(self):
|
||
record_ids = self.env["property.accounts.receivable"].browse(
|
||
self._context.get("active_ids", self._context.get("active_id"))
|
||
)
|
||
for record in record_ids:
|
||
if record.management_type_id.name == '违约金' and record.due_date:
|
||
if record.is_reconciliation:
|
||
due_date = record.payout_date
|
||
else:
|
||
due_date = record.due_date
|
||
# 查询同合同、租金类型、payout_date匹配的所有租金记录
|
||
rent_ids = self.env['property.accounts.receivable'].search([
|
||
('contract_id', '=', record.contract_id.id),
|
||
('management_type_id.name', '=', '租金'),
|
||
('payout_date', '=', due_date),
|
||
])
|
||
if not rent_ids:
|
||
continue
|
||
|
||
valid_rents = rent_ids.filtered(lambda r:
|
||
r.due_date and r.payout_date >= (
|
||
r.due_date + relativedelta(months=1)).replace(day=1)
|
||
)
|
||
|
||
if valid_rents:
|
||
record.source_receivable_id = valid_rents[0].id
|
||
|
||
def action_receivable(self):
|
||
record_ids = self.env["property.accounts.receivable"].browse(
|
||
self._context.get("active_ids", self._context.get("active_id"))
|
||
)
|
||
for record in record_ids:
|
||
record.discount_amount = record.submitted
|
||
record.discount_date = datetime(2026, 1, 1)
|
||
|
||
def action_clear_legacy_unpaid(self):
|
||
"""一次性历史欠费清理(应收日期 < 2026-01-01 且 未对账,不限收款状态):
|
||
1) 应收类型 = 租金 → 关闭「生成违约金」(is_liquidated_damages = False)
|
||
2) 应收类型 = 违约金 → 作废 (is_invalid = True,作废日期 = 当天)
|
||
|
||
作废后汇总视图 property.accounts.receivable.summary 与「未收明细」跳转都会自动排除。
|
||
"""
|
||
cutoff = date(2026, 1, 1)
|
||
today = fields.Date.context_today(self)
|
||
base_domain = [
|
||
('due_date', '<', cutoff),
|
||
('is_reconciliation', '=', False),
|
||
]
|
||
|
||
# 1) 租金:关闭生成违约金(只处理当前还开着的,避免无谓写入)
|
||
rent_records = self.search(base_domain + [
|
||
('management_type_id.name', '=', '租金'),
|
||
('is_liquidated_damages', '=', True),
|
||
])
|
||
rent_records.write({'is_liquidated_damages': False})
|
||
|
||
# 2) 违约金:作废 + 填当天作废日期(已作废的不动,保留原作废日期)
|
||
penalty_records = self.search(base_domain + [
|
||
('management_type_id.name', '=', '违约金'),
|
||
('is_invalid', '=', False),
|
||
])
|
||
penalty_records.write({'is_invalid': True, 'void_date': today})
|
||
|
||
return {
|
||
'type': 'ir.actions.client',
|
||
'tag': 'display_notification',
|
||
'params': {
|
||
'type': 'success',
|
||
'title': '清理完成',
|
||
'message': '租金关闭「生成违约金」%s 条;违约金作废 %s 条。'
|
||
% (len(rent_records), len(penalty_records)),
|
||
'sticky': False,
|
||
},
|
||
}
|
||
|
||
def verification_wizard(self):
|
||
receivable_lines = self.env['property.accounts.receivable'].search([
|
||
('contract_id', '=', self.contract_id.id),
|
||
('is_reconciliation', '=', False),
|
||
])
|
||
receivable_line_vals = []
|
||
for line in receivable_lines:
|
||
receivable_line_vals.append((0, 0, {
|
||
'due_date': line.due_date,
|
||
'management_type_id': line.management_type_id.id,
|
||
'amount': line.submitted,
|
||
'payment_type': 'pay',
|
||
'receivable_id': line.id,
|
||
'remake': line.remark,
|
||
}))
|
||
|
||
bills_lines = self.env['property.bills.list'].search([
|
||
('contract_id', '=', self.contract_id.id),
|
||
('actual_date', '!=', False),
|
||
])
|
||
bills_line_vals = []
|
||
for line in bills_lines:
|
||
bills_line_vals.append((0, 0, {
|
||
'due_date': line.due_date,
|
||
'management_type_id': line.management_type_id.id,
|
||
'amount': line.amount,
|
||
'bills_id': line.id,
|
||
'payment_type': 'spending',
|
||
'remake': line.remake
|
||
}))
|
||
all_line_vals = receivable_line_vals + bills_line_vals
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "押金核销弹窗",
|
||
"res_model": "property.deposit.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
"context": {
|
||
"default_receivable_id": self.id,
|
||
"default_contract_id": self.contract_id.id,
|
||
"default_lessee_id": self.lessee_id.id,
|
||
"default_actual_date": self.due_date,
|
||
"default_deposit": self.original_amount,
|
||
'default_reconciliation_line_ids': all_line_vals
|
||
}
|
||
}
|
||
|
||
def action_batch_fix_deposit(self):
|
||
"""批量修复2026年3月之前未收的押金数据,标记为已收并同步到修改明细"""
|
||
records = self.env['property.accounts.receivable'].search([
|
||
('management_type_id.name', '=', '押金'),
|
||
('due_date', '<', '2026-03-01'),
|
||
('deposit_state', '=', 'unpaid'),
|
||
])
|
||
|
||
if not records:
|
||
return {
|
||
'type': 'ir.actions.client',
|
||
'tag': 'display_notification',
|
||
'params': {
|
||
'title': '提示',
|
||
'message': '没有符合条件的数据需要修复。',
|
||
'type': 'info',
|
||
'sticky': False,
|
||
}
|
||
}
|
||
|
||
modify_line_vals = []
|
||
for rec in records:
|
||
delivered = rec.original_amount - rec.discount_amount
|
||
rec.write({
|
||
'deposit_state': 'paid',
|
||
'payout_date': rec.due_date,
|
||
'delivered1': delivered,
|
||
'is_reconciliation': True,
|
||
})
|
||
modify_line_vals.append({
|
||
'receivable_id': rec.id,
|
||
'reconciliation_date': rec.due_date,
|
||
'amount': delivered,
|
||
'is_batch_fix': True,
|
||
'remake1': '批量修复押金旧数据',
|
||
'bank_state': 'reconciled',
|
||
})
|
||
|
||
if modify_line_vals:
|
||
self.env['receivable.modify.line'].create(modify_line_vals)
|
||
|
||
return {
|
||
'type': 'ir.actions.client',
|
||
'tag': 'display_notification',
|
||
'params': {
|
||
'title': '批量修复完成',
|
||
'message': f'成功修复 {len(records)} 条押金数据,并同步到修改明细。',
|
||
'type': 'success',
|
||
'sticky': False,
|
||
}
|
||
}
|
||
|
||
def update_compute_lessee_id(self):
|
||
rent_ids = self.env["property.accounts.receivable"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
for rent in rent_ids:
|
||
rent._compute_lessee_id()
|
||
|
||
@api.depends('contract_id', 'due_date')
|
||
def _compute_lessee_id(self):
|
||
for record in self:
|
||
record.lessee_id = False
|
||
record.company_id = False
|
||
if not record.contract_id:
|
||
continue
|
||
lessee_lines = record.contract_id.lessee_line
|
||
valid_lessee_lines = lessee_lines.filtered(
|
||
lambda line:
|
||
line.start_date and
|
||
line.start_date <= record.due_date and #
|
||
(not line.end_date or line.end_date >= record.due_date)
|
||
)
|
||
if valid_lessee_lines and len(valid_lessee_lines) > 0:
|
||
record.lessee_id = valid_lessee_lines[0].lessee_id
|
||
else:
|
||
record.lessee_id = record.contract_id.lessee_id or False
|
||
|
||
lessor_lines = record.contract_id.lessor_line
|
||
valid_lessor_lines = lessor_lines.filtered(
|
||
lambda line:
|
||
line.start_date and
|
||
line.start_date <= record.due_date and
|
||
(not line.end_date or line.end_date >= record.due_date)
|
||
)
|
||
if valid_lessor_lines and len(valid_lessor_lines) > 0:
|
||
record.company_id = valid_lessor_lines[0].lessor_id
|
||
else:
|
||
record.company_id = record.contract_id.lessor_id
|
||
|
||
@api.depends('contract_id', 'due_date')
|
||
def compute_tenure_type(self):
|
||
for record in self:
|
||
is_deposit = record.management_type_id.name == '押金'
|
||
tenure_type = False
|
||
first_lease = record.contract_id.lease_ids[:1]
|
||
manage_infos = first_lease.address_id.manage_information_ids
|
||
if not manage_infos:
|
||
record.tenure_type = tenure_type
|
||
continue
|
||
|
||
due_date_dt = fields.Date.from_string(record.due_date)
|
||
matched_info = False
|
||
if is_deposit:
|
||
matched_info = manage_infos.sorted(key=lambda x: x.create_date, reverse=True)[:1]
|
||
else:
|
||
for info in manage_infos:
|
||
start_time = fields.Date.from_string(info.start_time)
|
||
if start_time > due_date_dt:
|
||
continue
|
||
if info.end_date:
|
||
end_dt = fields.Date.from_string(info.end_date)
|
||
if end_dt < due_date_dt:
|
||
continue
|
||
matched_info = info
|
||
break
|
||
|
||
if matched_info:
|
||
tenure_type = matched_info.tenure_type
|
||
record.tenure_type = tenure_type
|
||
|
||
|
||
def update_compute_tenure_type(self):
|
||
accounts = self.env["property.accounts.receivable"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id', []))
|
||
)
|
||
for account in accounts:
|
||
account.compute_tenure_type()
|
||
|
||
@api.depends('tenure_type')
|
||
def _compute_tenure_type_label(self):
|
||
label_map = {'self': '自有', 'agency': '代管'}
|
||
for record in self:
|
||
record.tenure_type_label = label_map.get(record.tenure_type, '')
|
||
|
||
def update_charging_range(self):
|
||
accounts = self.env["property.accounts.receivable"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id', []))
|
||
)
|
||
for account in accounts:
|
||
type_name = account.management_type_id.name or ""
|
||
def get_charging_range(start, end):
|
||
if not start or not end:
|
||
return "" # 日期不全时返回空
|
||
return f"{start.strftime('%Y-%m-%d')} 到 {end.strftime('%Y-%m-%d')}"
|
||
|
||
if type_name in ["租金", "管理费", '灯箱', '管子间']:
|
||
contract_actual_end = account.contract_id.actual_end_time
|
||
min_end = min(contract_actual_end,
|
||
account.due_date_end) if contract_actual_end else account.due_date_end
|
||
account.charging_range = get_charging_range(account.due_date, min_end)
|
||
|
||
elif type_name == "押金":
|
||
if account.due_date:
|
||
end_date = account.due_date + relativedelta(months=1)
|
||
account.charging_range = f"{account.due_date.strftime('%Y-%m-%d')} 到 {end_date.strftime('%Y-%m-%d')}"
|
||
|
||
elif type_name in ["水费", "电费"]:
|
||
if account.due_date:
|
||
start_date = (account.due_date - relativedelta(months=2)).replace(day=16)
|
||
end_date = (start_date + relativedelta(months=1)).replace(day=14)
|
||
account.charging_range = get_charging_range(start_date, end_date)
|
||
|
||
elif type_name == "违约金":
|
||
if account.due_date_end:
|
||
account.charging_range = get_charging_range(account.due_date, account.due_date_end)
|
||
|
||
@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
|
||
elif record.contract_id:
|
||
record.contract_name = record.contract_id.contract_code
|
||
else:
|
||
record.contract_name = False
|
||
|
||
@api.depends('company_id')
|
||
def _compute_company_id2(self):
|
||
for rec in self:
|
||
if not rec.company_id:
|
||
continue
|
||
if rec.company_id:
|
||
if rec.company_id.referred == '天河物管':
|
||
company = self.env['res.company'].browse(3)
|
||
rec.company_id2 = company.id if company else False
|
||
elif rec.company_id.referred in ('天河投资', '天投城建'):
|
||
company = self.env['res.company'].search([('name', '=', '集团本部')], limit=1)
|
||
rec.company_id2 = company.id if company else False
|
||
|
||
@api.depends('original_amount', 'pre_collection')
|
||
def _compute_actual_coping(self):
|
||
for record in self:
|
||
if record.original_amount:
|
||
record.actual_coping = record.original_amount - record.pre_collection
|
||
else:
|
||
record.actual_coping = 0
|
||
|
||
def update_lessee_id(self):
|
||
# 1. 获取选中的应收账单记录
|
||
accounts_ids = self.env["property.accounts.receivable"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id'))
|
||
)
|
||
if not accounts_ids:
|
||
return
|
||
unique_contracts = {
|
||
account.contract_id for account in accounts_ids
|
||
if account.contract_id
|
||
}
|
||
for contract in unique_contracts:
|
||
contract_account = self.env['contract.accounts.receivable'].search([
|
||
('contract_id', '=', contract.id)
|
||
], limit=1)
|
||
if not contract_account:
|
||
contract_account = self.env['contract.accounts.receivable'].create({
|
||
'contract_id': contract.id,
|
||
})
|
||
contract_account.onchange_contract_id()
|
||
else:
|
||
contract_account.onchange_contract_id()
|
||
|
||
@api.depends('management_type_id')
|
||
def _compute_is_penalty(self):
|
||
for rec in self:
|
||
if rec.management_type_id.name == '违约金':
|
||
rec.is_penalty = True
|
||
else:
|
||
rec.is_penalty = False
|
||
|
||
def penalty_wizard(self):
|
||
self.ensure_one()
|
||
penalty_lines = []
|
||
penalty_lines.append((0, 0, {
|
||
'date': self.due_date,
|
||
'remake': f'开始计算违约金,应收金额: ¥{self.original_amount}',
|
||
'days_overdue': 0,
|
||
'remaining_principal': self.original_amount,
|
||
'penalty': 0,
|
||
}))
|
||
return {
|
||
'name': '收款明细',
|
||
'type': 'ir.actions.act_window',
|
||
'res_model': 'penalty.line.wizard',
|
||
'view_mode': 'form',
|
||
'target': 'new',
|
||
'context': {
|
||
'default_receivable_id': self.id,
|
||
'default_penalty_line_ids': penalty_lines,
|
||
'default_rent_collected': self.submitted,
|
||
}
|
||
}
|
||
|
||
def _resolve_source_receivable(self):
|
||
self.ensure_one()
|
||
empty_receivable = self.env['property.accounts.receivable']
|
||
if self.source_receivable_id:
|
||
return self.source_receivable_id
|
||
if self.management_type_id.name != '违约金' or not self.contract_id:
|
||
return empty_receivable
|
||
|
||
date_match = re.search(r'(\d{4}-\d{2}-\d{2})[至-](\d{4}-\d{2}-\d{2})', self.remark or '')
|
||
if not date_match:
|
||
return empty_receivable
|
||
|
||
start_date = datetime.strptime(date_match.group(1), "%Y-%m-%d").date()
|
||
candidates = self.search([
|
||
('contract_id', '=', self.contract_id.id),
|
||
('id', '!=', self.id),
|
||
('is_invalid', '=', False),
|
||
('management_type_id.name', 'not in', ['违约金', '押金']),
|
||
('is_liquidated_damages', '=', True),
|
||
], order='due_date asc, id asc')
|
||
|
||
payout_matches = candidates.filtered(
|
||
lambda r: r.payout_date and r.payout_date + timedelta(days=1) == start_date
|
||
)
|
||
if len(payout_matches) == 1:
|
||
return payout_matches[:1]
|
||
|
||
cycle_matches = candidates.filtered(
|
||
lambda r: r.due_date and (r.due_date + relativedelta(months=1)).replace(day=1) == start_date
|
||
)
|
||
if len(cycle_matches) == 1:
|
||
return cycle_matches[:1]
|
||
|
||
if payout_matches:
|
||
return payout_matches[:1]
|
||
if cycle_matches:
|
||
return cycle_matches[:1]
|
||
return empty_receivable
|
||
|
||
def generate_penalty(self):
|
||
"""根据选中的租金应收生成违约金应收(仅对已对账部分,逾期判定 + 去重)"""
|
||
for receivable in self:
|
||
receivable._compute_is_overdue()
|
||
if receivable.management_type_id.name != '租金':
|
||
continue
|
||
if not receivable.is_liquidated_damages:
|
||
continue
|
||
if not receivable.is_overdue:
|
||
continue
|
||
if not receivable.contract_id:
|
||
continue
|
||
|
||
percentage = receivable.contract_id.percentage_liquidated_damages
|
||
if not percentage or not percentage.name:
|
||
continue
|
||
|
||
original_amount = receivable.original_amount
|
||
first_day = (receivable.due_date + relativedelta(months=1)).replace(day=1)
|
||
penalty_days = (receivable.payout_date - first_day).days + 1
|
||
modify_line = self.env['receivable.modify.line'].search([
|
||
('receivable_id', '=', receivable.id),
|
||
('reconciliation_date', '!=', False),
|
||
('is_penalty', '=', False)
|
||
], order='reconciliation_date asc', limit=1)
|
||
if receivable.number_of_payments == 1:
|
||
original_amount = receivable.original_amount
|
||
elif modify_line:
|
||
original_amount = receivable.original_amount - modify_line.amount
|
||
penalty_amount = round(penalty_days * original_amount * float(percentage.name), 2)
|
||
if penalty_amount <= 0:
|
||
continue
|
||
|
||
existing = self.env['property.accounts.receivable'].search([
|
||
('contract_id', '=', receivable.contract_id.id),
|
||
('management_type_id.name', '=', '违约金'),
|
||
('original_amount', '=', penalty_amount),
|
||
('source_receivable_id', '=', receivable.id),
|
||
('is_invalid', '=', False),
|
||
], limit=1)
|
||
if existing:
|
||
existing.write({
|
||
"due_date": receivable.payout_date,
|
||
"due_date_end": None,
|
||
'is_batch': True,
|
||
})
|
||
continue
|
||
acc_receivable = self.env['property.accounts.receivable'].create({
|
||
'contract_id': receivable.contract_id.id,
|
||
'management_type_id': self.env['yuthon.management.type'].search(
|
||
[('name', '=', '违约金')], limit=1).id,
|
||
'due_date': receivable.payout_date,
|
||
'due_date_end': None,
|
||
'is_batch': True,
|
||
'original_amount': penalty_amount,
|
||
'remark': f'违约金({first_day}至{receivable.payout_date},共{penalty_days}天)',
|
||
'source_receivable_id': receivable.id,
|
||
'charging_range': f'{first_day}到{receivable.payout_date}',
|
||
'deposit_state': 'unpaid',
|
||
})
|
||
acc_receivable.update_property_ids()
|
||
|
||
@api.depends('contract_id')
|
||
def _compute_property_name(self):
|
||
for record in self:
|
||
record.property_name = record.contract_id.use_address
|
||
|
||
@api.depends('property_ids')
|
||
def _compute_title_deed(self):
|
||
for record in self:
|
||
title_deed = ""
|
||
if record.property_ids:
|
||
first_property = record.property_ids[0]
|
||
title_deed = first_property.title_deed or ""
|
||
record.title_deed = title_deed
|
||
|
||
def invalid(self):
|
||
for record in self:
|
||
# 处理多对多物业名称,用逗号拼接
|
||
property_names = ",".join(record.property_ids.mapped('name')) if record.property_ids else "未知物业"
|
||
receivable_log = (
|
||
f'当前登录人{self.env.user.name}于{fields.Datetime.now()}将物业为 {property_names}的应收账单状态修改为作废')
|
||
original_log = record.receivable_log or ''
|
||
new_log = f"{original_log}\n{receivable_log}" if original_log else receivable_log
|
||
record.write({
|
||
'is_invalid': True,
|
||
'receivable_log': new_log
|
||
})
|
||
return True
|
||
|
||
# 红冲
|
||
def all_red(self):
|
||
receivable_ids = self.env["property.accounts.receivable"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id', []))
|
||
)
|
||
for receivable in receivable_ids:
|
||
property_names = ",".join(receivable.property_ids.mapped('name')) if receivable.property_ids else "未知物业"
|
||
receivable_log = (
|
||
f'当前登录人{self.env.user.name}于{fields.Datetime.now()}将物业为 {property_names}的应收账单红冲')
|
||
original_log = receivable.receivable_log or ''
|
||
new_log = f"{original_log}\n{receivable_log}" if original_log else receivable_log
|
||
receivable.write({
|
||
'is_red': True,
|
||
'red_state_date': fields.Date.today(),
|
||
'receivable_log': new_log
|
||
})
|
||
return True
|
||
|
||
@api.depends('contract_id')
|
||
def _compute_is_refunded(self):
|
||
for record in self:
|
||
record.is_refunded = False
|
||
if record.contract_id:
|
||
# 检查是否有已退款的押金记录
|
||
refund_bills = self.env['property.bills.list'].search([
|
||
('contract_id', '=', record.contract_id.id),
|
||
('management_type_id.name', '=', '押金'),
|
||
('actual_date', '!=', False),
|
||
], limit=1)
|
||
record.is_refunded = bool(refund_bills)
|
||
|
||
@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
|
||
|
||
def update_property_ids(self):
|
||
receivable_ids = self.env["property.accounts.receivable"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
for receivable in receivable_ids:
|
||
receivable._compute_property_ids()
|
||
|
||
@api.onchange('penalty_date')
|
||
def _onchange_penalty_date(self):
|
||
for record in self:
|
||
record.write({
|
||
'is_reconciliation': True
|
||
})
|
||
|
||
def fill_penalty_ratio_value(self):
|
||
# 服务器动作调用,通过 active_ids 获取选中的记录集
|
||
records = self.env["property.accounts.receivable"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
# 如果 context 没有 active_ids(比如通过其他方式调用),回退到 self
|
||
if not records:
|
||
records = self
|
||
# 从关联合同 percentage_liquidated_damages.name 读取违约金比例并写入 penalty_ratio_value
|
||
updated = 0
|
||
for record in records:
|
||
if record.contract_id and record.contract_id.percentage_liquidated_damages:
|
||
record.penalty_ratio_value = float(record.contract_id.percentage_liquidated_damages.name)
|
||
updated += 1
|
||
# 调试日志:打印处理了多少条记录
|
||
_logger = logging.getLogger(__name__)
|
||
_logger.info('fill_penalty_ratio_value: 处理 %d 条记录,成功写入 %d 条', len(records), updated)
|
||
|
||
def all_penalty_date(self):
|
||
receivable_ids= self.env["property.accounts.receivable"].browse(self._context.get('active_ids', self._context.get('active_id')))
|
||
for receivable in receivable_ids:
|
||
if receivable.receivable_id.create_time and receivable.penalty_date:
|
||
create_time = receivable.receivable_id.create_time
|
||
dt = datetime.strptime(str(create_time), '%Y-%m-%d').date()
|
||
new_month = dt.month + 1
|
||
new_year = dt.year
|
||
if new_month > 12:
|
||
new_month = 1
|
||
new_year += 1
|
||
new_date = dt.replace(year=new_year, month=new_month, day=1)
|
||
penalty_date = datetime.strptime(str(receivable.penalty_date), '%Y-%m-%d').date()
|
||
if penalty_date.year == datetime.now().year - 1:
|
||
raise UserWarning("选择的日期为去年的年份,请重新选择!")
|
||
days_difference = (penalty_date - new_date).days
|
||
penalty = days_difference * receivable.receivable_id.difference_amount
|
||
receivable.receivable_id.write({'penalty': penalty})
|
||
|
||
# 批量退回
|
||
def action_refuse_all(self):
|
||
model_type_data = {
|
||
'水费': 'meter.water.line',
|
||
'电费': 'meter.archives.line',
|
||
'押金': 'property.deposit.list',
|
||
'租金': 'property.rent.list',
|
||
'管理费': 'property.management.list',
|
||
}
|
||
archives_ids = self.env["property.accounts.receivable"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
valid_management_types = list(model_type_data.values())
|
||
for rec in archives_ids:
|
||
if rec.is_reconciliation == True:
|
||
return {
|
||
"name": "批量退回提醒弹窗",
|
||
'type': 'ir.actions.act_window',
|
||
'res_model': 'action.refuse.wizard',
|
||
"view_mode": "form",
|
||
'target': 'new',
|
||
'context': {
|
||
'default_receivable_ids': archives_ids.ids,
|
||
}
|
||
}
|
||
model_type = model_type_data.get(rec.management_type_id.name)
|
||
if model_type not in valid_management_types:
|
||
rec.unlink()
|
||
continue
|
||
if model_type in ('meter.archives.line', 'meter.water.line'):
|
||
if not rec.charging_range:
|
||
raise UserError("请输入计费区间, 按照应为'YYYY-MM-DD 到 YYYY-MM-DD格式")
|
||
charging_range = rec.charging_range.strip()
|
||
start_str, end_str = [x.strip() for x in charging_range.split("到")]
|
||
start_date = (datetime.strptime(end_str, "%Y-%m-%d").date()).replace(day=1)
|
||
end_date = datetime.strptime(end_str, "%Y-%m-%d").date()
|
||
model_id = self.env[model_type].search([
|
||
('contract_id', '=', rec.contract_id.id),
|
||
('meter_date', '>=', start_date),
|
||
('meter_date', '<=', end_date)
|
||
])
|
||
else:
|
||
last_day = rec.due_date + relativedelta(months=1) - timedelta(days=1)
|
||
model_id = self.env[model_type].search([
|
||
('contract_id', '=', rec.contract_id.id),
|
||
('due_date', '>=', rec.due_date),
|
||
('due_date', '<=', last_day)
|
||
])
|
||
|
||
if model_id:
|
||
model_id.write({'is_push': False})
|
||
rec.unlink()
|
||
|
||
def button_accounts_wizard(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "操作弹窗",
|
||
"res_model": "accounts.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
"context": {
|
||
"default_accounts_id": self.id
|
||
}
|
||
}
|
||
|
||
|
||
# 租金
|
||
class PropertyRentList(models.Model):
|
||
_inherit = 'property.rent.list'
|
||
|
||
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
|
||
tenure_type = fields.Selection(related='contract_id.tenure_type', string="权属类型", store=True)
|
||
property_address = fields.Text(string="物业信息", compute='get_property_address')
|
||
contract_sort = fields.Selection([('property', '物业'), ('guanggao', '广告位'),('parking', '停车场'), ('guanzi', '管子间'), ('room', '机房'), ('base_station','基站'), ('other', '其他')], related='contract_id.contract_sort', string="资产类型", index=True)
|
||
company_id = fields.Many2one('yuthon.property.company', string="出租方公司", store=True, compute='_compute_company_id')
|
||
# 核对人:推送生成应收时写入当前登录人
|
||
verifier_id = fields.Many2one('res.users', string="核对人")
|
||
|
||
@api.depends("property_id", "due_date")
|
||
def _compute_admin_id(self):
|
||
for record in self:
|
||
record.admin_id = False
|
||
if not record.property_id or not record.due_date:
|
||
continue
|
||
manage_lines = record.property_id.manage_information_ids
|
||
if not manage_lines:
|
||
continue
|
||
valid_lines = self.env['manage.information.line']
|
||
target_date = record.due_date
|
||
for line in manage_lines:
|
||
start_valid = line.start_time <= target_date if line.start_time else True
|
||
end_valid = True
|
||
if line.end_date:
|
||
end_valid = target_date <= line.end_date
|
||
if start_valid and end_valid:
|
||
valid_lines += line
|
||
|
||
if valid_lines:
|
||
valid_lines = valid_lines.sorted(key=lambda l: l.start_time, reverse=True)
|
||
record.admin_id = valid_lines[0].admin_id.id
|
||
|
||
@api.depends('contract_id', 'due_date')
|
||
def _compute_company_id(self):
|
||
for record in self:
|
||
record.company_id = False
|
||
if not record.contract_id:
|
||
continue
|
||
lessor_lines = record.contract_id.lessor_line
|
||
valid_lessor_lines = lessor_lines.filtered(
|
||
lambda line:
|
||
line.start_date and
|
||
line.start_date <= record.due_date and
|
||
(not line.end_date or line.end_date >= record.due_date)
|
||
)
|
||
if valid_lessor_lines:
|
||
record.company_id = valid_lessor_lines[0].lessor_id
|
||
else:
|
||
record.company_id = record.contract_id.lessor_id or False
|
||
|
||
@api.depends('contract_id')
|
||
def get_property_address(self):
|
||
for rec in self:
|
||
rec.property_address = rec.contract_id.property_name
|
||
|
||
def clear_the_data(self):
|
||
archives_ids = self.env["property.rent.list"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
for rec in archives_ids:
|
||
first_day_of_month = rec.due_date.replace(day=1)
|
||
last_day_of_month = first_day_of_month + relativedelta(months=1) - timedelta(days=1)
|
||
existing_receivable = self.env['property.accounts.receivable'].search([
|
||
('lessee_id', '=', rec.lessee_id.id),
|
||
('due_date', '>=', first_day_of_month),
|
||
('due_date', '<=', last_day_of_month),
|
||
('contract_id', '=', rec.id),
|
||
], limit=1)
|
||
if existing_receivable:
|
||
rec.unlink()
|
||
|
||
def push_rent(self):
|
||
"""推送租金应收到 property.accounts.receivable
|
||
如果合同勾选了 is_parking_fee,则应收类型为"停车费"而非"租金"
|
||
"""
|
||
current_user = self.env.user
|
||
is_admin = current_user.has_group('base.group_system') or current_user.has_group('base.group_admin')
|
||
for rec in self:
|
||
if not is_admin and rec.admin_id.id != current_user.id:
|
||
raise UserError(f"您只能推送自己记录")
|
||
# 根据合同的 is_parking_fee 标志决定应收类型
|
||
if rec.contract_id and rec.contract_id.is_parking_fee:
|
||
type_name = '停车费'
|
||
else:
|
||
type_name = '租金'
|
||
management_type = self.env['yuthon.management.type'].search([('name', '=', type_name)], limit=1)
|
||
if not management_type:
|
||
management_type = self.env['yuthon.management.type'].create({'name': type_name})
|
||
due_date = rec.due_date
|
||
due_date_end = False
|
||
theoretical_end = False
|
||
contract = rec.contract_id
|
||
if contract and contract.cycle_mode and due_date:
|
||
due_date_obj = fields.Date.from_string(due_date)
|
||
contract_actual_end = fields.Date.from_string(
|
||
contract.actual_end_time) if contract.actual_end_time else None
|
||
# 处理自然月
|
||
if contract.cycle_mode == 'floating_monthly':
|
||
breakdown_line = self.env['property.rent.breakdown.line'].search([
|
||
('breakdown_id', '=', contract.id),
|
||
('start_data', '<=', due_date),
|
||
('end_data', '>=', due_date)
|
||
], limit=1)
|
||
if breakdown_line:
|
||
start_data_obj = fields.Date.from_string(breakdown_line.start_data)
|
||
if not (due_date_obj.year == start_data_obj.year and
|
||
due_date_obj.month == start_data_obj.month):
|
||
due_date = fields.Date.to_string(due_date_obj.replace(day=1))
|
||
due_date_obj = fields.Date.from_string(due_date)
|
||
|
||
last_day = calendar.monthrange(due_date_obj.year, due_date_obj.month)[1]
|
||
theoretical_end = date(due_date_obj.year, due_date_obj.month, last_day)
|
||
else:
|
||
# 处理合同月 (统一先按合同月处理)
|
||
# 计算理论应收结束日(当月最后一天)
|
||
next_month_first = (due_date_obj + relativedelta(months=1))
|
||
theoretical_end = next_month_first - timedelta(days=1)
|
||
|
||
if contract_actual_end:
|
||
due_date_end = fields.Date.to_string(min(theoretical_end, contract_actual_end))
|
||
else:
|
||
due_date_end = fields.Date.to_string(theoretical_end)
|
||
|
||
# 创建应收记录
|
||
accounts_id = self.env['property.accounts.receivable'].create({
|
||
'contract_id': contract.id if contract else False,
|
||
'lessee_id': contract.lessee_id.id if contract and contract.lessee_id else None,
|
||
'original_amount': rec.original_amount,
|
||
'is_liquidated_damages': rec.is_liquidated_damages,
|
||
'penalty_ratio_value': rec.percentage_damages,
|
||
'due_date': due_date,
|
||
'acc_type': rec.acc_type,
|
||
'charging_range': f"{due_date} 到 {due_date_end}",
|
||
'due_date_end': due_date_end,
|
||
'create_time': datetime.now(),
|
||
'management_type_id': management_type.id,
|
||
})
|
||
rec.is_push = True
|
||
rec.verifier_id = self.env.user.id # 核对人 = 当前登录人
|
||
accounts_id._compute_precinct_id()
|
||
accounts_id._compute_lessee_id()
|
||
self.reconcile_with_bank_statements(rec)
|
||
|
||
def batch_push_rent(self):
|
||
archives_ids = self.env["property.rent.list"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
for rec in archives_ids:
|
||
rec.push_rent()
|
||
|
||
def reconcile_with_bank_statements(self, rec):
|
||
bank_accounts = self.env['property.bank.line'].search([
|
||
('tenant_id', '=', rec.contract_id.lessee_id.id)
|
||
])
|
||
if not bank_accounts:
|
||
return
|
||
|
||
if not rec.property_id:
|
||
return
|
||
|
||
# 构建银行对账单搜索条件
|
||
bank_domain = [
|
||
('name', 'in', [account.bank for account in bank_accounts]),
|
||
]
|
||
# 查找匹配的银行对账单
|
||
matching_statements = self.env['property.bank.statements'].search(bank_domain)
|
||
|
||
# 更新银行对账单,关联合同和物业
|
||
for statement in matching_statements:
|
||
# 添加合同ID(如果不存在)
|
||
if rec.contract_id.id not in statement.contract_ids.ids:
|
||
statement.write({
|
||
'contract_ids': [(4, rec.contract_id.id)], # 添加关系
|
||
})
|
||
# 添加物业ID(如果不存在)
|
||
if rec.property_id.id not in statement.property_ids.ids:
|
||
statement.write({
|
||
'property_ids': [(4, rec.property_id.id)], # 添加关系
|
||
})
|
||
# 押金
|
||
class PropertyDepositList(models.Model):
|
||
_inherit = 'property.deposit.list'
|
||
|
||
contract_id = fields.Many2one('property.lease.contract', string="合同")
|
||
due_date = fields.Date(related='contract_id.actual_start_time', string='应收日期', store=True)
|
||
posit_type = fields.Selection([('deposit', '押金'), ('performance_bonds', '经营履约保证金'),
|
||
('utility_bills', '水电费'), ('decorate', '装修')], string="保证金类型")
|
||
original_amount = fields.Float(string="金额")
|
||
tenure_type = fields.Selection(related='contract_id.tenure_type', string="权属类型", store=True)
|
||
company_id = fields.Many2one(related='contract_id.lessor_id', string="出租方公司", store=True)
|
||
# 核对人:推送生成应收时写入当前登录人
|
||
verifier_id = fields.Many2one('res.users', string="核对人")
|
||
|
||
def push_deposit(self):
|
||
for rec in self:
|
||
management_type_id = self.env['yuthon.management.type'].search([('name', '=', '押金')], limit=1)
|
||
if not management_type_id:
|
||
management_type_id = self.env['yuthon.management.type'].create({'name': '押金'})
|
||
accounts_id = self.env['property.accounts.receivable'].create({
|
||
'contract_id': rec.contract_id.id,
|
||
'due_date' :rec.due_date + relativedelta(months=1),
|
||
'due_date_end': None,
|
||
'lessee_id':rec.contract_id.lessee_id.id if rec.contract_id.lessee_id else None,
|
||
'original_amount' : rec.original_amount,
|
||
'create_time': datetime.now(),
|
||
'management_type_id':management_type_id.id,
|
||
'charging_range': f"{rec.due_date.strftime('%Y-%m-%d')} 到 {(rec.due_date + relativedelta(months=1)).strftime('%Y-%m-%d')}",
|
||
})
|
||
accounts_id._compute_lessee_id()
|
||
rec.is_push = True
|
||
rec.verifier_id = self.env.user.id # 核对人 = 当前登录人
|
||
|
||
def batch_push_deposit(self):
|
||
archives_ids = self.env["property.deposit.list"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
management_type_id = self.env['yuthon.management.type'].search([('name', '=', '押金')], limit=1)
|
||
for rec in archives_ids:
|
||
self.env['property.accounts.receivable'].create({
|
||
'contract_id': rec.contract_id.id,
|
||
'due_date': rec.due_date,
|
||
'lessee_id': rec.contract_id.lessee_id.id if rec.contract_id.lessee_id else None,
|
||
'original_amount': rec.original_amount,
|
||
'create_time': datetime.now(),
|
||
'management_type_id': management_type_id.id,
|
||
})
|
||
rec.is_push = True
|
||
rec.verifier_id = self.env.user.id # 核对人 = 当前登录人
|
||
|
||
def delete_duplicate_deposit(self):
|
||
"""删除重复押金:同一合同 + 同一保证金类型只保留最早一条,删除其余"""
|
||
deposits = self.env['property.deposit.list'].search([
|
||
('contract_id', '!=', False),
|
||
('posit_type', '!=', False),
|
||
])
|
||
seen = {}
|
||
to_delete = self.env['property.deposit.list']
|
||
for dep in deposits.sorted(key=lambda d: (d.contract_id.id, d.posit_type, d.id)):
|
||
key = (dep.contract_id.id, dep.posit_type)
|
||
if key in seen:
|
||
to_delete |= dep
|
||
else:
|
||
seen[key] = dep.id
|
||
count = len(to_delete)
|
||
if to_delete:
|
||
to_delete.unlink()
|
||
return {
|
||
'type': 'ir.actions.client',
|
||
'tag': 'display_notification',
|
||
'params': {
|
||
'title': '删除重复押金',
|
||
'message': f'已删除 {count} 条重复押金记录' if count else '没有重复的押金记录',
|
||
'type': 'success' if count else 'info',
|
||
'sticky': False,
|
||
},
|
||
}
|
||
|
||
# 管理费
|
||
class PropertyManagementList(models.Model):
|
||
_inherit = 'property.management.list'
|
||
|
||
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
|
||
property_name = fields.Text(string='物业', compute="_compute_property_name")
|
||
company_id = fields.Many2one(related='contract_id.lessor_id', string="出租方公司", store=True)
|
||
tenure_type = fields.Selection(related='contract_id.tenure_type', string="权属类型", store=True)
|
||
# 核对人:推送生成应收时写入当前登录人
|
||
verifier_id = fields.Many2one('res.users', string="核对人")
|
||
|
||
@api.depends('contract_id')
|
||
def _compute_property_name(self):
|
||
for record in self:
|
||
record.property_name = record.contract_id.property_name
|
||
|
||
def push_management(self):
|
||
type_name_map = {
|
||
'management': '管理费',
|
||
'guanzijian': '管子间',
|
||
'lightbox': '灯箱'
|
||
}
|
||
for rec in self:
|
||
target_name = type_name_map.get(rec.type)
|
||
management_type_id = self.env['yuthon.management.type'].search(
|
||
[('name', '=', target_name)],
|
||
limit=1
|
||
)
|
||
if not management_type_id:
|
||
management_type_id = self.env['yuthon.management.type'].create({'name': target_name})
|
||
|
||
due_date = rec.due_date
|
||
due_date_end = False
|
||
theoretical_end = False
|
||
contract = rec.contract_id
|
||
if contract and contract.cycle_mode and due_date:
|
||
due_date_obj = fields.Date.from_string(due_date)
|
||
contract_actual_end = fields.Date.from_string(
|
||
contract.actual_end_time) if contract.actual_end_time else None
|
||
# 处理合同月
|
||
if contract.cycle_mode == 'fixed_monthly':
|
||
# 计算理论应收结束日(当月最后一天)
|
||
next_month_first = (due_date_obj + relativedelta(months=1))
|
||
theoretical_end = next_month_first - timedelta(days=1)
|
||
# 处理自然月
|
||
elif contract.cycle_mode == 'floating_monthly':
|
||
breakdown_line = self.env['property.rent.breakdown.line'].search([
|
||
('breakdown_id', '=', contract.id),
|
||
('start_data', '<=', due_date),
|
||
('end_data', '>=', due_date)
|
||
], limit=1)
|
||
if breakdown_line:
|
||
start_data_obj = fields.Date.from_string(breakdown_line.start_data)
|
||
if not (due_date_obj.year == start_data_obj.year and
|
||
due_date_obj.month == start_data_obj.month):
|
||
due_date = fields.Date.to_string(due_date_obj.replace(day=1))
|
||
due_date_obj = fields.Date.from_string(due_date)
|
||
|
||
last_day = calendar.monthrange(due_date_obj.year, due_date_obj.month)[1]
|
||
theoretical_end = date(due_date_obj.year, due_date_obj.month, last_day)
|
||
|
||
if contract_actual_end:
|
||
if theoretical_end:
|
||
due_date_end = fields.Date.to_string(min(theoretical_end, contract_actual_end))
|
||
else:
|
||
due_date_end = fields.Date.to_string(contract_actual_end)
|
||
else:
|
||
due_date_end = fields.Date.to_string(theoretical_end)
|
||
|
||
accounts_id = self.env['property.accounts.receivable'].create({
|
||
'contract_id': rec.contract_id.id,
|
||
'lessee_id': rec.contract_id.lessee_id.id if rec.contract_id.lessee_id else None,
|
||
'original_amount': rec.original_amount,
|
||
'due_date': rec.due_date,
|
||
'due_date_end': due_date_end,
|
||
'charging_range': f"{due_date} 到 {due_date_end}",
|
||
'create_time': datetime.now(),
|
||
'management_type_id': management_type_id.id,
|
||
})
|
||
accounts_id._compute_lessee_id()
|
||
rec.is_push = True
|
||
rec.verifier_id = self.env.user.id # 核对人 = 当前登录人
|
||
|
||
def batch_push_management(self):
|
||
archives_ids = self.env["property.management.list"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
management_type_id = self.env['yuthon.management.type'].search([('name', '=', '电费')], limit=1)
|
||
for rec in archives_ids:
|
||
self.env['property.accounts.receivable'].create({
|
||
'contract_id': rec.contract_id.id,
|
||
'lessee_id': rec.contract_id.lessee_id.id if rec.contract_id.lessee_id else None,
|
||
'original_amount': rec.original_amount,
|
||
'create_time': datetime.now(),
|
||
'management_type_id': management_type_id.id,
|
||
})
|
||
rec.is_push = True
|
||
rec.verifier_id = self.env.user.id # 核对人 = 当前登录人
|
||
|
||
# 电费
|
||
class PropertyTariffList(models.Model):
|
||
_inherit = 'property.tariff.list'
|
||
|
||
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
|
||
admin_id = fields.Many2one(related='contract_id.admin_id', string="片区管理员", store=True)
|
||
property_name = fields.Char(related='contract_id.property_name', string="物业信息", store=True)
|
||
company_id = fields.Many2one(related='contract_id.lessor_id', string="出租方公司", store=True)
|
||
tenure_type = fields.Selection(related='contract_id.tenure_type', string="权属类型", store=True)
|
||
|
||
def push_tariff(self):
|
||
for rec in self:
|
||
management_type_id = self.env['yuthon.management.type'].search([('name', '=', '电费')], limit=1)
|
||
if not management_type_id:
|
||
management_type_id = self.env['yuthon.management.type'].create({'name': '电费'})
|
||
self.env['property.accounts.receivable'].create({
|
||
'contract_id': rec.contract_id.id,
|
||
'lessee_id':rec.contract_id.lessee_id.id if rec.contract_id.lessee_id else None,
|
||
'original_amount' : rec.original_amount,
|
||
'due_date': rec.due_date,
|
||
'create_time': datetime.now(),
|
||
'management_type_id':management_type_id.id,
|
||
})
|
||
rec.is_push = True
|
||
|
||
def batch_push_tariff(self):
|
||
archives_ids = self.env["property.tariff.list"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
management_type_id = self.env['yuthon.management.type'].search([('name', '=', '电费')], limit=1)
|
||
for rec in archives_ids:
|
||
self.env['property.accounts.receivable'].create({
|
||
'contract_id': rec.contract_id.id,
|
||
'lessee_id': rec.contract_id.lessee_id.id if rec.contract_id.lessee_id else None,
|
||
'original_amount': rec.original_amount,
|
||
'due_date': rec.due_date,
|
||
'create_time': datetime.now(),
|
||
'management_type_id': management_type_id.id,
|
||
})
|
||
rec.is_push = True
|
||
|
||
# 水费
|
||
class PropertyWaterList(models.Model):
|
||
_inherit = 'property.water.list'
|
||
|
||
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
|
||
admin_id = fields.Many2one(related='contract_id.admin_id', string="片区管理员", store=True)
|
||
property_name = fields.Char(related='contract_id.property_name', string="物业信息")
|
||
company_id = fields.Many2one(related='contract_id.lessor_id', string="出租方公司", store=True)
|
||
tenure_type = fields.Selection(related='contract_id.tenure_type', string="权属类型", store=True)
|
||
|
||
def push_water(self):
|
||
for rec in self:
|
||
management_type_id = self.env['yuthon.management.type'].search([('name', '=', '水费')], limit=1)
|
||
if not management_type_id:
|
||
management_type_id = self.env['yuthon.management.type'].create({'name': '水费'})
|
||
self.env['property.accounts.receivable'].create({
|
||
'contract_id': rec.contract_id.id,
|
||
'lessee_id': rec.contract_id.lessee_id.id if rec.contract_id.lessee_id else None,
|
||
'original_amount': rec.original_amount,
|
||
'create_time': datetime.now(),
|
||
'management_type_id': management_type_id.id,
|
||
})
|
||
rec.is_push = True
|
||
|
||
def batch_push_water(self):
|
||
archives_ids = self.env["property.water.list"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
management_type_id = self.env['yuthon.management.type'].search([('name', '=', '水费')], limit=1)
|
||
for rec in archives_ids:
|
||
self.env['property.accounts.receivable'].create({
|
||
'contract_id': rec.contract_id.id,
|
||
'lessee_id': rec.contract_id.lessee_id.id if rec.contract_id.lessee_id else None,
|
||
'original_amount': rec.original_amount,
|
||
'create_time': datetime.now(),
|
||
'management_type_id': management_type_id.id,
|
||
})
|
||
rec.is_push = True
|
||
|
||
class PropertyPenaltyList(models.Model):
|
||
_inherit = 'property.penalty.list'
|
||
|
||
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
|
||
|