367 lines
19 KiB
Python
367 lines
19 KiB
Python
# -*- coding: utf-8 -*-
|
||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
|
||
from odoo import models, fields, api, exceptions, _
|
||
from datetime import timedelta
|
||
from dateutil.relativedelta import relativedelta
|
||
import calendar
|
||
import re
|
||
from datetime import datetime, date
|
||
|
||
|
||
class PropertyManagement(models.Model):
|
||
_name = 'property.management'
|
||
_description = '物业管理变更'
|
||
_inherit = ['mail.thread.main.attachment', 'mail.activity.mixin', 'soong.workflow.mixin']
|
||
_rec_name = 'name'
|
||
|
||
name = fields.Char(string='标题')
|
||
code = fields.Char(string="编号",
|
||
default=lambda self: self.env['ir.sequence'].next_by_code('property_management_code'))
|
||
reason_change = fields.Char(string="变更事由")
|
||
user_id = fields.Many2one('res.users', string="申请人", default=lambda self: self.env.user)
|
||
department_id = fields.Many2one('hr.department', related='user_id.employee_id.department_id',string="申请人部门", store=True)
|
||
handling_opinions = fields.Text(string="经办意见")
|
||
management_line2_ids = fields.One2many('property.management.line2', 'property_management_id', string="原物业信息")
|
||
management_line_ids = fields.One2many('property.management.line', 'property_management_id', string="物业信息")
|
||
urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度',
|
||
default='normal')
|
||
is_issues = fields.Boolean(string='是否上会')
|
||
is_line = fields.Boolean(string='明细行隐藏')
|
||
issues_result = fields.Html(string="会议结果")
|
||
related_issues_id = fields.Many2one('issue.library', string="关联议题")
|
||
active = fields.Boolean('Active', default=True, tracking=True)
|
||
work_end = fields.Boolean(string="结束")
|
||
|
||
company_id = fields.Many2one('yuthon.property.company', string="公司")
|
||
purpose_id = fields.Many2one('yuthon.resource.purpose', string="管理类型")
|
||
admin_id_all = fields.Many2one('res.users', string="管理员")
|
||
precinct_id = fields.Many2one('yuthon.precinct.message', string="所属片区")
|
||
tenure_type = fields.Selection([('self', '自有'), ('agency', '代管')], string='权属类型')
|
||
start_time = fields.Date(string="生效日期", default=lambda self: fields.Date.today())
|
||
|
||
def _get_default_name_title(self):
|
||
return self._description if self._description else self._name
|
||
|
||
name_title = fields.Char(default=_get_default_name_title)
|
||
document_ids = fields.Many2many('ir.attachment', string="附件")
|
||
is_generate_new_contract = fields.Boolean(string='是否生成新合同', default=False)
|
||
self_users_ids = fields.Many2many('res.users', string="当前经办人", default=lambda self: [(6, 0, [self.env.user.id])])
|
||
|
||
@api.onchange('precinct_id')
|
||
def _onchange_precinct_id(self):
|
||
if self.precinct_id:
|
||
self.admin_id_all = self.precinct_id.employee_id.user_id.id
|
||
|
||
@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(),
|
||
})
|
||
|
||
@api.onchange('management_line2_ids')
|
||
def _onchange_management_line2_ids(self):
|
||
self.management_line_ids = [(5, 0, 0)]
|
||
for line in self.management_line2_ids:
|
||
if line.property_id:
|
||
self.management_line_ids = [(0, 0, {
|
||
'property_id': line.property_id.id,
|
||
'company_id': line.company_id.id,
|
||
'purpose_id': line.purpose_id.id,
|
||
'precinct_id': line.precinct_id.id,
|
||
'admin_id_all': line.admin_id_all.id,
|
||
'tenure_type': line.tenure_type
|
||
})]
|
||
|
||
def _generate_and_resolve_contracts(self):
|
||
"""
|
||
如果勾选"生成新合同",为每个物业对应的合同创建新合同副本。
|
||
返回 {old_contract_id: new_contract_id} 映射,没勾选返回空字典
|
||
"""
|
||
new_contract_map = {}
|
||
if not self.is_generate_new_contract:
|
||
return new_contract_map
|
||
|
||
processed = set()
|
||
for manage_line in self.management_line2_ids:
|
||
if not manage_line.property_id:
|
||
continue
|
||
lease_line = self.env['property.lease.contract.line'].search([
|
||
('address_id', '=', manage_line.property_id.id),
|
||
('lease_id', '!=', False),
|
||
], limit=1)
|
||
if not lease_line:
|
||
continue
|
||
old_contract = lease_line.lease_id
|
||
if old_contract.id in processed:
|
||
continue
|
||
processed.add(old_contract.id)
|
||
|
||
# 编号尾部数字+1:租(2023)37号-2 → 租(2023)37号-3
|
||
base_code = old_contract.contract_code or ''
|
||
match = re.match(r'^(.+)-(\d+)$', base_code)
|
||
if match:
|
||
prefix = match.group(1)
|
||
counter = int(match.group(2)) + 1
|
||
else:
|
||
prefix = base_code
|
||
counter = 1
|
||
new_code = f"{prefix}-{counter}"
|
||
while self.env['property.lease.contract'].search(
|
||
[('contract_code', '=', new_code)], limit=1):
|
||
counter += 1
|
||
new_code = f"{prefix}-{counter}"
|
||
|
||
new_contract = old_contract.copy({
|
||
'contract_code': new_code,
|
||
'contract_source_id': old_contract.id,
|
||
})
|
||
new_contract_map[old_contract.id] = new_contract.id
|
||
|
||
# 手动复制物业明细行(copy 未复制 One2many)
|
||
for old_line in old_contract.lease_ids:
|
||
old_line.copy({'lease_id': new_contract.id})
|
||
|
||
# 时间处理
|
||
day_before = self.start_time - timedelta(days=1)
|
||
old_contract.write({'actual_end_time': day_before})
|
||
new_contract.write({'actual_start_time': self.start_time,
|
||
'is_child_contract': True})
|
||
|
||
# 触发原合同状态更新
|
||
old_contract.contract_time()
|
||
new_contract._onchange_action_generate_details()
|
||
|
||
return new_contract_map
|
||
|
||
def action_fix_lessor_alteration(self):
|
||
"""
|
||
修复:删除出租方变更明细(失效日期=2026-01-31、出租方=天河投资、
|
||
合同状态=中止/完成),并将合同出租方改回天河投资。
|
||
"""
|
||
tianhe = self.env['yuthon.property.company'].search(
|
||
[('name', '=', '天河投资')], limit=1)
|
||
if not tianhe:
|
||
return
|
||
|
||
lines = self.env['lessor.alteration.line'].search([
|
||
('end_date', '=', '2026-01-31'),
|
||
('lessor_id', '=', tianhe.id),
|
||
('contract_id.contact_state', 'in', ['terminate', 'complete']),
|
||
])
|
||
contracts_to_fix = lines.mapped('contract_id')
|
||
for contract in contracts_to_fix:
|
||
contract.write({'lessor_id': tianhe.id})
|
||
lines.unlink()
|
||
|
||
def _handle_rent_account_change(self, manage_line, prev_info, new_contract_map):
|
||
"""
|
||
收租公司账号变更时触发:
|
||
① 原合同 → property.bills.list 应付账单(押金)
|
||
② 当前合同 → property.accounts.receivable 押金应收
|
||
勾了生成新合同 → current_contract 直接取 new_contract_map 里的新合同ID
|
||
"""
|
||
if prev_info.yuthon_property_id.company_id.id == self.company_id.id:
|
||
return
|
||
if prev_info.yuthon_property_id.company_id.rent_account == self.company_id.rent_account:
|
||
return
|
||
|
||
# 找现有合同
|
||
lease_lines = self.env['property.lease.contract.line'].search([
|
||
('address_id', '=', manage_line.property_id.id),
|
||
('lease_id', '!=', False),
|
||
('lease_id.actual_end_time', '>', self.start_time),
|
||
], limit=1)
|
||
if not lease_lines:
|
||
return
|
||
|
||
for lease_line in lease_lines:
|
||
old_contract = lease_line.lease_id
|
||
# 勾了新合同 → 直接用新合同ID;没勾 → 用现有合同
|
||
new_cid = new_contract_map.get(old_contract.id)
|
||
current_contract = self.env['property.lease.contract'].browse(new_cid) if new_cid else old_contract
|
||
|
||
# 没有原合同 → original_contract 就是当前合同本身,只生成 bills.list
|
||
if not current_contract.contract_source_id:
|
||
original_contract = current_contract
|
||
deposit_type = self.env['yuthon.management.type'].search(
|
||
[('name', '=', '押金')], limit=1)
|
||
if not deposit_type:
|
||
deposit_type = self.env['yuthon.management.type'].create({'name': '押金'})
|
||
|
||
if original_contract and original_contract.deposit:
|
||
existing_bill = self.env['property.bills.list'].search([
|
||
('contract_id', '=', original_contract.id),
|
||
('management_type_id', '=', deposit_type.id),
|
||
], limit=1)
|
||
if not existing_bill:
|
||
self.env['property.bills.list'].create({
|
||
'contract_id': original_contract.id,
|
||
'amount': original_contract.deposit,
|
||
'due_date': self.start_time,
|
||
'management_type_id': deposit_type.id,
|
||
})
|
||
continue
|
||
|
||
def approval_end(self):
|
||
for record in self:
|
||
new_contract_map = record._generate_and_resolve_contracts()
|
||
for manage_line in record.management_line2_ids:
|
||
# 更新信息行的结束日期
|
||
if manage_line.information_line_id:
|
||
manage_line.information_line_id.write({'end_date': fields.Date.today()})
|
||
# 直接从关联的上一条记录获取值(如果存在)
|
||
prev_info = manage_line.information_line_id
|
||
target_start_time = record.start_time
|
||
target_property_id = manage_line.property_id.id
|
||
|
||
if prev_info.company_id.id != record.company_id.id and record.company_id.id:
|
||
lease_contract = self.env['property.lease.contract.line'].search(
|
||
[('address_id', '=', manage_line.property_id.id),
|
||
('lease_id', '!=', False),
|
||
('lease_id.actual_end_time', '>=', self.start_time),
|
||
], limit=1)
|
||
if lease_contract:
|
||
contract_actual_end = lease_contract.lease_id.actual_end_time
|
||
start_time = record.start_time
|
||
if not contract_actual_end or not start_time:
|
||
continue
|
||
start_date = fields.Date.to_date(start_time)
|
||
last_day = calendar.monthrange(start_date.year, start_date.month)[1]
|
||
month_end_date = date(start_date.year, start_date.month, last_day)
|
||
|
||
contract_actual_end_date = fields.Date.to_date(contract_actual_end) if contract_actual_end else None
|
||
if not contract_actual_end_date or contract_actual_end_date >= month_end_date:
|
||
lease_contract_ids = lease_contract.lease_id
|
||
lease_contract_ids.lessor_id = record.company_id.id
|
||
lease_contract_ids.is_lessor = True
|
||
lease_contract_ids.users_id = record.user_id
|
||
|
||
original_lessor_id = prev_info.company_id.id
|
||
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:
|
||
continue
|
||
lease_contract_ids._compute_name()
|
||
lease_contract_ids._update_wkf_records()
|
||
if (original_lessor_id and lease_contract_ids.actual_start_time and record.start_time):
|
||
new_end_date = record.start_time - relativedelta(days=1)
|
||
domain = [
|
||
('contract_id', '=', lease_contract_ids.id),
|
||
('end_date', '=', new_end_date)
|
||
]
|
||
existing_line = self.env['lessor.alteration.line'].search(domain, limit=1)
|
||
if existing_line:
|
||
existing_line.write({
|
||
'lessor_id': original_lessor_id,
|
||
'end_date': new_end_date
|
||
})
|
||
else:
|
||
self.env['lessor.alteration.line'].create({
|
||
'contract_id': lease_contract_ids.id,
|
||
'lessor_id': original_lessor_id,
|
||
'start_date': lease_contract_ids.actual_start_time,
|
||
'end_date': new_end_date
|
||
})
|
||
lease_contract_ids.version = new_version
|
||
|
||
# 收租账号变更 → 应付账单 + 押金应收
|
||
record._handle_rent_account_change(manage_line, prev_info, new_contract_map)
|
||
|
||
existing_info = self.env['manage.information.line'].search([
|
||
('yuthon_property_id', '=', target_property_id),
|
||
('start_time', '=', target_start_time)
|
||
], limit=1)
|
||
if existing_info:
|
||
existing_info.write({
|
||
'yuthon_property_id': manage_line.property_id.id,
|
||
'purpose_id': record.purpose_id.id if record.purpose_id else prev_info.purpose_id.id,
|
||
'precinct_id': record.precinct_id.id if record.precinct_id else prev_info.precinct_id.id,
|
||
'management_area': prev_info.management_area if prev_info else 0.0,
|
||
'tenure_type': record.tenure_type if record.tenure_type else prev_info.tenure_type,
|
||
'rent_area': prev_info.rent_area if prev_info else 0.0,
|
||
'admin_id': record.precinct_id.employee_id.user_id.id if record.precinct_id else prev_info.admin_id.id,
|
||
'company_id': record.company_id.id if record.company_id else prev_info.company_id.id if prev_info else False,
|
||
'start_time': record.start_time,
|
||
})
|
||
else:
|
||
info_line_vals = {
|
||
'yuthon_property_id': manage_line.property_id.id,
|
||
'purpose_id': record.purpose_id.id if record.purpose_id else prev_info.purpose_id.id,
|
||
'precinct_id': record.precinct_id.id if record.precinct_id else prev_info.precinct_id.id,
|
||
'management_area': prev_info.management_area if prev_info else 0.0,
|
||
'tenure_type': record.tenure_type if record.tenure_type else prev_info.tenure_type,
|
||
'rent_area': prev_info.rent_area if prev_info else 0.0,
|
||
'admin_id': record.precinct_id.employee_id.user_id.id if record.precinct_id else prev_info.admin_id.id,
|
||
'company_id': record.company_id.id if record.company_id else prev_info.company_id.id if prev_info else False,
|
||
'start_time': record.start_time,
|
||
}
|
||
self.env['manage.information.line'].create(info_line_vals)
|
||
prev_info.end_date = record.start_time - timedelta(days=1)
|
||
# 创建新记录
|
||
manage_line.property_id.compute_information_fields()
|
||
record.write({'is_line': True})
|
||
|
||
def put_property_management(self):
|
||
return {
|
||
'name': '物业信息',
|
||
'type': 'ir.actions.act_window',
|
||
'res_model': 'property.management.wizard',
|
||
'view_mode': 'form',
|
||
'target': 'new',
|
||
'context': {'default_property_management_id': self.id}
|
||
}
|
||
|
||
def clear_the_details(self):
|
||
self.management_line_ids.unlink()
|
||
self.management_line2_ids.unlink()
|
||
|
||
# 结果保留明细行
|
||
class PropertyManagementLine(models.Model):
|
||
_name = 'property.management.line'
|
||
_description = '物业信息'
|
||
|
||
property_id = fields.Many2one('yuthon.property', string="物业名称")
|
||
property_management_id = fields.Many2one('property.management', string="关联物业分配申请表")
|
||
information_line_id = fields.Many2one('manage.information.line')
|
||
company_id = fields.Many2one('yuthon.property.company', string="公司")
|
||
purpose_id = fields.Many2one('yuthon.resource.purpose', string="管理类型")
|
||
admin_id_all = fields.Many2one('res.users', string="管理员")
|
||
precinct_id = fields.Many2one('yuthon.precinct.message', string="所属片区")
|
||
tenure_type = fields.Selection([('self', '自有'), ('agency', '代管')], string='权属类型')
|
||
state = fields.Selection(related='property_management_id.state', string='状态')
|
||
|
||
|
||
# 初始明细行
|
||
class PropertyManagementLine2(models.Model):
|
||
_name = 'property.management.line2'
|
||
_description = '物业信息'
|
||
|
||
property_management_id = fields.Many2one('property.management', string="关联物业分配申请表")
|
||
property_id = fields.Many2one('yuthon.property', string="物业名称")
|
||
purpose_id = fields.Many2one('yuthon.resource.purpose', string="管理类型")
|
||
information_line_id = fields.Many2one('manage.information.line')
|
||
tenure_type = fields.Selection(related='property_id.tenure_type', string='权属类型')
|
||
company_id = fields.Many2one(related='property_id.company_id', string="公司")
|
||
admin_id_all = fields.Many2one(related='property_id.admin_id', string="管理员")
|
||
precinct_id = fields.Many2one('yuthon.precinct.message', string="所属片区")
|
||
state = fields.Selection(related='property_management_id.state', string='状态')
|
||
|
||
|
||
class PropertyInformationDetailLines(models.Model):
|
||
_name = 'property.information.detail.lines' |