Files
gzth/yuthon_property_approval/models/property_management.py
T
2026-07-24 16:09:00 +08:00

367 lines
19 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.
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'