Files
gzth/yuthon_property_approval/models/property_lease_contract.py
T
2026-06-24 18:19:03 +08:00

2879 lines
140 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import pandas as pd
import calendar
from dateutil.relativedelta import relativedelta
from datetime import datetime, timedelta, date
from odoo.tools.float_utils import float_round
from numpy.ma.core import default_filler
import re
from odoo import models, fields, api, exceptions, _
from odoo.exceptions import UserError, ValidationError
class PropertyContractLine(models.Model):
_name = 'property.contract.line'
_description = '物业合同明细行'
yuthon_property_id = fields.Many2one('yuthon.property', string="关联物业信息")
lease_contract_id = fields.Many2one('property.lease.contract', string="合同编号")
lessee_id = fields.Many2one('property.tenant.information', string='承租方')
rent = fields.Float(string="租金")
start_time = fields.Date(string="生效日期")
end_date = fields.Date(string="失效日期")
def open_record(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': '物业合同',
'view_mode': 'form',
'res_model': 'property.lease.contract',
"res_id": self.lease_contract_id.id,
}
@api.onchange('start_time', 'end_date', 'yuthon_property_id', 'lease_contract_id')
def _onchange_contract_dates(self):
# 1. 若未关联物业,直接返回
if not self.yuthon_property_id:
return
all_contract_lines = self.env['property.contract.line'].search([
('yuthon_property_id', '=', self.yuthon_property_id.id.origin)
])
if not all_contract_lines:
return
for contract_line in all_contract_lines:
if not all([contract_line.lease_contract_id, contract_line.start_time]):
continue
date_domain = [('meter_date', '>=', contract_line.start_time)]
if contract_line.end_date:
date_domain.append(('meter_date', '<=', contract_line.end_date))
domain = [
('yuthon_property_ids', 'in', [self.yuthon_property_id.id.origin]),
*date_domain
]
meter_models = [self.env["meter.archives.line"], self.env["meter.water.line"]]
for model in meter_models:
meter_lines = model.search(domain)
if meter_lines:
meter_lines.write({
'contract_id': contract_line.lease_contract_id.id,
})
class PropertyTenantLine(models.Model):
_name = 'property.tenant.line'
_description = '物业承租方合同明细行'
tenant_id = fields.Many2one('property.tenant.information', string="关联租户信息")
property_ids = fields.Many2many('yuthon.property', string="关联物业信息") # 数据库字段缺失 无实际意义
property_name = fields.Char(related='lease_contract_id.property_name', string="物业信息")
lease_contract_id = fields.Many2one('property.lease.contract', string="合同编号")
rent = fields.Float(related='lease_contract_id.rent', string="租金")
start_time = fields.Date(related='lease_contract_id.actual_start_time', string="生效日期")
end_date = fields.Date(related='lease_contract_id.actual_end_time',string="失效日期")
@api.model
def create(self, vals):
"""在创建记录时自动关联合同对应的物业"""
if vals.get('lease_contract_id'):
contract = self.env['property.lease.contract'].browse(vals['lease_contract_id'])
property_ids = contract.lease_ids.mapped('address_id.id')
if property_ids:
vals['property_ids'] = [(6, 0, property_ids)]
res = super(PropertyTenantLine, self).create(vals)
self.env.flush_all()
res._refresh_bank_stmt_by_tenant()
return res
def write(self, vals):
res = super().write(vals)
self.env.flush_all()
if 'tenant_id' in vals:
self._refresh_bank_stmt_by_tenant()
return res
def unlink(self):
tenants_to_refresh = self.mapped('tenant_id')
res = super().unlink()
if tenants_to_refresh:
self._refresh_bank_stmt_by_tenants(tenants_to_refresh)
return res
def _refresh_bank_stmt_by_tenant(self):
"""当 property.tenant.line 的 tenant_id 变更时,刷新关联的银行流水计算字段"""
tenant_list = self.mapped('tenant_id')
self._refresh_bank_stmt_by_tenants(tenant_list)
def _refresh_bank_stmt_by_tenants(self, tenant_list):
"""根据承租方列表,找到关联的银行流水并触发计算字段重算"""
if not tenant_list:
return
bank_lines = self.env['property.bank.line'].search([('tenant_id', 'in', tenant_list.ids)])
bank_no_list = bank_lines.mapped('bank')
if not bank_no_list:
return
stmt_recs = self.env['property.bank.statements'].search([('name', 'in', bank_no_list)])
if stmt_recs:
for stmt in stmt_recs:
stmt._compute_property_employee_id()
def _constrain_tenant_refresh_bank(self):
"""@deprecated: 请使用 _refresh_bank_stmt_by_tenant"""
self._refresh_bank_stmt_by_tenant()
class PropertyLeaseContract(models.Model):
_name = 'property.lease.contract'
_description = '租赁合同审批'
_inherit = ['mail.thread.main.attachment', 'mail.activity.mixin', 'soong.workflow.mixin']
_rec_name = 'contract_code'
property_name = fields.Char(string='物业信息')
confirm_id = fields.Many2one('property.confirm', string="关联成交确认审批")
version = fields.Char(string="合同版本", default="V1.0")
code = fields.Char(string="流水号", default=lambda self: self.env['ir.sequence'].next_by_code('property_lease_contract_code'))
admin_id = fields.Many2one('res.users', string="管理员", compute="_compute_admin_id", store=True)
tenure_type = fields.Selection([('self', '自有'), ('agency', '代管')], string='权属类型', compute="_compute_admin_id", store=True)
contract_code = fields.Char(string="合同编号")
is_chapter = fields.Selection([('yes', '是'), ('no', '否')], string="是否用章")
type_stamp_ids = fields.Many2many('seals.type.setting', string='用章类型')
scope_chapter_ids = fields.Many2many('seals.scope.setting', string='用章范围')
contract_date = fields.Date(string="合同签订日期")
is_print = fields.Boolean(string="当月打印", compute ="_compute_is_print", store=True)
property_documents_ids = fields.Many2many(
'ir.attachment',
relation='property_lease_contract_documents_rel',
column1='property_lease_contract_id',
column2='attachment_id',
string="租赁合同文件"
)
title_address = fields.Char(string="产权地址")
receivable_ids = fields.One2many('property.accounts.receivable', 'contract_id', string="应收账单")
receivable_report_ids = fields.Many2many('property.accounts.receivable', string="应收账单(报表)")
print_date = fields.Date(string="打印日期")
total_amount = fields.Float(string="合计金额")
total_amount_caps = fields.Char(string="合计金额大写")
payment_ids = fields.One2many('property.payment.notice', 'contract_id', string="合同打印")
handling_opinions = fields.Text(string="经办意见")
lessor_id = fields.Many2one('yuthon.property.company', string="出租方公司")
rent_company = fields.Char(related='lessor_id.name', string="收租公司名称")
rent_account = fields.Char(related='lessor_id.rent_account', string="收租公司账号")
users_id = fields.Many2one('res.users', string="申请人", default=lambda self: self.env.user)
department_id = fields.Many2one('hr.department', string="部门", default= lambda self: self.env.user.department_id)
company_id = fields.Many2one('res.company', string="转交公司", stroe=True, compute = "_compute_company_id")
# 承租方的个人信息
lessee_id = fields.Many2one('property.tenant.information', string='承租方')
lessee_name = fields.Char(related='lessee_id.name', string="承租方姓名")
contact = fields.Char(related='lessee_id.contact', string="联系人")
phone = fields.Char(related='lessee_id.phone', string="联系电话")
email = fields.Char(related='lessee_id.email', string="电子邮箱")
card = fields.Char(related='lessee_id.card', string="身份证号")
invoicing_type = fields.Selection([('spot', '专票'), ('cape', '普票')], related='lessee_id.invoicing_type', string="开票类型")
rental_purposes = fields.Many2one('yuthon.rental.purpose', string="出租用途")
invoicing_begins = fields.Char(related='lessee_id.invoicing_begins', string="开票抬头")
tax_number = fields.Char(related='lessee_id.tax_number', string="税号")
industry_id = fields.Many2one(related='lessee_id.industry_id', string="行业")
address_for_delivery = fields.Char(string="送达地址")
precinct_id = fields.Many2one('yuthon.precinct.message', string="所属片区", compute='_compute_precinct_id')
active = fields.Boolean('Active', default=True, tracking=True)
lease_term_year = fields.Integer(string="租期(年)")
lease_term_month = fields.Integer(string="租期(月)")
lease_term_day = fields.Integer(string="租期(日)")
is_less_than_2 = fields.Boolean(string="是否小于2年")
term_start_time = fields.Date(string="合约开始时间", default=fields.Datetime.now)
term_end_time = fields.Date(string="合约结束时间")
actual_start_time = fields.Date(string="实际开始时间")
actual_end_time = fields.Date(string="实际结束时间")
end_comparison = fields.Selection([('red', '红'), ('yellow', '黄'), ('blue', '蓝')], string="结束时间对比(颜色)", compute='_compute_end_comparison', store=True)
expiration_coming = fields.Selection([('coming', '即将到期'), ('due', '已到期')], string="结束时间对比(状态)", compute='_compute_expiration_coming', store=True)
is_equal = fields.Boolean(string="是否相等", compute='_compute_end_comparison', store=True)
is_has = fields.Boolean(string='已退押金', compute="compute_is_has")
is_has_generated = fields.Boolean()
cycle_dict = fields.Selection([('monthly', '月度'), ('quarter', '季度'), ('half', '半年'),('year', '年度')],string='付款方式', default='monthly', help="应收")
cycle_mode = fields.Selection([('fixed_monthly', '合同月'), ('floating_monthly', '自然月'), ('fixed_quarter', '合同季'), ('floating_quarter', '自然季'),
('fixed_year', '合同年'), ('floating_year', '自然年'), ('is_half_year', '合同半年')], string='周期方式', default='fixed_monthly')
expected_reminders = fields.Char(string="逾期提醒", default=25)
increment_method = fields.Selection([('percentage', '百分比'),('increment_price', '递增单价'),('increment_amount', '递增金额')], string='每月递增', default='percentage')
increment_percentage = fields.Float(string="递增百分比")
increment_price = fields.Float(string="递增单价")
increment_amount = fields.Float(string="递增金额")
increment_value = fields.Float(string="递增值")
first_years_begin = fields.Integer(string="第几年开始", default=2)
increments_every_few_years = fields.Integer(string="每几年递增")
rent_free_period = fields.Datetime(string="免租期")
rent_free_period_month = fields.Integer(string="免租期")
rent_free_period_days = fields.Integer(string="免租期")
breach = fields.Integer(string="违约计算时点", default=1)
is_split = fields.Boolean(string="是否合同拆分")
def _get_percentage_liquidated_damages(self):
default_rate = self.env['yuthon.penalty.setting'].search(
[('name', '=', 0.001)], limit=1
)
return default_rate.id if default_rate else False
percentage_liquidated_damages = fields.Many2one('yuthon.penalty.setting', string="违约金比例", default=_get_percentage_liquidated_damages)
percentage_damages= fields.Float(string="违约金", compute="_compute_contract_count", store=True)
date_of_payment = fields.Selection([('five', '5'), ('fifteen', '15'), ('twenty', '20'), ('twentyfive', '25')], string="支付日期")
first_pay = fields.Date(string="首次付款日")
unit_price = fields.Float(string="单价", store=True, digits=(16, 4))
deposit_type = fields.Selection([('deposit', '押金'), ('performance', '履约保证金')], string="押金/履约保证金")
deposit_status = fields.Selection([('unpaid', '未支付'), ('paid', '已支付'), ('returned','已退'),('no_paid', '没收')], string='押金状态')
deposit = fields.Float(string="押金/履约保证金", store=True)
performance_bonds_write = fields.Char(string="履约保证金大写")
first_year_rent = fields.Float(string="首年租金")
rent = fields.Float(string="租金", help='第一笔合同的应收')
total_contract_receivables = fields.Float(string="合同总应收", compute="_compute_total_contract_receivables")
lessor_line = fields.One2many('lessor.alteration.line', 'contract_id', string="出租变更明细")
lessee_line = fields.One2many('lessee.alteration.line', 'contract_id', string="承租变更明细")
is_lessor = fields.Boolean(string="出租方变更")
is_lessee = fields.Boolean(string="承租方变更")
def _get_default_tax_rate(self):
default_rate = self.env['yuthon.tariff.setting'].search(
[('name', '=', 0.05)], limit=1
)
return default_rate.id if default_rate else False
tax_rate = fields.Many2one('yuthon.tariff.setting', string="税率", default=_get_default_tax_rate)
use_address = fields.Char(string="财务专用地址")
not_tax_amount = fields.Float(string="不含税价")
tax = fields.Float(string="税金")
is_water = fields.Boolean(string="是否有水费")
is_electricity = fields.Boolean(string="是否有电费")
is_manage = fields.Boolean(string="是否有管理费")
miscellaneous = fields.Text(string="其他约定")
number = fields.Integer(string="次数", default=0)
total_area = fields.Float(string="总面积", digits=(16, 4))
not_received = fields.Float(string="未收", compute="_compute_contract_count")
month_received = fields.Float(string="本月已收")
actual_receivable = fields.Float(string="实际应收")
now_receivable = fields.Float(string="当期应收")
total_receivable = fields.Float(string="总已收")
comparison = fields.Selection([('outweigh', '大于'), ('equal', '等于'), ('less', '小于')], string="对比", compute='_compute_comparison')
progress_percentage = fields.Float(string="合同进度", compute="_compute_progress_percentage")
condition = fields.Selection([('new', '新建'), ('approval', '审批中'), ('cancel', '作废'), ('be_executed', '待执行'), ('executed', '已执行'), ('expired', '已终止')], string="合同审批状态", default='new')
contract_type = fields.Selection([('lease_renewal', '续租'), ('interim', '临租'), ('new', '补充协议'), ('first', '首租')], string="合同类型", default='first')
contract_id = fields.Many2one('property.lease.contract', string="原合同")
performance_bonds = fields.Float(string="经营履约保证金")
utility_bills = fields.Float(string="水电费押金")
renovation_deposit = fields.Float(string="装修押金")
lease_ids = fields.One2many('property.lease.contract.line', 'lease_id',string="物业信息明细行")
subset_ids = fields.One2many('property.subset.contract', 'subset_id',string="子集明细")
breakdown_ids = fields.One2many('property.rent.breakdown.line', 'breakdown_id', string="物业租金明细行")
water_breakdown_ids = fields.One2many('property.water.breakdown.line', 'breakdown_id', string="物业水费明细行")
ele_breakdown_ids = fields.One2many('property.ele.breakdown.line', 'breakdown_id', string="物业电费明细行")
management_breakdown_ids = fields.One2many('property.management.breakdown.line', 'breakdown_id', string="物业管理费明细行")
water_fee_ids = fields.One2many('property.water.fee.line', 'contract_id', string="物业水表明细行")
electricity_fee_ids = fields.One2many('property.electric.fee.line', 'contract_id', string="物业电表明细行")
other_ids = fields.One2many('property.other.fee.line', 'other_id', string="其他费用明细行")
lease_bank_ids = fields.One2many('property.bank.line', 'contract_id', string="承租方账户明细行")
bank_statements_ids = fields.One2many('receivable.modify.line', 'contract_id', string="银行流水明细行")
contact_state = fields.Selection([('to', '待执行' ), ('ing', '执行中'), ('terminate', '中止'), ('complete', '完成'), ('early_termination', '提前终止'), ('settle', '结清')], string='合同管理状态', default='to')
# 附件
license_ids = fields.Many2many('ir.attachment',
relation='property_lease_contract_license_rel',
column1='contract_id',
column2='attachment_id',
string="证照")
power_attorney_ids = fields.Many2many(
'ir.attachment',
relation='property_lease_contract_power_attorney_rel',
column1='contract_id',
column2='attachment_id',
string="授权委托书"
)
asset_valuation_ids = fields.Many2many(
'ir.attachment',
relation='property_lease_contract_asset_valuation_rel',
column1='contract_id',
column2='attachment_id',
string="资产评估结果明细表"
)
lease_contract = fields.Many2many(
'ir.attachment',
relation='property_lease_contract_lease_contract_rel',
column1='contract_id',
column2='attachment_id',
string="合同/协议"
)
confirmation_transaction_ids = fields.Many2many(
'ir.attachment',
relation='property_lease_contract_confirmation_transaction_rel',
column1='contract_id',
column2='attachment_id',
string="成交确认书"
)
contract_signature_ids = fields.Many2many(
'ir.attachment',
relation='property_lease_contract_signature_rel',
column1='contract_id',
column2='attachment_id',
string="合同签名附件"
)
responsibility_book_ids = fields.Many2many(
'ir.attachment',
relation='property_lease_contract_responsibility_book_rel',
column1='contract_id',
column2='attachment_id',
string="安全生产责任书(住宅)"
)
other_ment_ids = fields.Many2many(
'ir.attachment',
relation='property_lease_contract_other_rel',
column1='contract_id',
column2='attachment_id',
string="其他"
)
handover_time = fields.Date(string="交接日期")
contract_source_id = fields.Many2one('property.lease.contract', string="合同来源")
is_child_contract = fields.Boolean(string="是否子合同")
is_contract = fields.Boolean(string="是否主合同")
urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度',
default='normal')
work_end = fields.Boolean(string="结束")
name = fields.Char(compute='_compute_name', store=True)
self_users_ids = fields.Many2many('res.users', string="当前经办人", default=lambda self: [(6, 0, [self.env.user.id])])
title_information = fields.Text(string="产权证信息", compute='_compute_title_information')
contract_count = fields.Integer(string="联系人数量", compute='_compute_contract_count', store=True)
reconciliation_count = fields.Integer(string="对账数量", compute='_compute_reconciliation_count')
process_count = fields.Integer(string="流程数量", compute='_compute_process_count')
is_overdue = fields.Boolean(string="是否逾期", compute='_compute_contract_count', store=True)
is_issues = fields.Boolean(string='是否上会')
issues_result = fields.Html(string="会议结果")
related_issues_id = fields.Many2one('issue.library', string="关联议题")
# 催费通知单字段
fees_date = fields.Date(string="催费日期", compute='_compute_fees_all', store=True)
fees_rent = fields.Float(string="租金", compute='_compute_fees_all', store=True)
fees_water = fields.Float(string="水费", compute='_compute_fees_all', store=True)
fees_electricity = fields.Float(string="电费", compute='_compute_fees_all', store=True)
fees_manage = fields.Float(string="管理费", compute='_compute_fees_all', store=True)
fees_expenses = fields.Float(string="杂项费", compute='_compute_fees_all', store=True)
fees_bollards = fields.Float(string="灯箱费", compute='_compute_fees_all', store=True)
betweene_tubes = fields.Float(string="管子间", compute='_compute_fees_all', store=True)
fees_total = fields.Float(string="合计", compute='_compute_fees_all', store=True)
employee_ids = fields.Many2many('hr.employee', string="当前经办人", compute='_compute_workflow')
approve_state = fields.Selection([("start", "草稿"), ("pending", "审批中"), ("end", "结束")], string="审批状态", compute='_compute_workflow')
def refund_wizard(self):
contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', [])
)
line_vals = []
for contract in contract_ids:
line_vals.append((0, 0, {
'contract_id': contract.id,
}))
payment_unit_ids = []
for contract in contract_ids:
if contract.lessor_id:
payment_unit_ids.append(contract.lessor_id.id)
wizard = self.env['refund.wizard'].create({
'refund_line_ids': line_vals,
'payment_unit_ids': [(6, 0, payment_unit_ids)],
})
return {
'type': 'ir.actions.act_window',
'name': '批量退押',
'res_model': 'refund.wizard',
'view_mode': 'form',
'res_id': wizard.id,
'target': 'new',
}
def update_lessor_line(self):
contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for contract in contract_ids:
if not contract.lease_ids:
continue
first_lease_line = contract.lease_ids[0]
address_id = first_lease_line.address_id
manage_lines = self.env['manage.information.line'].search([
('yuthon_property_id', '=', address_id.id)
])
for manage_line in manage_lines:
# 跳过公司相同的情况
if manage_line.company_id == contract.lessor_id:
continue
existing_line = self.env['lessor.alteration.line'].search([
('contract_id', '=', contract.id),
('lessor_id', '=', manage_line.company_id.id),
], limit=1)
if not existing_line:
self.env['lessor.alteration.line'].create({
'contract_id': contract.id,
'lessor_id': manage_line.company_id.id,
'start_date': contract.term_start_time,
'end_date': manage_line.end_date
})
@api.onchange('term_start_time', 'term_end_time', 'total_area')
def onchange_lessor_id(self):
if not self.term_start_time:
return
if not self.lease_ids:
return
property_id = self.lease_ids[0].address_id.id
manage_line_domain = [
('yuthon_property_id', '=', property_id),
('start_time', '<=', self.term_start_time),
'|',
('end_date', '=', False),
('end_date', '>=', self.term_start_time)
]
manage_line = self.env['manage.information.line'].search(
manage_line_domain,
order='start_time desc',
limit=1
)
if manage_line and manage_line.company_id:
self.lessor_id = manage_line.company_id.id
if not (self.term_start_time and self.term_end_time and self.management_breakdown_ids):
return
breakdown_lines = []
for line in self.management_breakdown_ids:
breakdown_lines.append((1, line.id, {
'start_data': self.term_start_time,
'end_data': self.term_end_time,
'management_monthly_amount': line.management_unit_price * self.total_area
}))
self.management_breakdown_ids = breakdown_lines
@api.depends('code')
def _compute_workflow(self):
for record in self:
record.employee_ids = False
record.approve_state = False
workflow = self.env['soong.workflow.record'].search(
[('code', '=', record.code)],
limit=1
)
if workflow:
record.employee_ids = workflow.employee_ids
record.approve_state = workflow.approve_state
@api.depends()
def _compute_fees_all(self):
receivable_obj = self.env['property.accounts.receivable']
fee_mapping = {
'fees_rent': ['租金'],
'fees_water': ['水费'],
'fees_electricity': ['电费'],
'fees_manage': ['管理费'],
'fees_bollards': ['广告灯箱费'],
'betweene_tubes': ['管子间'],
'fees_expenses': ['人工费/日光灯款/代垫用料费']
}
for contract in self:
receivable_records = receivable_obj.search([
('submitted', '!=', 0),
('contract_id', '=', contract.id)
])
contract.fees_rent = 0.0
contract.fees_water = 0.0
contract.fees_electricity = 0.0
contract.fees_manage = 0.0
contract.fees_expenses = 0.0
contract.fees_bollards = 0.0
contract.betweene_tubes = 0.0
contract.fees_date = False
contract.fees_total = 0.0
if not receivable_records:
continue
all_due_dates = []
for field_name, target_names in fee_mapping.items():
filtered_recs = receivable_records.filtered(
lambda r: r.management_type_id and r.management_type_id.name in target_names
)
if filtered_recs:
setattr(contract, field_name, sum(filtered_recs.mapped('submitted')))
valid_dates = [d for d in filtered_recs.mapped('due_date') if d]
all_due_dates.extend(valid_dates)
if all_due_dates:
contract.fees_date = max(all_due_dates)
contract.fees_total = (
contract.fees_rent + contract.fees_water + contract.fees_electricity +
contract.fees_manage + contract.fees_expenses + contract.fees_bollards +
contract.betweene_tubes
)
def name_get(self):
"""
默认保持原来的显示(各处仍然用 display_name,例如 含版本号);
只有在 context 里带了 'show_lessor_in_contract_name' 时,
才在后面拼接出租方公司名称,并且不带版本号:
例如:租(2024) 86号(天河物管)
"""
# 先拿到原始 name_get 结果,保证不破坏默认行为
names = super(PropertyLeaseContract, self).name_get()
# 没有上下文标记 → 完全按原来返回(仍然是 租(2024) 86号(V1.0) 这种)
if not self.env.context.get('show_lessor_in_contract_name'):
return names
name_map = dict(names)
result = []
for rec in self:
# 这里只用合同编号,不带版本号
# contract_code 形如 “租(2024) 86号”
base_name = rec.contract_code or name_map.get(rec.id, rec.display_name or '')
# 出租方公司在 lessor_id 上
lessor_name = rec.lessor_id.display_name if rec.lessor_id else False
name = base_name
if lessor_name:
# 拼成:租(2024) 86号(天河物管)
name = '%s(%s)' % (base_name, lessor_name)
result.append((rec.id, name))
return result
@api.depends('payment_ids.print_date', 'payment_ids.is_print')
def _compute_is_print(self):
today = date.today()
start_date = today.replace(day=1)
end_date = start_date + relativedelta(months=1) - relativedelta(days=1)
for record in self:
printed_lines = record.payment_ids.filtered(
lambda line:
line.print_date and
start_date <= line.print_date <= end_date and
line.is_print is True
)
record.is_print = bool(printed_lines)
def refund_deposit(self):
contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for record in contract_ids:
if len(record) > 1:
raise UserError("只能选择一个合同")
if record.is_has_generated:
raise UserError("该合同已走应付账单流程")
management_type_id = self.env['yuthon.management.type'].search([('name', '=', '押金')], limit=1)
return {
'name': '应付账单',
'type': 'ir.actions.act_window',
'res_model': 'property.bills.payable',
'view_mode': 'form',
'target': 'current',
'context': {
'default_lease_contract_id': record.id,
'default_name': f'{record.contract_code}退押金申请',
'default_amount_payable': record.deposit,
'default_actual_amount': record.deposit,
},
}
def compute_is_has(self):
receivable_id = self.env['property.accounts.receivable'].search([('contract_id', '=', self.id),
('management_type_id.name', '=', '押金')
], limit=1)
if receivable_id:
if receivable_id.has_date:
self.is_has = True
else:
self.is_has = False
else:
self.is_has = False
@api.depends('users_id')
def _compute_company_id(self):
for record in self:
if record.users_id:
record.company_id = record.users_id.company_id.id
else:
record.company_id = None
def action_view_contract(self):
contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for record in contract_ids:
total_month_number = 0
if record.term_start_time and record.term_end_time:
delta = relativedelta(record.term_end_time, record.term_start_time)
total_month_number = delta.years * 12 + delta.months
if delta.days > 0:
total_month_number += 1
# 生成水费默认明细行
if record.is_water and not record.water_breakdown_ids:
self.env['property.water.breakdown.line'].create({
'breakdown_id': record.id,
'start_data': record.term_start_time,
'end_data': record.term_end_time,
'water_month_number': total_month_number,
})
# 生成电费默认明细行
if record.is_electricity and not record.ele_breakdown_ids:
self.env['property.ele.breakdown.line'].create({
'breakdown_id': record.id,
'start_data': record.term_start_time,
'end_data': record.term_end_time,
'ele_month_number': total_month_number,
})
# 生成管理费默认明细行
if record.lease_ids:
management_unit_price = record.lease_ids[0].message_standard_id.amount
management_monthly_amount = record.lease_ids[0].property_area * management_unit_price
else:
management_unit_price = 0
management_monthly_amount = 0
if record.is_manage and not record.management_breakdown_ids:
self.env['property.management.breakdown.line'].create({
'breakdown_id': record.id,
'start_data': record.term_start_time,
'end_data': record.term_end_time,
'management_unit_price': management_unit_price,
'management_monthly_amount': management_monthly_amount,
'management_month_number': total_month_number,
})
@api.onchange('is_issues')
def _onchange_is_issues(self):
if self.is_issues:
issue_id = self.env['issue.library'].create({'name': self.name, 'is_invisible_workflow': True})
self.related_issues_id = issue_id.id
self.env['issue.library.approval.line'].create({
'issue_library_id': issue_id.id,
'workflow_name': self.name,
'approval_type': self.workflow_id.name,
'res_model': self._name,
'res_id': self.id,
'code': self.code,
'approval_date': fields.Datetime.now(),
})
name_title = fields.Char(string="租赁合同审批")
contract_sort = fields.Selection([('property', '物业'), ('guanggao', '广告位'),('parking', '停车场'), ('guanzi', '管子间'), ('room', '机房'), ('base_station','基站'), ('other', '其他')], string="资产类型", default='property')
@api.depends('lease_ids.address_id.title_deed')
def _compute_title_information(self):
for record in self:
seen_titles = set()
unique_titles = []
for lease in record.lease_ids:
if lease.address_id and lease.address_id.title_deed:
title = lease.address_id.title_deed.strip()
if title and title not in seen_titles:
seen_titles.add(title)
unique_titles.append(title)
record.title_information = ', '.join(unique_titles)
@api.depends('lease_ids.address_id.admin_id', 'lease_ids.address_id.admin_id.department_id', 'lease_ids.address_id.tenure_type')
def _compute_admin_id(self):
for record in self:
if record.lease_ids:
record.admin_id = record.lease_ids[0].address_id.admin_id.id
record.tenure_type = record.lease_ids[0].address_id.tenure_type
@api.depends('admin_id')
def _compute_precinct_id(self):
for rec in self:
rec.precinct_id = False
if rec.admin_id:
precinct = self.env['yuthon.precinct.message'].search([('employee_id.user_id', '=', rec.admin_id.id)], limit=1)
rec.precinct_id = precinct.id if precinct else False
def contract_signature(self):
return {
'type': 'ir.actions.act_window',
'name': '合同签订',
"target": "new",
'res_model': 'contract.signature.wizard',
'view_mode': 'form',
'context': {'default_contract_id': self.id}
}
def _compute_contract_count(self):
for record in self:
current_date = datetime.now()
current_month_start = datetime(current_date.year, current_date.month, 1)
if current_date.month == 12:
next_month = 1
next_year = current_date.year + 1
else:
next_month = current_date.month + 1
next_year = current_date.year
current_month_end = datetime(next_year, next_month, 1) - timedelta(days=1)
record.contract_count = self.env['property.accounts.receivable'].search_count([('contract_id', '=', record.id)])
overdue_domain = [
('contract_id', '=', record.id),
('due_date', '<', current_month_start),
('submitted', '!=', 0)
]
has_overdue = self.env['property.accounts.receivable'].search_count(overdue_domain) > 0
record.is_overdue = has_overdue
not_received_domain = [
('contract_id', '=', record.id),
('submitted', '!=', 0),
('due_date', '<=', current_month_end),
('management_type_id.name', '!=', '违约金')
]
receivable_records = self.env['property.accounts.receivable'].search(not_received_domain)
record.not_received = sum(receivable_records.mapped('submitted'))
percentage_damages_domain = [
('contract_id', '=', record.id),
('submitted', '!=', 0),
('management_type_id.name', '=', '违约金'),
('due_date', '<=', current_month_end),
]
receivable_records = self.env['property.accounts.receivable'].search(percentage_damages_domain)
record.percentage_damages = sum(receivable_records.mapped('submitted'))
def _compute_reconciliation_count(self):
for record in self:
record.reconciliation_count = self.env['receivable.modify.line'].search_count([('receivable_id.contract_id', '=', self.id)])
def _compute_process_count(self):
for record in self:
record.process_count = self.env['soong.workflow.record'].search_count([('code', '=', record.code)])
def action_open_contract(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("yuthon_property_approval.property_accounts_receivable_action")
action.update({
'domain': [('contract_id', '=', self.id)],
'context': {
'search_default_group_by_management_type_id': 1,
'group_by': 'management_type_id'
}
})
return action
def action_open_reconciliation(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("yuthon_work_brand.action_receivable_modify_line_wbrand")
action.update({'domain': [('receivable_id.contract_id', '=', self.id),
]
})
return action
def action_open_process(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("soong_workflow.soong_workflow_other_record_action")
action.update({'domain': [('code', '=', self.code)]})
return action
def update_contract(self):
contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for rec in contract_ids:
if rec.lessor_line:
rec.write({'lessor_line': [(5,)]})
def update_admin_id(self):
contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for rec in contract_ids:
if rec.is_electricity and rec.electricity_fee_ids:
for fee in rec.electricity_fee_ids:
fee.line_loss = 0.02
if rec.is_water and rec.water_fee_ids:
for fee in rec.water_fee_ids:
fee.line_loss = 0.02
@api.onchange("lease_ids")
def onchange_lease_ids(self):
for rec in self:
start = rec.term_start_time
end = rec.term_end_time
rec.is_water = False
rec.is_electricity = False
rec.water_fee_ids = [(5, 0, 0)]
rec.electricity_fee_ids = [(5, 0, 0)]
rec.management_breakdown_ids = [(5, 0, 0)]
water_fee_values = []
message_fee_values = []
electricity_fee_values = []
# 用于去重的ID集合:记录已添加的水表/电表ID
added_water_meter_ids = set()
added_electric_meter_ids = set()
if rec.lease_ids:
# 处理每个租赁记录
for lease in rec.lease_ids:
if lease.address_id:
# 处理水表:增加去重逻辑
if lease.address_id.is_meter and lease.address_id.water_meter_ids:
active_water_meters = lease.address_id.water_meter_ids.filtered(
lambda m: not m.demolition_date or m.demolition_date > fields.Date.today()
)
for meter in active_water_meters:
if meter.property_meter_water_id and meter.property_meter_water_id.id not in added_water_meter_ids:
# 记录已添加的水表ID,防止重复
added_water_meter_ids.add(meter.property_meter_water_id.id)
water_fee_values.append([0, 0, {
'contract_id': rec.id,
'property_meter_water_id': meter.property_meter_water_id.id,
}])
# 处理电表
if lease.address_id.is_electricity_meter and lease.address_id.energy_meter_ids:
active_electric_meters = lease.address_id.energy_meter_ids.filtered(
lambda m: not m.demolition_date or m.demolition_date > fields.Date.today()
)
for meter in active_electric_meters:
# 确保meter记录有效 + 未被添加过
if meter.property_meter_archives_id and meter.property_meter_archives_id.id not in added_electric_meter_ids:
# 记录已添加的电表ID,防止重复
added_electric_meter_ids.add(meter.property_meter_archives_id.id)
electricity_fee_values.append([0, 0, {
'contract_id': rec.id,
'property_meter_archives_id': meter.property_meter_archives_id.id,
}])
if lease.address_id.is_message_fee and lease.address_id.message_standard_id:
if lease.address_id.rent_area > 0:
if start and end:
total_months = (end.year - start.year) * 12 + (end.month - start.month)
else:
total_months = 0
management_month_number = max(total_months, 0)
message_fee_values.append([0, 0, {
'breakdown_id': rec.id,
'management_unit_price': lease.address_id.message_standard_id.amount,
'management_monthly_amount': lease.address_id.message_standard_id.amount * lease.address_id.rent_area,
'start_data': rec.term_start_time,
'management_month_number': management_month_number + 1,
'management_period_rent': management_month_number * lease.address_id.message_standard_id.amount * lease.address_id.rent_area,
'end_data': rec.term_end_time,
}])
rec.is_manage = bool(message_fee_values)
rec.is_water = bool(water_fee_values)
rec.is_electricity = bool(electricity_fee_values)
if water_fee_values:
rec.water_fee_ids = water_fee_values
if electricity_fee_values:
rec.electricity_fee_ids = electricity_fee_values
if message_fee_values:
rec.management_breakdown_ids = message_fee_values
def all_end_approval(self):
contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for contract in contract_ids:
contract.end_approval()
logs_to_update = self.env["soong.workflow.log"].search([
("model", "=", self._name),
("res_id", "=", contract.id),
])
if logs_to_update:
last_log = logs_to_update[-1]
last_log.write({
"comment": " ",
"approval_state": "3",
"lase_click_date": fields.Datetime.now()
})
soong_record = self.env["soong.workflow.record"].search([
("code", "=", contract.code),
], limit=1)
if soong_record:
soong_record.sudo().write({
'node_id' : False,
'employee_ids': [(5,)],
'soong_state': 'done',
'approve_state' : 'end',
})
def end_approval(self):
current_date = date.today()
# 续租审批应退押金逻辑
if self.contract_id and self.contract_id.deposit and self.contract_type == 'lease_renewal':
management_type_id = self.env['yuthon.management.type'].search([('name', '=', '押金')], limit=1)
bills_list = self.env['property.bills.list'].search([
('contract_id', '=', self.contract_id.id,)
])
workflow_record_id = self.env["soong.workflow.record"].search(
[("res_model", "=", self._name), ("res_id", "=", self.id)])
if not bills_list:
self.env['property.bills.list'].create({
'lessor_id': self.contract_id.lessor_id.id,
'due_date': self.contract_id.actual_end_time + timedelta(days=1), # 原合同
'management_type_id': management_type_id.id,
'amount': self.contract_id.deposit,
'contract_id': self.contract_id.id,
'remake': '续签退押',
'workflow_record_id': workflow_record_id.id,
})
bank_id = self.env['property.bank.statements'].search([
('name_account', '=', self.contract_id.lessee_id.name),
('contract_ids', 'in', self.id),
('remake', '=', '续签退押'),
])
if not bank_id:
lessor_id = self.contract_id.lessor_id # 出租方
tenant_line = self.env['property.tenant.line'].search([
('tenant_id', '=', self.lessee_id.id),
('lease_contract_id', '=', self.id),
], limit=1)
if not tenant_line:
self.env['property.tenant.line'].create({
'tenant_id': self.lessee_id.id,
'lease_contract_id': self.id,
})
company_id = self.env['res.company'].search([('name', '=', '集团本部')], limit=1)
if lessor_id.referred in ['天河投资', '天投城建']:
company_id = self.env['res.company'].search([('name', '=', '集团本部')], limit=1)
elif lessor_id.referred in ['天河物管']:
company_id = self.env['res.company'].search([('name', '=', '天河物管')], limit=1)
elif lessor_id.referred in ['天河粮储']:
company_id = self.env['res.company'].search([('name', '=', '天河粮储')], limit=1)
bank_accounts = self.env['property.bank.line'].search([
('tenant_id', '=', self.lessee_id.id)
]).mapped('bank')
bank_accounts = [acc for acc in bank_accounts if acc][0]
end_date = self.contract_id.actual_end_time + timedelta(days=1)
start_time = f"{end_date} 09:00:00"
bank_id = self.env['property.bank.statements'].sudo().create({
'start_time': start_time,
'name': bank_accounts,
'original_amount': self.contract_id.deposit,
'name_account': self.contract_id.lessee_id.name,
'remake': '续签退押',
'lending_direction': 'loan',
'workflow_record_id': workflow_record_id.id,
'company_id': company_id.id,
'your_own_unit': lessor_id.referred,
'your_own_account': lessor_id.rent_account,
})
bank_id._compute_property_employee_id()
if self.term_start_time <= current_date <= self.term_end_time:
new_contact_state = 'ing'
old_contact_state = 'terminate'
else:
new_contact_state = 'to'
self.write({
'condition': 'be_executed',
'contact_state': new_contact_state,
'actual_start_time': self.term_start_time,
'actual_end_time': self.term_end_time,
})
old_contract_id = self.env['property.lease.contract'].search([
('contract_code', '=', self.contract_code),
('id', '!=', self.id),
])
if old_contract_id:
if old_contact_state:
old_contract_id.write({'contact_state': old_contact_state})
for contract in self:
address_ids = contract.lease_ids.mapped('address_id').filtered(lambda addr: addr.id)
if not address_ids:
continue
if not contract.actual_start_time:
continue
start_date = contract.actual_start_time
next_month = start_date.month + 1
next_year = start_date.year
if next_month > 12:
next_month = 1
next_year += 1
meter_lines = self.env['meter.archives.line'].search([
('yuthon_property_ids', 'in', address_ids.ids),
('meter_date', '>=', f'{next_year}-{next_month:02d}-01') # 下个月1号及之后
])
if not meter_lines:
continue
meter_lines.write({
'lessee_id': contract.lessee_id.id,
'contract_id': contract.id
})
contract._update_contract_lines(contract)
def open_record(self):
return {
'type': 'ir.actions.act_window',
'res_model': 'property.lease.contract',
'res_id': self.id,
'view_mode': 'form',
}
@api.depends('contract_code', 'name', 'contract_type', 'property_name')
def _compute_name(self):
contract_dict = {
'lease_renewal': '续租',
'interim': '临租',
'new': '补充协议',
'first': '首租'
}
for record in self:
if record.contract_code and record.property_name and record.contract_type:
record.name = f"{record.contract_code}{contract_dict.get(record.contract_type)}{record.property_name}"
elif record.contract_code and record.property_name:
record.name = f"{record.contract_code}{record.property_name}"
elif record.name:
record.name = record.property_name
else:
record.name = self._description
def update_property(self):
empty_contract_lines = self.env['property.contract.line'].search([('lease_contract_id', '=', False)])
if empty_contract_lines:
empty_contract_lines.unlink()
contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for contract in contract_ids:
self._update_contract_lines(contract)
def _update_contract_lines(self, contract):
"""
处理单个合同的明细行更新
:param contract: 物业租赁合同记录
"""
for record in contract.lease_ids:
property_addr = record.address_id
if not property_addr:
continue
existing_line = self.env['property.contract.line'].search([
('yuthon_property_id', '=', property_addr.id),
('lease_contract_id', '=', contract.id)
], limit=1)
property_addr.lessee_id = contract.lessee_id.id
# 准备要写入的数据
contract_data = {
'yuthon_property_id': property_addr.id,
'lease_contract_id': contract.id,
'lessee_id': contract.lessee_id.id,
'rent': contract.rent,
'start_time': contract.actual_start_time,
'end_date': contract.actual_end_time,
}
if existing_line:
# 如果存在则更新
existing_line.write(contract_data)
else:
# 如果不存在则追加
property_addr.write({
'property_contract_ids': [(0, 0, contract_data)]
})
# 更新承租方合同信息
lessee_contract = self.env['property.tenant.line'].search([
('tenant_id', '=', contract.lessee_id.id),
('lease_contract_id', '=', contract.id)
], limit=1)
lessee_date = {
'tenant_id': contract.lessee_id.id,
'lease_contract_id': contract.id,
}
if not lessee_contract:
lessee_contract.write({
'property_contract_ids': [(0, 0, lessee_date)]
})
contract_lines = self.env['property.contract.line'].search([
('lease_contract_id', '=', contract.id)
])
for line in contract_lines:
# 从明细行中获取关键信息
start_time = line.start_time
end_date = line.end_date
yuthon_property_id = line.yuthon_property_id.id
if not (start_time and yuthon_property_id):
continue
# 获取当前物业记录
property_addr = self.env['yuthon.property'].browse(yuthon_property_id)
if not property_addr:
continue
date_domain = [
('meter_date', '>=', start_time)
]
if end_date:
date_domain.append(('meter_date', '<=', end_date))
# 处理水表
if property_addr.is_meter:
water_lines = self.env['meter.water.line'].search([
('yuthon_property_ids', 'in', [yuthon_property_id]),
*date_domain
])
for water_line in water_lines:
water_line.write({'lessee_id': line.lessee_id.id})
if property_addr.is_electricity_meter:
electric_lines = self.env['meter.archives.line'].search([
('yuthon_property_ids', 'in', [yuthon_property_id]),
*date_domain
])
for electric_line in electric_lines:
electric_line.write({'lessee_id': line.lessee_id.id})
@api.onchange('lessee_id')
def _onchange_lessee_id(self):
for record in self:
if record.lessee_id:
lease_bank_ids = self.env['property.bank.line'].search([('tenant_id', '=', record.lessee_id.id)])
record.lease_bank_ids = lease_bank_ids.ids
def set_actual_time_to_contract_time(self):
contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for contract in contract_ids:
contract._compute_end_comparison()
@api.depends('not_received', 'now_receivable')
def _compute_comparison(self):
for record in self:
if record.not_received and record.now_receivable:
if record.not_received > record.now_receivable:
record.comparison = 'outweigh'
elif record.not_received < record.now_receivable:
record.comparison = 'less'
else:
record.comparison = 'equal'
else:
record.comparison = None
def update_color(self):
button_ids = self.env["soong.workflow.node.button"].browse(
self._context.get('active_ids', self._context.get('active_id')))
@api.depends('actual_end_time', 'term_start_time', 'term_end_time')
def _compute_end_comparison(self):
for record in self:
now = date.today()
if record.actual_end_time and record.term_start_time:
if record.actual_end_time > now:
delta = record.actual_end_time - now
if 60<= delta.days < 90:
record.end_comparison = 'blue'
elif 30<= delta.days< 60:
record.end_comparison = 'yellow'
elif delta.days < 30:
record.end_comparison = 'red'
else:
record.end_comparison = None
else:
record.end_comparison = None
if record.term_end_time:
if record.actual_end_time < record.term_end_time:
record.end_comparison = 'red'
else:
record.end_comparison = None
if record.actual_end_time == record.term_start_time:
record.is_equal = True
else:
record.end_comparison = None
@api.depends('actual_end_time')
def _compute_expiration_coming(self):
for record in self:
if record.actual_end_time:
delta = record.actual_end_time - date.today()
if 0 < delta.days < 90:
record.expiration_coming = 'coming'
elif delta.days <=0:
record.expiration_coming = 'due'
else:
record.expiration_coming = None
else:
record.expiration_coming = None
@api.onchange('total_area', 'rent')
def _onchange_unit_price(self):
for i in self:
if i.total_area and i.rent:
i.unit_price = i.rent / i.total_area
@api.depends('breakdown_ids')
def _compute_total_contract_receivables(self):
for record in self:
record.total_contract_receivables = sum(line.period_rent for line in record.breakdown_ids)
# @api.onchange("term_start_time", "term_end_time",
# "increments_every_few_years", "unit_price",
# "first_years_begin", "increment_method",
# "increment_price", "increment_percentage", "increment_amount",
# "rent_free_period_year", "rent_free_period_month", "total_area")
# def onchange_first_year_rent(self):
# for record in self:
# if record.term_start_time and record.term_end_time:
# start_date = record.term_start_time
# end_date = record.term_end_time + relativedelta(days=1)
# if start_date + relativedelta(months=12) < end_date:
# end_date = start_date + relativedelta(months=12)
# all_period_rent = record.my_compute_all_period_rent(start_date, end_date)
# record.first_year_rent = all_period_rent
# all_start_date = record.term_start_time
# all_end_date = record.term_end_time + relativedelta(days=1)
# record.total_contract_receivables = record.my_compute_all_period_rent(all_start_date, all_end_date)
@api.onchange("lease_term_year", "lease_term_month", "term_start_time" , "lease_term_day")
def onchange_lease_term_year(self):
for record in self:
if record.lease_term_year < 2:
record.is_less_than_2 = True
else:
record.is_less_than_2 = False
if record.lease_term_year or record.lease_term_month:
if not record.lease_term_day:
if record.term_start_time:
year = record.lease_term_year if record.lease_term_year else 0
month = record.lease_term_month if record.lease_term_month else 0
months = year * 12 + month
record.term_end_time = record.term_start_time + relativedelta(months=months) - relativedelta(days=1)
else:
if record.term_start_time:
year = record.lease_term_year if record.lease_term_year else 0
month = record.lease_term_month if record.lease_term_month else 0
months = year * 12 + month
record.term_end_time = record.term_start_time + relativedelta(months=months) - relativedelta(days=1) + relativedelta(days=record.lease_term_day)
@api.onchange("total_contract_receivables", "tax_rate")
def onchange_total_contract_receivables(self):
for record in self:
if record.total_contract_receivables and record.tax_rate:
record.not_tax_amount = record.total_contract_receivables / (1 + record.tax_rate.ratio)
record.tax = record.total_contract_receivables - record.not_tax_amount
@staticmethod
def divide_time_range_fixed(start_time, end_time, months):
time_ranges = []
while start_time < end_time:
end_of_range = start_time + relativedelta(months=months)
if end_of_range > end_time:
end_of_range = end_time
time_range = [start_time, end_of_range]
time_ranges.append(time_range)
start_time = end_of_range
return time_ranges
@api.onchange('cycle_dict')
def onchange_cycle_dict(self):
if self.cycle_dict == 'monthly':
self.cycle_mode = 'fixed_monthly'
elif self.cycle_dict == 'quarter':
self.cycle_mode = 'fixed_quarter'
elif self.cycle_dict == 'year':
self.cycle_mode = 'fixed_year'
# @staticmethod
# def divide_time_range_floating(start_time, end_time, months):
# time_ranges = []
# while start_time < end_time:
# start_time = start_time.replace(day=1)
# end_of_range = start_time + relativedelta(months=months)
# time_range = [start_time, end_of_range - relativedelta(days=1)]
# time_ranges.append(time_range)
# start_time = end_of_range
# return time_ranges
# def my_compute_all_period_rent(self, start_date, end_date):
# first_years_begin = self.first_years_begin if self.first_years_begin else 0
# increments_every_few_years = self.increments_every_few_years
# monthly_amount = self.total_area * self.unit_price # 月租金
# unit_price = self.unit_price # 单价
# rent_free_period_month = 0
# # 先计算免租期
# # if self.rent_free_period_year:
# # rent_free_period_month += self.rent_free_period_year * 12
# if self.rent_free_period_month:
# rent_free_period_month += self.rent_free_period_month
# free_start = start_date # 免租期开始
# if rent_free_period_month:
# free_start = start_date + relativedelta(months=rent_free_period_month)
#
# all_period_rent = 0
# if not increments_every_few_years:
# # 计算剩余日期
# delta = relativedelta(end_date, free_start)
# months = delta.years * 12 + delta.months
# all_period_rent += monthly_amount * months
# else:
# # 不递增年份的计算
# increments_start = free_start # 平稳和递增开始
# all_months = 0 # 第几月份
# # 有免递增年份的计算
# if first_years_begin:
# first_index = 0 # 第几递增周期年
# divided_year_segments = self.divide_time_range_fixed(start_date, end_date, 12)
# for segment_year in divided_year_segments:
# first_index += 1
# increments_start = segment_year[1]
# if first_years_begin and first_index >= first_years_begin:
# # 进入 递增时间 跳出平稳计费极端
# break
# delta = relativedelta(segment_year[1], segment_year[0])
# months = delta.years * 12 + delta.months
# for month in range(months):
# all_months += 1
# # 判断是否是免租期
# if all_months > rent_free_period_month:
# all_period_rent += monthly_amount
# # 开始递增
# divided_increments_segments = self.divide_time_range_fixed(increments_start, end_date, increments_every_few_years * 12)
# index = 0 # 第几递增周期年
# for increments_year in divided_increments_segments:
# index += 1
# """
# increment_percentage = fields.Float(string="递增百分比")
# increment_price = fields.Float(string="递增单价")
# increment_amount = fields.Float(string="递增金额")
# """
# increments_month = 0
# if self.increment_method == "increment_price" and self.increment_value:
# unit_price = unit_price + self.increment_value
# monthly_amount = self.total_area * unit_price
# if self.increment_method == "percentage" and self.increment_value:
# monthly_amount = monthly_amount * self.increment_value
# if self.increment_method == "increment_amount" and self.increment_value:
# monthly_amount = monthly_amount + self.increment_value
# delta = relativedelta(increments_year[1], increments_year[0])
# months = delta.years * 12 + delta.months
# # 判断第几月份进入 付费租期
# for month in range(months):
# all_months += 1
# # 判断是否是免租期
# if all_months > rent_free_period_month:
# increments_month += 1
# if increments_month:
# all_period_rent += monthly_amount * increments_month
# return all_period_rent
def action_generate_rental_charges(self):
"""
跳转租金费用弹窗
:return:
"""
return {
'name': '租金费用弹窗',
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'property.rental.wizard',
'target': 'new',
'context': {
'default_lease_contract_id': self.id,
}
}
def action_open_contract_rental_wizard(self):
"""指定合同生成租金弹窗"""
property_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
return {
'name': '生成租金',
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'property.contract.rental.wizard',
'target': 'new',
'context': {
'default_contract_ids': property_ids.ids,
},
}
def action_generate_administrative_expenses(self):
"""
跳转租金费用弹窗
:return:
"""
return {
'name': '租金费用弹窗',
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'property.rental.wizard',
'target': 'new',
'context': {
'default_lease_contract_id': self.id,
}
}
@api.onchange("term_start_time", "term_end_time", "rent",
"increments_every_few_years", "unit_price",
"first_years_begin", "increment_method", "increment_value",
"rent_free_period_month", "rent_free_period_days", "total_area")
def _onchange_action_generate_details(self):
"""
生产租金明细
:return:
"""
# todo 还没有计算递增递减
# todo 还没有计算首月优惠
# todo 还没有计算开始和结束不足月份和季度的计算比例
for record in self:
if record.term_start_time and record.term_end_time:
record.write({'breakdown_ids': [(5, 0, 0)]})
start_date = record.term_start_time
end_date = record.term_end_time + relativedelta(days=1)
record.do_generate_details_core(start_date, end_date, True)
# 计算首年租金
first_year_end_date = start_date + relativedelta(months=12)
if first_year_end_date > end_date:
first_year_end_date = end_date
all_period_rent = record.do_generate_details_core(start_date, first_year_end_date, False)
record.first_year_rent = all_period_rent
def all_action_generate_details(self):
property_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for record in property_ids:
if record.term_start_time and record.term_end_time:
record.write({'breakdown_ids': [(5, 0, 0)]})
start_date = record.term_start_time
end_date = record.term_end_time + relativedelta(days=1)
record.do_generate_details_core(start_date, end_date, True)
# 计算首年租金
first_year_end_date = start_date + relativedelta(months=12)
if first_year_end_date > end_date:
first_year_end_date = end_date
all_period_rent = record.do_generate_details_core(start_date, first_year_end_date, False)
record.first_year_rent = all_period_rent
def do_generate_details_core(self, start_date, end_date, write_db=False):
all_period_rent = 0
unit_price = self.unit_price # 单价
unit_price = float_round(unit_price, precision_digits=2, rounding_method='HALF-UP')
monthly_amount = self.rent
monthly_amount = float_round(monthly_amount, precision_digits=0, rounding_method='HALF-UP') # 月租金
# 如果有免租期 月 时间进行到 start_date
free_start_dict = {}
free_start = start_date
if self.rent_free_period_month:
free_start = start_date + relativedelta(months=self.rent_free_period_month)
if free_start > end_date:
free_start = end_date
delta = relativedelta(free_start, start_date)
month_number = delta.years * 12 + delta.months
if delta.days:
month_number += 1
free_start_dict = {
"unit_price": unit_price,
"monthly_amount": monthly_amount,
"breakdown_id": self.id,
"start_data": start_date,
"end_data": free_start - relativedelta(days=1),
"month_number": month_number,
"period_rent": 0,
}
free_day_start = free_start
add_year_index = 0 # 第几年
year_month_index = 0 # 第几个月
free_start_day_dict = {}
monthly_amount_last_dict = {}
monthly_amount_new_dict = {}
# 说明存在免增期 单独计算
if start_date < end_date:
one_years_list = self.divide_time_range_fixed(start_date, end_date, 12)
for one_year in one_years_list:
add_year_index += 1
# 是否存在免增期
first_years_begin = self.first_years_begin if self.first_years_begin else 0
# 存在递增期 并且进入递增金额计算
if self.increments_every_few_years > 0 and add_year_index >= first_years_begin:
if add_year_index % self.increments_every_few_years == 0:
# 单价和金额计算
if self.increment_method == "increment_price" and self.increment_value:
unit_price = unit_price + self.increment_value
unit_price = float_round(unit_price, precision_digits=2, rounding_method='HALF-UP')
monthly_amount = self.total_area * unit_price
monthly_amount = float_round(monthly_amount, precision_digits=0, rounding_method='HALF-UP') # 月租金
if self.increment_method == "percentage" and self.increment_value:
unit_price = unit_price * (1 + (self.increment_value / 100))
unit_price = float_round(unit_price, precision_digits=2, rounding_method='HALF-UP')
monthly_amount = self.total_area * unit_price
monthly_amount = float_round(monthly_amount, precision_digits=0, rounding_method='HALF-UP') # 月租金
if self.increment_method == "increment_amount" and self.increment_value:
monthly_amount = monthly_amount + self.increment_value
monthly_amount = float_round(monthly_amount, precision_digits=0, rounding_method='HALF-UP') # 月租金
unit_price = monthly_amount / self.total_area
unit_price = float_round(unit_price, precision_digits=2, rounding_method='HALF-UP')
# 开始时间, 结束时间
one_year_start = one_year[0]
one_year_end = one_year[1]
one_month_list = self.divide_time_range_fixed(one_year_start, one_year_end, 1)
for one_month in one_month_list:
period_rent = round(monthly_amount)
# 所有月统计
year_month_index += 1
# 如果开始时间 是 免租期月的结束时间,那么进入免租期日
if one_month[0] == free_start:
# 如果免租期有天那么默认增加一个月
if self.rent_free_period_days:
# 免租日的截至日期
free_day_start = one_month[1]
# 计算这个月的天数
total_days = (one_month[1] - one_month[0]).days
current_no_free = (total_days - self.rent_free_period_days) / total_days
period_rent = round(monthly_amount * current_no_free) if monthly_amount * current_no_free > 0 else 0
# 计算 免租期天的比值
free_start_day_dict = {
"unit_price": unit_price,
"monthly_amount": monthly_amount,
"breakdown_id": self.id,
"start_data": one_month[0],
"end_data": one_month[1] - relativedelta(days=1),
"month_number": 1,
"period_rent": period_rent,
}
all_period_rent += period_rent
# 已经跳出 免租期了 开始计费
if one_month[0] >= free_day_start:
current_days = calendar.monthrange(one_month[0].year, one_month[0].month)[1]
total_days = (one_month[1] - one_month[0]).days
# 说明不足月 单独计算
if current_days > total_days:
current_no_free = total_days / current_days
period_rent = round(monthly_amount * current_no_free) if monthly_amount * current_no_free > 0 else 0
monthly_amount_last_dict = {
"unit_price": unit_price,
"monthly_amount": monthly_amount,
"breakdown_id": self.id,
"start_data": one_month[0],
"end_data": one_month[1] - relativedelta(days=1),
"month_number": 1,
"period_rent": period_rent,
}
# 足月的单独进行计算
else:
if str(monthly_amount) in monthly_amount_new_dict:
amount_dict = monthly_amount_new_dict.get(str(monthly_amount))
old_period_rent = amount_dict.get("period_rent")
old_start_data = amount_dict.get("start_data")
old_end_data = amount_dict.get("end_data")
start_data = min(old_start_data, one_month[0])
end_data = max(old_end_data, one_month[1])
delta = relativedelta(end_data, start_data)
month_number = delta.years * 12 + delta.months
if delta.days:
month_number += 1
monthly_amount_new_dict[str(monthly_amount)] = {
"unit_price": unit_price,
"monthly_amount": monthly_amount,
"breakdown_id": self.id,
"start_data": start_data,
"end_data": end_data - relativedelta(days=1),
"month_number": month_number,
"period_rent": old_period_rent + period_rent,
}
else:
monthly_amount_new_dict[str(monthly_amount)] = {
"unit_price": unit_price,
"monthly_amount": monthly_amount,
"breakdown_id": self.id,
"start_data": one_month[0],
"end_data": one_month[1] - relativedelta(days=1),
"month_number": 1,
"period_rent": period_rent,
}
all_period_rent += period_rent
if write_db:
# 是否免租期 月
if free_start_dict:
self.sudo().write({'breakdown_ids': [(0, 0, free_start_dict)]})
# 是否免租期 日
if free_start_day_dict:
self.sudo().write({'breakdown_ids': [(0, 0, free_start_day_dict)]})
# 开始统计月份阶段金额
for k, v in monthly_amount_new_dict.items():
self.sudo().write({'breakdown_ids': [(0, 0, v)]})
# 最后有不足月的单独计算
if monthly_amount_last_dict:
self.sudo().write({'breakdown_ids': [(0, 0, monthly_amount_last_dict)]})
return all_period_rent
# def do_generate_details_core_1(self, start_date, end_date):
# """
# 计算核心算法
# :return:
# """
# all_period_rent = 0 # 总金额
# monthly_amount = self.total_area * self.unit_price
# monthly_amount = float_round(monthly_amount, precision_digits=0, rounding_method='HALF-UP') # 月租金
# # 月租金
# unit_price = self.unit_price # 单价
#
# # 如果有免租期 月 时间进行到 start_date
# free_start = start_date
# if self.rent_free_period_month:
# free_start = start_date + relativedelta(months=self.rent_free_period_month)
# if free_start > end_date:
# free_start = end_date
# self.sudo().write({'breakdown_ids': [(0, 0, {
# "unit_price": unit_price,
# "monthly_amount": monthly_amount,
# "breakdown_id": self.id,
# "start_data": start_date,
# "end_data": free_start - relativedelta(days=1),
# "month_number": self.rent_free_period_month,
# "period_rent": 0,
# })]})
#
# # 如果没有递增 那么计算全部
# if not self.increments_every_few_years:
# # 如果免租期有天那么默认增加一个月
# # 如果有免增期 日
# free_start_day = free_start
# if self.rent_free_period_days:
# free_start_day = free_start + relativedelta(months=self.rent_free_period_days)
# if free_start_day < end_date:
# # 计算这个月的天数
# current_days = calendar.monthrange(free_start.year, free_start.month)[1]
# current_no_free = (current_days - self.rent_free_period_days) / current_days
# period_rent = monthly_amount * 1 * current_no_free if monthly_amount * 1 * current_no_free > 0 else 0
# # 计算 免租期天的比值
# self.sudo().write({'breakdown_ids': [(0, 0, {
# "unit_price": unit_price,
# "monthly_amount": monthly_amount,
# "breakdown_id": self.id,
# "start_data": free_start,
# "end_data": free_start_day - relativedelta(days=1),
# "month_number": 1,
# "period_rent": period_rent,
# })]})
# all_period_rent += period_rent
#
# # 计算 剩余日期的金额
# if free_start_day < end_date:
# delta = relativedelta(end_date, free_start_day)
# months = delta.years * 12 + delta.months
# free_start_day_end = free_start_day + relativedelta(months=months)
# self.sudo().write({'breakdown_ids': [(0, 0, {
# "unit_price": unit_price,
# "monthly_amount": monthly_amount,
# "breakdown_id": self.id,
# "start_data": free_start_day,
# "end_data": free_start_day_end - relativedelta(days=1),
# "month_number": months,
# "period_rent": monthly_amount * months,
# })]})
# all_period_rent += monthly_amount * months
# # 说明最后还有几天
# if delta.days > 0:
# current_days = calendar.monthrange(free_start_day_end.year, free_start_day_end.month)[1]
# current_no_free = delta.days / current_days
# period_rent = monthly_amount * 1 * current_no_free if monthly_amount * 1 * current_no_free > 0 else 0
# # 计算 免租期天的比值
# self.sudo().write({'breakdown_ids': [(0, 0, {
# "unit_price": unit_price,
# "monthly_amount": monthly_amount,
# "breakdown_id": self.id,
# "start_data": free_start_day_end,
# "end_data": end_date,
# "month_number": 1,
# "period_rent": period_rent,
# })]})
#
# # 如果有递增
# else:
# first_years_begin_end = start_date
# # 免递增年份计算
# if self.first_years_begin > 1:
# # 免递增结束时间
# first_years_begin_end = start_date + relativedelta(months=(self.first_years_begin -1) * 12)
# # 免租期结束时间 free_start
# # 计算免租期和免增期的差值, 如果免租期超过免增期那么不存在免增期
# if first_years_begin_end > free_start:
# # 如果免租期有天那么默认增加一个月
# if self.rent_free_period_days:
# # 计算这个月的天数
# current_days = calendar.monthrange(free_start.year, free_start.month)[1]
# current_no_free = (current_days - self.rent_free_period_days) / current_days
# # 计算 免租期天的比值
# self.sudo().write({'breakdown_ids': [(0, 0, {
# "unit_price": unit_price,
# "monthly_amount": monthly_amount,
# "breakdown_id": self.id,
# "start_data": free_start,
# "end_data": free_start + relativedelta(months=1) - relativedelta(days=1),
# "month_number": 1,
# "period_rent": monthly_amount * 1 * current_no_free if monthly_amount * 1 * current_no_free > 0 else 0,
# })]})
# new_free_day_amount = monthly_amount * 1 * current_no_free if monthly_amount * 1 * current_no_free > 0 else 0
# all_period_rent += new_free_day_amount
# # 免租期后退一个月
# free_start = free_start + relativedelta(months=1)
# # 计算剩下的免增期
# delta = relativedelta(first_years_begin_end, free_start)
# first_months = delta.years * 12 + delta.months
# self.sudo().write({'breakdown_ids': [(0, 0, {
# "unit_price": unit_price,
# "monthly_amount": monthly_amount,
# "breakdown_id": self.id,
# "start_data": free_start,
# "end_data": first_years_begin_end - relativedelta(days=1),
# "month_number": first_months,
# "period_rent": first_months * monthly_amount,
# })]})
# all_period_rent += first_months * monthly_amount
# # 免增期结束开始递增
# divided_increments_segments = self.divide_time_range_fixed(first_years_begin_end, end_date, increments_every_few_years*12)
# year_index = 0 # 第几递增周期年
# all_months = 0 # 递增的第几个月
# # 如果免租期 超过 免增期 那么递增期也需要排除
# if first_years_begin_end < free_start:
# delta = relativedelta(free_start, first_years_begin_end)
# free_first_months = delta.years * 12 + delta.months
# # 如果免租期有天那么默认增加一个月
# if self.rent_free_period_days:
# free_first_months = free_first_months + 1
# else:
# free_first_months = 0
#
# for increments_year in divided_increments_segments:
# year_index += 1
# """
# increment_percentage = fields.Float(string="递增百分比")
# increment_price = fields.Float(string="递增单价")
# increment_amount = fields.Float(string="递增金额")
# """
# increments_month = 0
# # delta = relativedelta(increments_year[1], increments_year[0])
# # months = delta.years * 12 + delta.months
# if self.increment_method == "increment_price" and self.increment_value:
# unit_price = unit_price + self.increment_value
# monthly_amount = self.total_area * unit_price
# monthly_amount = float_round(monthly_amount, precision_digits=0, rounding_method='HALF-UP') # 月租金
# if self.increment_method == "percentage" and self.increment_value:
# unit_price = unit_price * (1 + (self.increment_value / 100))
# monthly_amount = self.total_area * unit_price
# monthly_amount = float_round(monthly_amount, precision_digits=0, rounding_method='HALF-UP') # 月租金
# if self.increment_method == "increment_amount" and self.increment_value:
# monthly_amount = monthly_amount + self.increment_value
# monthly_amount = float_round(monthly_amount, precision_digits=0, rounding_method='HALF-UP') # 月租金
# unit_price = monthly_amount / self.total_area
#
# delta = relativedelta(increments_year[1], increments_year[0])
# months = delta.years * 12 + delta.months
# month_index = 0
# # 判断第几月份进入 付费租期
# for month in range(months):
# month_index += 1 # 当前第个月
# # 判断第几月份进入 付费租期
# all_months += 1
# if all_months == free_first_months:
# # 如果免租期有天那么默认增加一个月
# if self.rent_free_period_days:
# current_now = increments_year[0] + relativedelta(months=month_index)
# # 计算这个月的天数
# current_days = calendar.monthrange(current_now.year, current_now.month)[1]
# current_no_free = (current_days - self.rent_free_period_days) / current_days
# # 计算 免租期天的比值
# self.sudo().write({'breakdown_ids': [(0, 0, {
# "unit_price": unit_price,
# "monthly_amount": monthly_amount,
# "breakdown_id": self.id,
# "start_data": current_now,
# "end_data": current_now + relativedelta(months=1) - relativedelta(days=1),
# "month_number": 1,
# "period_rent": monthly_amount * 1 * current_no_free if monthly_amount * 1 * current_no_free > 0 else 0,
# })]})
# new_free_day_amount = monthly_amount * 1 * current_no_free if monthly_amount * 1 * current_no_free > 0 else 0
# all_period_rent += new_free_day_amount
# # 判断是否是免租期
# if all_months > free_first_months:
# increments_month += 1
# if increments_month:
# self.sudo().write({'breakdown_ids': [(0, 0, {
# "unit_price": unit_price,
# "monthly_amount": monthly_amount,
# "breakdown_id": self.id,
# "start_data": increments_year[1] - relativedelta(months=increments_month),
# "end_data": increments_year[1] - relativedelta(days=1),
# "month_number": increments_month,
# "period_rent": monthly_amount * increments_month,
# })]})
# all_period_rent += monthly_amount * increments_month
# return all_period_rent
# @api.onchange("first_year_rent")
# def _onchange_first_year_rent(self):
# for rec in self:
# rec.performance_bonds = rec.first_year_rent * 2
# @api.onchange("total_area", "unit_price")
# def _onchange_deposit(self):
# for record in self:
# record.deposit = record.total_area * record.unit_price * 2 # 月租金
@api.onchange('rent')
def _onchange_deposit(self):
for record in self:
if record.rent:
record.deposit = record.rent * 2
else:
record.deposit = 0.0
def update_lease_ids(self):
property_ids = self.env["property.lease.contract"].browse(self._context.get('active_ids', self._context.get('active_id')))
for rec in property_ids:
lease_name = []
electricity_list = []
water_list = []
title_address = []
for record in rec.lease_ids:
if record.address_id and record.address_id.title_address:
lease_name.append(record.address_id.name)
title_address.append(record.address_id.title_address.name)
rec.name = ','.join(lease_name)
rec.use_address = '丶'.join(lease_name)
rec.title_address = ','.join(title_address)
property_id = self.env['yuthon.property'].search([('id', '=', record.address_id.id)])
if property_id.energy_meter_ids:
rec.is_electricity = True
for electricity_id in property_id.energy_meter_ids:
electricity_list.append([0, 0, {
'fees_id': electricity_id.standard_id.id,
'proportional': electricity_id.rate,
}])
else:
rec.is_electricity = False
if property_id.water_meter_ids:
rec.is_water = True
for water_id in property_id.water_meter_ids:
water_list.append([0, 0, {
'fees_id': water_id.standard_id.id,
'proportional': water_id.rate,
}])
else:
rec.is_water = False
rec.electricity_fee_ids = [(5, 0, 0)]
rec.admin_id = rec.lease_ids[0].admin_line_id.id if rec.lease_ids else ''
rec.tenure_type = rec.lease_ids[0].address_id.tenure_type if rec.lease_ids else ''
rec.lessor_id = rec.lease_ids[0].address_id.company_id.id if rec.lease_ids else ''
rec.total_area = sum([lease.property_area for lease in rec.lease_ids])
rec.electricity_fee_ids = electricity_list
def trigger_lease_ids(self):
property_ids = self.env["property.lease.contract"].browse(self._context.get('active_ids', self._context.get('active_id')))
for record in property_ids:
lessor_id = record.lease_ids[-1].address_id.company_id.id if record.lease_ids else ''
admin_id = record.lease_ids[-1].admin_line_id.id if record.lease_ids else ''
tenure_type = record.lease_ids[-1].address_id.tenure_type if record.lease_ids else ''
record.sudo().write({'lessor_id': lessor_id,
'admin_id': admin_id,
'tenure_type':tenure_type,})
record._compute_company_id()
def action_yuthon_property_contract_wizard(self):
return {
"type": "ir.actions.act_window",
"name": "批量导入物业合同",
"res_model": "property.contract.wizard",
"view_mode": "form",
"target": "new",
}
def action_relevance_property(self):
contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id'))
)
contract_dict = {
'lease_renewal': '续租',
'interim': '临租',
'new': '补充协议',
'first': '首租'
}
for record in contract_ids:
if record.contract_code and record.property_name and record.contract_type:
record.name = f"{record.contract_code}{contract_dict.get(record.contract_type)}{record.property_name}"
elif record.contract_code and record.property_name:
record.name = f"{record.contract_code}{record.property_name}"
elif record.name:
record.name = record.property_name
else:
record.name = self._description
lease_name = []
if record.lease_ids:
for lease in record.lease_ids:
if lease.address_id and lease.address_id.name:
lease_name.append(str(lease.address_id.name))
record.write({
'property_name': ','.join(lease_name) if lease_name else ''
})
@api.constrains("contract_code")
def _check_contract_code(self):
"""检查合同编号的唯一性(不区分中英文括号)"""
if self.env.context.get('skip_contract_code_check'):
return
for record in self:
normalized_code = self._normalize_contract_code(record.contract_code)
existing_records = self.search([
('id', '!=', record.id),
('contract_code', '!=', False),
('version', '=', record.version),
('contract_type', '=', record.contract_type),
])
for existing in existing_records:
if self._normalize_contract_code(existing.contract_code) == normalized_code:
raise ValidationError(_(
f"合同编号必须唯一!已存在编号:{existing.contract_code}",
))
@api.onchange("contract_sort")
def _onchange_contract_sort(self):
if not self.contract_sort:
return
current_year = fields.Date.today().year
# 定义各类型前缀和编号长度
type_config = {
'guanggao': {'prefix': '广', 'length': 3}, # 广告位:3位数字
'guanzi': {'prefix': '管', 'length': 3}, # 管子间:3位数字
'property': {'prefix': '租', 'length': 4}, # 物业:4位数字
'room': {'prefix': '租', 'length': 4}, # 机房:4位数字
'base_station': {'prefix': '租', 'length': 4}, # 基站:4位数字
'other': {'prefix': '租', 'length': 4} # 其他:4位数字
}
config = type_config.get(self.contract_sort)
if not config:
return
prefix = config['prefix']
num_length = config['length']
# 正则模式:前缀 + (年份) + 固定长度数字(如 租(2025)0001 或 广(2025)001)
patterns = [
re.compile(rf"^{re.escape(prefix)}\({current_year}\)(\d{{{num_length}}})$"),
re.compile(rf"^{re.escape(prefix)}({current_year})(\d{{{num_length}}})$")
]
max_num = 0
if hasattr(self.id, 'origin'):
exclude_id = self.id.origin
else:
exclude_id = self.id
same_type_contracts = self.env['property.lease.contract'].search([
('term_start_time', '>=', date(2025, 1, 1)),
('id', '!=', exclude_id)
])
for contract in same_type_contracts:
if not contract.contract_code:
continue
# 检查是否匹配两种格式中的任意一种
for pattern in patterns:
match = pattern.match(contract.contract_code)
if match:
# 提取数字部分并比较
num = int(match.group(1))
if num > max_num:
max_num = num
break
new_num = max_num + 1
formatted_num = f"{new_num:0{num_length}d}"
self.contract_code = f"{prefix}({current_year}){formatted_num}"
def _normalize_contract_code(self, code):
"""将合同编号中的括号统一为中文括号"""
if not code:
return code
# 替换中英文括号为统一格式
code = re.sub(r'[(]', '(', code) # 替换左括号
code = re.sub(r'[)]', ')', code) # 替换右括号
return code
def action_copy_record1(self):
lease_contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for record in lease_contract_ids:
if len(lease_contract_ids) > 1:
raise UserError("只能选择一个拆分")
record.is_contract = True
record.is_child_contract = False
record.is_split = True
record.number += 1
contract_id = self.env['property.lease.contract'].create({
'property_name': record.property_name,
'tenure_type': record.tenure_type,
'is_child_contract': True,
'is_contract': False,
'contract_source_id': record.id,
'actual_start_time': record.actual_start_time,
'actual_end_time': record.actual_end_time,
'contract_code': record.contract_code+ f'-{record.number}',
'contract_date': record.contract_date,
'lessee_id': record.lessee_id.id,
'use_address': record.use_address,
'condition':'new',
'admin_id': record.admin_id.id,
'lessor_id':record.lessor_id.id,
'rental_purposes': record.rental_purposes.id,
'address_for_delivery': record.address_for_delivery,
# 'lease_term': record.lease_term,
'lease_term_year': record.lease_term_year,
'lease_term_month': record.lease_term_month,
'term_end_time': record.term_end_time,
'cycle_dict': record.cycle_dict,
'cycle_mode': record.cycle_mode,
'increment_method': record.increment_method,
# 'increment_percentage': record.increment_percentage,
# 'increment_price': record.increment_price,
# 'increment_amount': record.increment_amount,
'increment_value': record.increment_value,
'first_years_begin': record.first_years_begin,
'increments_every_few_years': record.increments_every_few_years,
'rent_free_period': record.rent_free_period,
'rent_free_period_month': record.rent_free_period_month,
'rent_free_period_days': record.rent_free_period_days,
'breach': record.breach,
'percentage_liquidated_damages': record.percentage_liquidated_damages.id,
'first_pay': record.first_pay,
'unit_price': record.unit_price,
'deposit_type': record.deposit_type,
'performance_bonds_write': record.performance_bonds_write,
'first_year_rent': record.first_year_rent,
'deposit': record.deposit,
'rent': record.rent,
'total_contract_receivables': record.total_contract_receivables,
'tax_rate': record.tax_rate.id,
'not_tax_amount': record.not_tax_amount,
'tax': record.tax,
'is_water': record.is_water,
'is_electricity': record.is_electricity,
'miscellaneous': record.miscellaneous,
'total_area': record.total_area,
'contract_type': record.contract_type,
})
lease_list = []
for lease_id in record.lease_ids:
lease_list.append([0, 0, {
'lease_id':contract_id.id,
'address_id':lease_id.address_id.id,
'evaluate_unit_price': lease_id.evaluate_unit_price,
}])
other_list = []
if record.other_ids:
for other_id in record.other_ids:
other_list.append([0, 0, {
'other_id': contract_id.id,
'charges': other_id.charges,
'coping': other_id.coping.id,
'coping_time': other_id.coping_time,
}])
rent_list = []
if record.breakdown_ids:
rent_list.append([0, 0, {
'contract_id': contract_id.id,
}])
subset_list = []
subset_list.append([0, 0, {
'subset_id': record.id,
'contract_id': contract_id.id}])
record.subset_ids = subset_list
contract_id.write({
'lease_ids': lease_list,
'other_ids': other_list,})
contract_id.onchange_lease_ids()
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'success',
'message': '拆分成功',
'sticky': False,
}
}
def disposable_button(self):
property_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for record in property_ids:
admin_id = record.admin_id
if admin_id:
record.write({
'users_id': admin_id.id,
'company_id' : admin_id.employee_id.company_id.id,
})
def choose_property_button(self):
address_ids = self.lease_ids.mapped('address_id').filtered(lambda r: r)
property_ids2 = address_ids.ids
return {
"type": "ir.actions.act_window",
"name": "批量选择物业信息",
"res_model": "choose.property.wizard",
"view_mode": "form",
"target": "new",
"context": {
'default_lease_contract_id': self.id,
'default_property_ids2': property_ids2,
}
}
@api.depends('term_start_time', 'term_end_time')
def _compute_progress_percentage(self):
for record in self:
if record.term_start_time and record.term_end_time:
# 将字段值从 Date 类型转换为 datetime 类型(默认为 00:00:00 时间)
start_time = fields.Date.from_string(record.term_start_time)
end_time = fields.Date.from_string(record.term_end_time)
current_time = fields.Date.today()
total_duration = (end_time - start_time).days
elapsed_time = (current_time - start_time).days
if current_time < start_time:
record.progress_percentage = 0
# 如果总天数差不为零,则计算进度百分比
elif total_duration > 0:
record.progress_percentage = (elapsed_time / total_duration) * 100
else:
record.progress_percentage = 0
else:
record.progress_percentage = 0
@api.onchange("name")
def _onchange_name(self):
if self.property_name:
self.address_for_delivery = self.property_name
@api.onchange("admin_id")
def _onchange_admin_id(self):
if self.admin_id:
self.users_id = self.admin_id.user_id.id
@api.onchange("actual_start_time", "actual_end_time", "term_end_time")
def _onchange_actual_start_time(self):
for record in self:
record.contract_time()
def get_property_contract_time(self):
records_only_in_tree1 = self.env['property.lease.contract'].search([('state', '!=', 'settle')])
for record in records_only_in_tree1:
record.contract_time()
def contract_time(self):
today = fields.Date.today()
for contract in self:
# 获取时间字段
actual_start = contract.actual_start_time
actual_end = contract.actual_end_time
term_end = contract.term_end_time
effective_month = None
if actual_end:
# 获取实际结束日期所在月的最后一天
last_day_of_month = calendar.monthrange(actual_end.year, actual_end.month)[1]
if actual_end.day == last_day_of_month:
# 如果是最后一天,生效月份为下月
effective_month = fields.Date.to_date(f"{actual_end.year}-{actual_end.month}-{last_day_of_month}")
else:
# 如果不是最后一天,生效月份为当月
effective_month = fields.Date.to_date(f"{actual_end.year}-{actual_end.month}-01")
# 根据时间条件更新状态
if not actual_start or today < actual_start:
# 待执行:实际开始时间未到或未设置
contract.contact_state = 'to'
elif actual_start and (not actual_end or today < actual_end):
# 执行中:已开始且未结束(或结束时间未设置)
contract.contact_state = 'ing'
elif today >= actual_end == term_end:
contract.contact_state = 'complete'
elif effective_month and today >= effective_month < term_end:
contract.contact_state = 'terminate'
def update_contract_state_task(self):
executing_contracts = self.search([('contact_state', '=', 'ing')])
for contract in executing_contracts:
contract.update_contact_state()
def update_contact_state(self):
for contract in self:
receivable_records = self.env['property.accounts.receivable'].search([
('contract_id', '=', contract.id),
])
deposit_record = receivable_records.filtered(
lambda r: r.management_type_id.name == '押金'
)
other_records = receivable_records - deposit_record
all_other_unpaid_zero = all(other.submitted == 0 for other in other_records)
if deposit_record.has_date and all_other_unpaid_zero:
contract.write({'contact_state': 'settle'})
higher_version_contract = self.search([
('contract_code', '=', contract.contract_code),
('version', '>', contract.version),
], order='version DESC', limit=1)
if higher_version_contract:
deposit_record.copy({
'contract_id': higher_version_contract.id,
'has_date': None
})
@api.onchange('actual_start_time', 'actual_end_time')
def _onchange_contact_state(self):
for contract in self:
today = fields.Date.today()
term_end = contract.term_start_time
actual_start = contract.actual_start_time
actual_end = contract.actual_end_time
effective_month = None
if actual_end:
last_day_of_month = calendar.monthrange(actual_end.year, actual_end.month)[1]
if actual_end.day == last_day_of_month:
effective_month = fields.Date.to_date(f"{actual_end.year}-{actual_end.month}-{last_day_of_month}")
if effective_month.month == 12:
effective_month = fields.Date.to_date(f"{effective_month.year + 1}-01-01")
else:
effective_month = fields.Date.to_date(f"{effective_month.year}-{effective_month.month + 1}-01")
else:
effective_month = fields.Date.to_date(f"{actual_end.year}-{actual_end.month}-01")
if not actual_start or today < actual_start:
# 待执行:实际开始时间未到或未设置
contract.contact_state = 'to'
elif actual_start and (not actual_end or today < actual_end):
# 执行中:已开始且未结束(或结束时间未设置)
contract.contact_state = 'ing'
elif today >= actual_end == term_end:
contract.contact_state = 'complete'
elif effective_month and today >= effective_month and effective_month < term_end:
contract.contact_state = 'terminate'
# 续租
def button_lease_extension(self):
lease_contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
if len(lease_contract_ids) > 1:
raise UserError("只能单个操作合同续租")
for original_contract in lease_contract_ids:
record_id = self.env['property.lease.contract'].search([('contract_id', '=', original_contract.id)])
if record_id:
raise UserError("该合同已续租,请勿重复操作")
new_contract = original_contract.with_context(skip_contract_code_check=True).copy()
new_term_start = original_contract.term_end_time + timedelta(
days=1) if original_contract.term_end_time else False
if not lease_contract_ids.contract_sort:
new_contract.write({
'contract_sort': 'property'
})
new_contract.write({
'admin_id': original_contract.admin_id,
'code': self.env['ir.sequence'].next_by_code('property_lease_contract_code'),
'contract_code': None,
'work_end': False,
'lease_term_year': None,
'term_start_time': new_term_start,
'self_users_ids': [(6, 0, self.env.user.ids)],
'term_end_time': None,
'condition':'new',
'contract_id': original_contract.id,
'contract_type': 'lease_renewal'
})
water_lines = self.env['property.water.fee.line'].search([
('contract_id', '=', original_contract.id)
])
for line in water_lines:
line.copy({'contract_id': new_contract.id})
# 复制电费明细行
electric_lines = self.env['property.electric.fee.line'].search([
('contract_id', '=', original_contract.id)
])
for line in electric_lines:
line.copy({'contract_id': new_contract.id})
# 复制物业管理费明细行
manage_lines = self.env['property.management.breakdown.line'].search([
('breakdown_id', '=', original_contract.id)
])
for line in manage_lines:
line.copy({'breakdown_id': new_contract.id})
# 复制其他费用明细行
other_lines = self.env['property.other.fee.line'].search([
('other_id', '=', original_contract.id)
])
for line in other_lines:
line.copy({'other_id': new_contract.id})
# 复制子合同明细
subset_lines = self.env['property.subset.contract'].search([
('subset_id', '=', original_contract.id)
])
for line in subset_lines:
line.copy({'subset_id': new_contract.id})
lease_lines = self.env['property.lease.contract.line'].search([
('lease_id', '=', original_contract.id)
])
for line in lease_lines:
line.copy({'lease_id': new_contract.id})
new_contract._compute_name()
new_contract._onchange_contract_sort()
return {
'type': 'ir.actions.act_window',
'name': '续期合同',
'res_model': 'property.lease.contract',
'view_mode': 'form',
'res_id': new_contract.id,
'target': 'current',
}
# 合同补充
def button_lease_supplement(self):
lease_contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
if len(lease_contract_ids) > 1:
raise UserError("只能单个操作合同补充")
version_str = lease_contract_ids.version.lstrip('V')
main_version = version_str.split('.')[0]
new_main_version = int(main_version) + 1
new_version = f"V{new_main_version}.0"
record_id = self.env['property.lease.contract'].search([('contract_code', '=', lease_contract_ids.contract_code),
('version', '=', new_version)
])
if record_id:
raise UserError("该合同已有补充协议审批,请勿重复操作")
new_contract = lease_contract_ids.with_context(skip_contract_code_check=True).copy()
if not lease_contract_ids.contract_sort:
new_contract.write({
'contract_sort': 'property'
})
new_contract.write({
'work_end': False,
'version': new_version,
'condition': 'new',
'code': self.env['ir.sequence'].next_by_code('property_lease_contract_code'),
'self_users_ids': [(6, 0, [self.env.user.id])],
'contact_state': 'to',
})
water_lines = self.env['property.water.fee.line'].search([
('contract_id', '=', lease_contract_ids.id)
])
for line in water_lines:
line.copy({'contract_id': new_contract.id})
# 复制电费明细行
electric_lines = self.env['property.electric.fee.line'].search([
('contract_id', '=', lease_contract_ids.id)
])
for line in electric_lines:
line.copy({'contract_id': new_contract.id})
# 复制物业管理费明细行
manage_lines = self.env['property.management.breakdown.line'].search([
('breakdown_id', '=', lease_contract_ids.id)
])
for line in manage_lines:
line.copy({'breakdown_id': new_contract.id})
# 复制其他费用明细行
other_lines = self.env['property.other.fee.line'].search([
('other_id', '=', lease_contract_ids.id)
])
for line in other_lines:
line.copy({'other_id': new_contract.id})
# 复制子合同明细
subset_lines = self.env['property.subset.contract'].search([
('subset_id', '=', lease_contract_ids.id)
])
for line in subset_lines:
line.copy({'subset_id': new_contract.id})
lease_lines = self.env['property.lease.contract.line'].search([
('lease_id', '=', lease_contract_ids.id)
])
for line in lease_lines:
line.copy({'lease_id': new_contract.id})
return {
'type': 'ir.actions.act_window',
'name': '合同补充',
'res_model': 'property.lease.contract',
'view_mode': 'form',
'res_id': new_contract.id,
'target': 'current',
}
# 变更
def button_lease_change(self):
form_id = self.env.ref('yuthon_property_approval.view_property_lease_renewal_form').id
address_ids = self.lease_ids.mapped('address_id').ids
return {
'name': '变更申请',
"type": "ir.actions.act_window",
'view_mode': 'form',
'res_model': 'property.lease.renewal',
"views": [[form_id, "form"]],
"context": {
'default_renewal_type': 'lease',
'default_contract_id': self.id,
'default_property_ids': [(6, 0, address_ids)] if address_ids else False
},
}
def read_coming_count(self):
return self.search_count([('expiration_coming', '=', 'coming'), ('admin_id', '=', self.env.user.id)])
def read_due_count(self):
return self.search_count([('expiration_coming', '=', 'due'), ('admin_id', '=', self.env.user.id)])
def put_evaluation_plan(self):
for record in self:
property_ids = []
rent_area = 0
first_address_id = record.lease_ids[0].admin_line_id if record.lease_ids else None
for lease_id in record.lease_ids:
property_ids.append(lease_id.address_id.id)
rent_area += lease_id.address_id.rent_area
self.env['property.evaluation.plan'].create({
'property_name': record.property_name,
'property_ids': [(6, 0, property_ids)],
'evaluation_area': rent_area,
'owner_id': first_address_id.title_information_ids[0].owner_id.id if first_address_id and first_address_id.title_information_ids else None,
'registered_id': first_address_id.title_information_ids[0].registered_id.id if first_address_id and first_address_id.title_information_ids else None,
'rent_id': first_address_id.reference_ids[0].rent_id.id if first_address_id and first_address_id.reference_ids else None,
'lease_period': first_address_id.reference_ids[0].lease_period if first_address_id and first_address_id.reference_ids else None,
'annual_increase_percentage': first_address_id.reference_ids[0].annual_increase_percentage if first_address_id and first_address_id.reference_ids else None,
'remake': first_address_id.remark if first_address_id else None,
'assessment_category': 'rent_money',
})
record.write({'contact_state': 'ing'})
return {
"name": "物业评估方案",
'type': 'ir.actions.act_window',
'res_model': 'property.evaluation.plan',
"view_mode": "tree",
}
def all_put_evaluation_plan(self):
contract_ids = self.env["property.lease.contract"].browse(self._context.get('active_ids', self._context.get('active_id')))
for record in contract_ids:
property_ids = []
property_id = record.lease_ids[0].admin_line_id
if record.contact_state == 'done':
raise UserError(f'{record.property_name}该物业已评估,请勿重复评估')
rent_area = 0
for lease_id in record.lease_ids:
property_ids.append(lease_id.address_id.id)
rent_area += lease_id.address_id.rent_area
self.env['property.evaluation.plan'].create({
'property_name': record.property_name,
'property_ids': property_ids,
'evaluation_area': property_id.rent_area,
'owner_id': property_id.title_information_ids[0].owner_id.id if property_id.title_information_ids else None,
'registered_id': property_id.title_information_ids[0].registered_id.id if property_id.title_information_ids else None,
'rent_id': property_id.reference_ids[0].rent_id.id if property_id.reference_ids else None,
'lease_period': property_id.reference_ids[0].lease_period if property_id.reference_ids else None,
'annual_increase_percentage': property_id.reference_ids[0].annual_increase_percentage if property_id.reference_ids else None,
'remake': property_id.remark,
'assessment_category': 'rent_money',
})
record.write({'contact_state': 'ing'})
return {
"name": "物业评估方案",
'type': 'ir.actions.act_window',
'res_model': 'property.evaluation.plan',
"view_mode": "tree",
}
def print_payment_notice(self):
contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
return {
"name": "打印缴费通知单",
'type': 'ir.actions.act_window',
'res_model': 'print.payment.wizard',
"view_mode": "form",
'target': 'new',
'context': {
'default_contract_ids': contract_ids.ids,
}
}
def print_contract(self):
return {
"name": "打印合同",
'type': 'ir.actions.act_window',
'res_model': 'print.contract.wizard',
"view_mode": "form",
'target': 'new',
'context': {
'default_contract_id': self.id,
}
}
def action_print_lease_contract(self):
"""打印租赁合同审批单"""
report_ref = self.env.ref('yuthon_property_approval.action_property_lease_contract_print_report')
return report_ref.report_action(self)
def _get_property_notify_users(self, task_config):
"""从 task_config 获取需要通知的用户列表.
- user_ids: 直接指定的人
- users_role_id + users_role_group_ids: 角色组 -> 角色 -> 花名册中 user_role_id(主角色)或 user_role_ids(辅助角色)匹配的员工
"""
user_ids = set(task_config.user_ids.ids)
role_ids = set(task_config.users_role_group_ids.mapped('user_role_ids').ids)
if task_config.users_role_id:
role_ids.add(task_config.users_role_id.id)
if role_ids:
all_target_roles = self.env['soong.user.role'].browse(list(role_ids))
role_employees = self.env['hr.employee'].search([
'|',
('user_role_id', 'in', all_target_roles.ids),
('user_role_ids', 'in', all_target_roles.ids),
('user_id', '!=', False),
])
for emp in role_employees:
user_ids.add(emp.user_id.id)
return self.env['res.users'].browse(list(user_ids))
def task_property_vacant_reminder(self):
"""物业空置提醒:以物业为单位,合同实际结束日期距今已3个月或以上,或没有合同."""
today = fields.Date.today()
three_months_ago = today - timedelta(days=90)
properties = self.env['yuthon.property'].search([])
vacant_properties = self.env['yuthon.property']
for prop in properties:
contract_lines = self.env['property.lease.contract.line'].search([
('address_id', '=', prop.id),
])
if not contract_lines:
vacant_properties |= prop
continue
contracts = contract_lines.mapped('lease_id')
active_contracts = contracts.filtered(
lambda c: c.contact_state not in ['complete', 'terminate', 'early_termination', 'settle']
)
if active_contracts:
continue
ended_contracts = contracts.filtered(
lambda c: c.contact_state in ['complete', 'terminate', 'early_termination', 'settle'] and c.actual_end_time
)
if not ended_contracts:
continue
last_end_time = max(ended_contracts.mapped('actual_end_time'))
if last_end_time <= three_months_ago:
vacant_properties |= prop
if not vacant_properties:
return
task_config = self.env['yuthon.property.task'].search([('cron_id.name', '=', '定时任务:物业空置提醒')], limit=1)
if not task_config:
return
notify_users = self._get_property_notify_users(task_config)
if not notify_users:
return
count = len(vacant_properties)
for user in notify_users:
hr_employee = user.employee_ids.filtered(lambda e: e.wecom_userid)[:1]
if not hr_employee:
continue
result_data = self.sudo().env['wecom.apps'].sync_send_message_textcard(
category='oa',
user_id=hr_employee.wecom_userid,
title='物业空置提醒',
description=f"提醒:有{count}个物业已空置,请移至工作台查看。",
url='https://oa.thtzjt.com/web?debug=1#action=3584&cids=1&menu_id=9668',
btntxt='详细信息',
)
confirm = self.env['yuthon.property.task.confirm'].create({
'task_id': task_config.id,
'user_id': user.id,
'state': 'read',
})
if result_data and len(result_data) > 1:
msg_id = result_data[1].get('msgid')
if msg_id:
confirm.sudo().write({'msg_id': msg_id})
def task_property_contract_reminder(self):
"""物业合同到期提醒:距实际结束日期还有3个月."""
today = fields.Date.today()
target_date = today + timedelta(days=90)
contracts = self.search([
('actual_end_time', '=', target_date),
('contact_state', 'in', ['to', 'ing']),
])
if not contracts:
return
task_config = self.env['yuthon.property.task'].search([('cron_id.name', '=', '定时任务:物业合同到期提醒')], limit=1)
if not task_config:
return
notify_users = self._get_property_notify_users(task_config)
if not notify_users:
return
count = len(contracts)
for user in notify_users:
hr_employee = user.employee_ids.filtered(lambda e: e.wecom_userid)[:1]
if not hr_employee:
continue
result_data = self.sudo().env['wecom.apps'].sync_send_message_textcard(
category='oa',
user_id=hr_employee.wecom_userid,
title='物业合同到期提醒',
description=f"提醒:有{count}个物业即将到期,请移至工作台查看。",
url='https://oa.thtzjt.com/web?debug=1#action=3584&cids=1&menu_id=9668',
btntxt='详细信息',
)
confirm = self.env['yuthon.property.task.confirm'].create({
'task_id': task_config.id,
'user_id': user.id,
'state': 'read',
})
if result_data and len(result_data) > 1:
msg_id = result_data[1].get('msgid')
if msg_id:
confirm.sudo().write({'msg_id': msg_id})
class PropertyLeaseContractLine(models.Model):
_name = 'property.lease.contract.line'
_description = '物业列表明细行'
lease_id = fields.Many2one('property.lease.contract', string="关联凭租合同审批")
confirm_id = fields.Many2one('property.confirm', string="关联成交确认审批")
construction_id = fields.Many2one('property.construction.approval', string="关联建设工程造价咨询合同审批")
project_id = fields.Many2one('property.project.approval', string="关联工程立项")
design_id = fields.Many2one('property.design.approval', string="关联建设工程设计合同审批")
accounting_id = fields.Many2one('property.accounting.approval', string="关联建设工程委托监理合同审批")
supervising_id = fields.Many2one('property.supervising.approval', string="关联建设工程施工合同审批")
open_rent_id = fields.Many2one('property.open.rent', string="关联公开招租审批")
address_id = fields.Many2one('yuthon.property', string="物业地址")
tax = fields.Float(related='address_id.tax',string="税率")
admin_line_id = fields.Many2one(related='address_id.admin_id', string="管理员", store=True)
property_area = fields.Float(string="可租面积", compute="_compute_property_area")
message_standard_id = fields.Many2one(related='address_id.message_standard_id', string="管理费标准", store=True)
message_unit = fields.Float(related='message_standard_id.amount', string="管理费单价", store=True)
evaluate_unit_price = fields.Float(string="评估单价")
@api.depends('address_id', 'address_id.manage_information_ids')
def _compute_property_area(self):
for line in self:
line.property_area = 0.0
if line.address_id and line.address_id.manage_information_ids:
latest_line = line.address_id.manage_information_ids.sorted('id', reverse=True)[0]
line.property_area = latest_line.rent_area
def open_record(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': '物业信息',
'res_model': 'yuthon.property',
'res_id': self.address_id.id,
'view_mode': 'form',
'target': 'current',
}
class PropertyRentBreakdownLine(models.Model):
_name = 'property.rent.breakdown.line'
_description = '物业租金明细行'
breakdown_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
renewal_id = fields.Many2one('property.lease.renewal', string="关联租赁变更审批")
open_rent_id = fields.Many2one('property.open.rent', string="关联公开招租审批")
charges = fields.Char(string="收费项")
unit_price = fields.Float(string="单价")
monthly_amount = fields.Float(string="月租金", digits=(16, 2))
start_data = fields.Date(string="开始日期")
end_data = fields.Date(string="结束日期")
month_number = fields.Integer(string="期间月数")
period_rent = fields.Float(string="期间租金", digits=(16, 2))
@api.onchange('monthly_amount', 'month_number')
def _onchange_water_amount(self):
self.period_rent = self.monthly_amount * self.month_number
class PropertyWaterBreakdownLine(models.Model):
_name = 'property.water.breakdown.line'
breakdown_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
water_unit_price = fields.Float(string="单价")
water_monthly_amount = fields.Float(string="金额", digits=(16, 2))
start_data = fields.Date(string="开始日期")
end_data = fields.Date(string="结束日期")
water_month_number = fields.Integer(string="期间月数")
water_period_rent = fields.Float(string="期间费用", digits=(16, 2))
@api.onchange('water_monthly_amount', 'month_number')
def _onchange_water_amount(self):
self.water_period_rent = self.water_monthly_amount * self.water_month_number
class PropertyEleBreakdownLine(models.Model):
_name = 'property.ele.breakdown.line'
breakdown_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
ele_unit_price = fields.Float(string="单价")
ele_monthly_amount = fields.Float(string="金额", digits=(16, 2))
start_data = fields.Date(string="开始日期")
end_data = fields.Date(string="结束日期")
ele_month_number = fields.Integer(string="期间月数")
ele_period_rent = fields.Float(string="期间费用", digits=(16, 2))
@api.onchange('ele_monthly_amount', 'month_number')
def _onchange_electricity_amount(self):
self.ele_period_rent = self.ele_monthly_amount * self.ele_month_number
class PropertyManagementBreakdownLine(models.Model):
_name = 'property.management.breakdown.line'
breakdown_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
management_unit_price = fields.Float(string="单价")
management_monthly_amount = fields.Float(string="金额", digits=(16, 2))
start_data = fields.Date(string="开始日期")
end_data = fields.Date(string="结束日期")
management_month_number = fields.Float(string="期间月数", digits=(16, 2))
management_period_rent = fields.Float(string="期间费用", digits=(16, 2))
@api.onchange('start_data', 'end_data')
def _onchange_management_month_number(self):
if self.start_data and self.end_data:
if self.end_data >= self.start_data:
total_days = (self.end_data - self.start_data).days + 1
start_month_days = calendar.monthrange(self.start_data.year, self.start_data.month)[1]
self.management_month_number = round(total_days / start_month_days, 2)
else:
self.management_month_number = 0
else:
self.management_month_number = 0
@api.onchange('management_monthly_amount', 'management_month_number')
def _onchange_management_amount(self):
self.management_period_rent = round(self.management_monthly_amount * self.management_month_number, 2)
class PropertyWaterFeeLine(models.Model):
_name = 'property.water.fee.line'
_description = '物业水表库明细行'
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
property_meter_water_id = fields.Many2one('property.meter.water', string="水表库")
rate = fields.Integer(related='property_meter_water_id.gauge_magnification', string="倍率", store=True)
unit_number = fields.Char(related='property_meter_water_id.unit_number', string="单元编号/用途", store=True)
start_date = fields.Date(related='contract_id.term_start_time', string="开始日期")
end_date = fields.Date(related='contract_id.term_end_time', string="结束日期")
unit_price = fields.Float(string='固定单价', digits=(16, 4))
line_loss = fields.Float(string='固定线损', digits=(16, 4))
is_fix_loss = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定线损")
is_fix_price = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定单价")
epitope_values_id = fields.Many2one(related='property_meter_water_id.epitope_values_id', string="表位置")
@api.onchange('is_fix_price')
def _onchange_is_fix_price(self):
if self.is_fix_price == 'no':
self.unit_price = 0
@api.onchange('is_fix_loss')
def _onchange_is_fix_loss(self):
if self.is_fix_loss == 'no':
self.line_loss = 0
class PropertyElectricFeeLine(models.Model):
_name = 'property.electric.fee.line'
_description = '物业电表信息明细行'
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
property_meter_archives_id = fields.Many2one('property.meter.archives', string="电表编号")
rate = fields.Integer(related='property_meter_archives_id.gauge_magnification', string="倍率", store=True)
unit_number = fields.Char(related='property_meter_archives_id.unit_number', string="单元编号/用途", store=True)
start_date = fields.Date(related='contract_id.term_start_time', string="开始日期")
end_date = fields.Date(related='contract_id.term_end_time', string="结束日期")
unit_price = fields.Float(string='固定单价', digits=(16, 4))
line_loss = fields.Float(string='固定线损', digits=(16, 4))
is_fix_loss = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定线损")
is_fix_price = fields.Selection([('yes', '是'),('no', '否')], default="no", string="是否固定单价")
epitope_values_id = fields.Many2one(related='property_meter_archives_id.epitope_values_id', string="表位置")
@api.onchange('is_fix_price')
def _onchange_is_fix_price(self):
if self.is_fix_price == 'no':
self.unit_price = 0
@api.onchange('is_fix_loss')
def _onchange_is_fix_loss(self):
if self.is_fix_loss == 'no':
self.line_loss = 0
class PropertyOtherFeeLine(models.Model):
_name = 'property.other.fee.line'
_description = '物业其他费用明细行'
other_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
charges = fields.Char(string="收费项")
coping = fields.Float(string="应收")
coping_time = fields.Date(string="应收日期")
class PropertySubsetContract(models.Model):
_name = 'property.subset.contract'
_description = '物业子合同明细'
subset_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
contract_id = fields.Many2one('property.lease.contract', string="合同")
name = fields.Char(related='contract_id.name', string="物业信息")
lessee_id = fields.Many2one(related='contract_id.lessee_id', string="承租方")
rental_purposes = fields.Many2one(related='contract_id.rental_purposes', string="出租用途")
class LesseeAlterationLine(models.Model):
_name = 'lessee.alteration.line'
_description = '承租方变更明细行'
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
lessee_id = fields.Many2one('property.tenant.information', string='承租方')
start_date = fields.Date(string="生效日期", default=fields.Datetime.today())
end_date = fields.Date(string="失效日期")
class LessorAlterationLine(models.Model):
_name = 'lessor.alteration.line'
_description = '出租方变更明细行'
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
lessor_id = fields.Many2one('yuthon.property.company', string="出租方")
start_date = fields.Date(string="生效日期", default=fields.Datetime.today())
end_date = fields.Date(string="失效日期")