@@ -2053,6 +2053,22 @@ class SoongWorkflowMixin(models.AbstractModel):
|
||||
return record_id.users_id.employee_id
|
||||
return self.env['hr.employee'] # 默认返回空记录集
|
||||
|
||||
def _get_active_workflow_record(self):
|
||||
"""本单据「未进回收站」的工作流记录(没有则返回空记录集)。
|
||||
|
||||
按 res_model + res_id 匹配;回收站里的记录 reclaim_state=True,不算有效记录。
|
||||
"""
|
||||
self.ensure_one()
|
||||
return self.env['soong.workflow.record'].sudo().search([
|
||||
('res_model', '=', self._name),
|
||||
('res_id', '=', self.id),
|
||||
('reclaim_state', '=', False),
|
||||
], limit=1)
|
||||
|
||||
def _has_active_workflow_record(self):
|
||||
"""本单据是否存在「未进回收站」的工作流记录(回收站里的不算)"""
|
||||
return bool(self._get_active_workflow_record())
|
||||
|
||||
# ---- 公司历史名称展示:web_read 直接改写指向 res.company 的公司字段显示名(见 web_read)----
|
||||
company_id = fields.Many2one('res.company', string='公司')
|
||||
|
||||
|
||||
@@ -806,6 +806,10 @@ class SoongWorkflowCommentWizard(models.TransientModel):
|
||||
title=title + '传阅/抄送,测试提醒',
|
||||
description=description, url=url,
|
||||
btntxt="查看详情")
|
||||
# 转交成功:仅这几类单据需要推进业务状态
|
||||
# (评估申请 -> 物业/方案「评估中」;挂牌申报表 待挂牌 -> 挂牌中;成交确认书 待签约 -> 签约中)
|
||||
if self.res_model in ('property.evaluate.application', 'property.rental.form', 'property.confirm'):
|
||||
record_id._on_workflow_transfer()
|
||||
self._handle_avoidance()
|
||||
if self.node_btn_name == '退回':
|
||||
if not self.return_link_id:
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
'data/data_yuthon_resource_mgmt_config.xml',
|
||||
'data/yuthon_property_approval_data.xml',
|
||||
'data/yuthon_new_property.xml',
|
||||
# 必须早于 yuthon_property_views.xml:物业列表头部按钮用 %(...)d 引用该动作
|
||||
'wizard/property_import_wizard_views.xml',
|
||||
'wizard/property_evaluation_import_wizard_views.xml',
|
||||
'views/yuthon_property_views.xml',
|
||||
'views/property_patrol_duty.xml',
|
||||
'views/property_patrol_plan.xml',
|
||||
|
||||
@@ -54,6 +54,14 @@ class PropertyBankStatements(models.Model):
|
||||
change_amount = fields.Float(string='修改金额')
|
||||
receivable_modify_ids = fields.One2many('receivable.modify.line', 'bank_id', string='修改应收单')
|
||||
|
||||
# 对账(贷)列表排序用:挂账的行排最前面(挂账=0,其余=1),配合「交易时间倒序」使用
|
||||
bank_state_sort = fields.Integer(string='排序(挂账优先)', compute='_compute_bank_state_sort', store=True)
|
||||
|
||||
@api.depends('bank_state')
|
||||
def _compute_bank_state_sort(self):
|
||||
for rec in self:
|
||||
rec.bank_state_sort = 0 if rec.bank_state == 'registered' else 1
|
||||
|
||||
def open_record(self):
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
|
||||
@@ -9,41 +9,77 @@ class PropertyEvaluationPlan(models.Model):
|
||||
|
||||
name = fields.Char(string='物业名称')
|
||||
evaluation_area = fields.Float(string='评估面积(m²)')
|
||||
property_ids = fields.Many2many('yuthon.property', string='物业')
|
||||
property_id = fields.Many2one('yuthon.property', string='物业')
|
||||
# 业务状态:取关联物业的业务状态(只读展示)
|
||||
business_state = fields.Selection(related='property_id.business_state', string='业务状态')
|
||||
company_id = fields.Many2one('yuthon.property.company', string='评估公司', compute='_compute_search', store=True)
|
||||
admin_id = fields.Many2one('res.users', string="管理员", compute='_compute_search', store=True)
|
||||
assessment_category = fields.Selection([('rent_money', '租金'), ('value', '价值')], string="评估类别", default='rent_money')
|
||||
owner_id = fields.Many2one('yuthon.owner.message', string="权属人")
|
||||
registered_id = fields.Many2one('yuthon.purpose.evidence', string='证载用途')
|
||||
registered_id2 = fields.Many2one('yuthon.purpose.evidence', string='设定用途')
|
||||
# 以下字段均从关联物业带出,属计算字段,不落库(口径见 _compute_from_property)
|
||||
owner_id = fields.Many2one('yuthon.owner.message', string="权属人", compute='_compute_from_property')
|
||||
registered_id = fields.Many2one('yuthon.purpose.evidence', string='证载用途', compute='_compute_from_property')
|
||||
registered_id2 = fields.Many2one('yuthon.resource.type', string='设定用途', compute='_compute_from_property')
|
||||
rent_id = fields.Many2one('rent.free.setting', string="办法")
|
||||
lease_period = fields.Integer(string='租赁期限(<=)')
|
||||
first_years_begin = fields.Integer(string='第几年开始')
|
||||
increments_few_years = fields.Integer(string='每几年递增')
|
||||
lease_period = fields.Integer(string='租赁期限(<=)', compute='_compute_from_property')
|
||||
first_years_begin = fields.Integer(string='第几年开始', compute='_compute_from_property')
|
||||
increments_few_years = fields.Integer(string='每几年递增', compute='_compute_from_property')
|
||||
lease_form = fields.Selection([('sublease', '分租'), ('whole', '整租')], string="租赁形式", compute='compute_lease_form')
|
||||
annual_increase_percentage = fields.Float(string='递增百分比', igits=(6, 2))
|
||||
annual_increase_amount = fields.Float(string='递增金额', igits=(6, 2))
|
||||
rent_unit = fields.Float(string='递增单价', igits=(6, 2))
|
||||
annual_increase_percentage = fields.Float(string='递增百分比', igits=(6, 2), compute='_compute_from_property')
|
||||
annual_increase_amount = fields.Float(string='递增金额', igits=(6, 2), compute='_compute_from_property')
|
||||
rent_unit = fields.Float(string='递增单价', igits=(6, 2), compute='_compute_from_property')
|
||||
remake = fields.Text(string='备注')
|
||||
property_state = fields.Selection([('to', '待审批'), ('done', '已审批')], string="状态", default='to')
|
||||
property_state = fields.Selection([('to', '待评估'), ('ing', '评估中'), ('done', '已评估')],
|
||||
string="状态", default='to')
|
||||
|
||||
@api.depends('property_ids')
|
||||
@api.depends('property_id')
|
||||
def _compute_search(self):
|
||||
for record in self:
|
||||
if record.property_ids:
|
||||
record.company_id = record.property_ids[0].company_id.id
|
||||
record.admin_id = record.property_ids[0].admin_id.id
|
||||
if record.property_id:
|
||||
record.company_id = record.property_id.company_id.id
|
||||
record.admin_id = record.property_id.admin_id.id
|
||||
|
||||
@api.depends('property_ids')
|
||||
@api.depends('property_id',
|
||||
'property_id.asset_type_id',
|
||||
'property_id.reference_ids.start_date',
|
||||
'property_id.reference_ids.end_date',
|
||||
'property_id.reference_ids.lease_period',
|
||||
'property_id.reference_ids.rent_period',
|
||||
'property_id.reference_ids.first_years_begin',
|
||||
'property_id.reference_ids.increments_few_years',
|
||||
'property_id.reference_ids.annual_increase_amount',
|
||||
'property_id.reference_ids.rent_unit',
|
||||
'property_id.reference_ids.annual_increase_percentage',
|
||||
'property_id.title_information_ids.start_time',
|
||||
'property_id.title_information_ids.end_date',
|
||||
'property_id.title_information_ids.owner_id',
|
||||
'property_id.title_information_ids.registered_id')
|
||||
def _compute_from_property(self):
|
||||
"""从关联物业带出:
|
||||
- 租赁期限 / 第几年开始 / 每几年递增 / 递增金额 / 递增单价 / 递增百分比 ← 物业「出租参考条件」中日期范围覆盖当天的那行
|
||||
- 权属人 / 证载用途 ← 物业「产权信息明细」中日期范围覆盖当天的那行
|
||||
- 设定用途 ← 物业的「出租类型」(asset_type_id)
|
||||
"""
|
||||
for plan in self:
|
||||
prop = plan.property_id
|
||||
ref_line = prop._get_valid_reference_line() if prop else prop
|
||||
title_line = prop._get_valid_title_line() if prop else prop
|
||||
|
||||
plan.lease_period = ref_line.lease_period if ref_line else 0
|
||||
plan.first_years_begin = ref_line.first_years_begin if ref_line else 0
|
||||
plan.increments_few_years = ref_line.increments_few_years if ref_line else 0
|
||||
plan.annual_increase_amount = ref_line.annual_increase_amount if ref_line else 0.0
|
||||
plan.rent_unit = ref_line.rent_unit if ref_line else 0.0
|
||||
plan.annual_increase_percentage = ref_line.annual_increase_percentage if ref_line else 0.0
|
||||
|
||||
plan.owner_id = title_line.owner_id.id if title_line and title_line.owner_id else False
|
||||
plan.registered_id = title_line.registered_id.id if title_line and title_line.registered_id else False
|
||||
plan.registered_id2 = prop.asset_type_id.id if prop and prop.asset_type_id else False
|
||||
|
||||
@api.depends('property_id')
|
||||
def compute_lease_form(self):
|
||||
# 一个方案只对应一个物业 → 恒为「整租」(原「多物业=分租」的判断随 m2o 改造取消)
|
||||
for record in self:
|
||||
if record.property_ids:
|
||||
if len(record.property_ids) > 1:
|
||||
record.lease_form = 'sublease'
|
||||
else:
|
||||
record.lease_form = 'whole'
|
||||
else:
|
||||
record.lease_form = False
|
||||
record.lease_form = 'whole' if record.property_id else False
|
||||
|
||||
|
||||
def put_assessment_approval(self):
|
||||
@@ -52,12 +88,14 @@ class PropertyEvaluationPlan(models.Model):
|
||||
form_id = self.env.ref('yuthon_property_approval.property_evaluate_application_form').id
|
||||
property_list = []
|
||||
for i in property_evaluation_ids:
|
||||
if i.property_state == 'done':
|
||||
raise UserError('该方案不可提交')
|
||||
# 仅「待评估」可提交评估申请;评估中 / 已评估不再提供该入口
|
||||
if i.property_state != 'to':
|
||||
raise UserError('仅「待评估」的方案可以提交评估申请')
|
||||
property_list.append([0, 0, {
|
||||
'property_evaluation_id': i.id,
|
||||
}])
|
||||
i.write({'property_state': 'done'})
|
||||
# 此处只负责跳转,状态改为「评估中」由评估申请保存成功时触发
|
||||
# (见 property.evaluate.application.create)
|
||||
return {
|
||||
'name': '提交物业评估',
|
||||
'view_mode': 'form',
|
||||
@@ -69,27 +107,60 @@ class PropertyEvaluationPlan(models.Model):
|
||||
}
|
||||
}
|
||||
|
||||
def action_listing_rental(self):
|
||||
"""挂牌招租:把所选评估方案关联的物业带进「挂牌申报表」明细行"""
|
||||
property_evaluation_ids = self.env["property.evaluation.plan"].browse(
|
||||
self._context.get('active_ids', self._context.get('active_id')))
|
||||
properties = property_evaluation_ids.mapped('property_id')
|
||||
if not properties:
|
||||
raise UserError('所选评估方案未关联物业,无法挂牌招租')
|
||||
# 已进入签约流程(待签约/签约中/已签约)的物业不放进挂牌申报表
|
||||
form_model = self.env['property.rental.form']
|
||||
properties = form_model._get_listable_properties(properties)
|
||||
if not properties:
|
||||
raise UserError('所选物业均已进入签约流程(待签约/签约中/已签约),不能在挂牌申报表中提交')
|
||||
form_model._check_listing_available(properties)
|
||||
form_id = self.env.ref('yuthon_property_approval.view_property_rental_form_form').id
|
||||
line_list = []
|
||||
for prop in properties:
|
||||
line_list.append([0, 0, {
|
||||
'property_id': prop.id,
|
||||
}])
|
||||
return {
|
||||
'name': '挂牌招租',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'property.rental.form',
|
||||
'view_mode': 'form',
|
||||
'views': [(form_id, 'form')],
|
||||
'target': 'current',
|
||||
'context': {
|
||||
'default_property_rental_form_line_ids': line_list,
|
||||
},
|
||||
}
|
||||
|
||||
def button_source(self):
|
||||
domain = [('id', 'in', self.property_ids.ids)]
|
||||
"""详情:跳转到对应物业的表单"""
|
||||
self.ensure_one()
|
||||
prop = self.property_id
|
||||
if not prop:
|
||||
raise UserError('该评估方案未关联物业,无法查看详情')
|
||||
return {
|
||||
"name": "物业",
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "tree",
|
||||
"res_model": "yuthon.property",
|
||||
'tree_view_ref': 'yuthon_property.view_property_evaluation_plan_tree1',
|
||||
"domain": domain,
|
||||
"target": "new",
|
||||
"view_mode": "form",
|
||||
"views": [[self.env.ref('yuthon_property.yuthon_property_form').id, 'form']],
|
||||
"target": "current",
|
||||
"res_id": prop.id,
|
||||
}
|
||||
|
||||
DERIVED_FIELDS = ['owner_id', 'registered_id', 'registered_id2', 'lease_period',
|
||||
'first_years_begin', 'increments_few_years', 'annual_increase_amount',
|
||||
'rent_unit', 'annual_increase_percentage']
|
||||
|
||||
def update_message(self):
|
||||
"""这些字段已改为从关联物业实时计算、不落库,这里只需清缓存触发重算"""
|
||||
property_evaluation_ids = self.env["property.evaluation.plan"].browse(
|
||||
self._context.get('active_ids', self._context.get('active_id')))
|
||||
for record in property_evaluation_ids:
|
||||
property_id = self.sudo().env['yuthon.property'].search([('name', '=', record.name)])
|
||||
if property_id:
|
||||
record.write({
|
||||
'owner_id': property_id.owner_id,
|
||||
'lease_period': property_id.lease_period,
|
||||
'registered_id': property_id.registered_id,
|
||||
})
|
||||
property_evaluation_ids.invalidate_recordset(self.DERIVED_FIELDS)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from pypinyin import pinyin,lazy_pinyin
|
||||
from datetime import datetime,date
|
||||
from datetime import datetime,date,timedelta
|
||||
import calendar
|
||||
|
||||
|
||||
@@ -39,7 +39,24 @@ class YuthonProperty(models.Model):
|
||||
string="朝向")
|
||||
street = fields.Char(string="街道")
|
||||
property_state = fields.Selection([('on_rent', '在租'), ('vacant', '空置')], string='租赁状态', default='on_rent')
|
||||
property_leasing = fields.Selection([('to', '待评估'), ('ing', '评估中'), ('done', '已评估'), ('net', '挂网'), ('signing', '签约中')], string='评估状态', default='to')
|
||||
# 业务状态:评估 → 挂牌 → 签约 的完整链路
|
||||
# (原先拆成「评估状态 property_leasing」+「挂牌状态 listing_state」两个字段,现合并为一个)
|
||||
# 流转:保存只置「待X」,转交才置「X中」
|
||||
business_state = fields.Selection([
|
||||
('', ''),
|
||||
('to', '待评估'),
|
||||
('ing', '评估中'),
|
||||
('done', '已评估'),
|
||||
('to_list', '待挂牌'),
|
||||
('listing', '挂牌中'),
|
||||
('listed', '已挂牌'),
|
||||
('to_sign', '待签约'),
|
||||
('signing', '签约中'),
|
||||
('signed', '已签约'),
|
||||
], string='业务状态', default='to')
|
||||
# 该物业走过的所有审批(soong.workflow.record):展示用,不落库
|
||||
workflow_record_ids = fields.Many2many('soong.workflow.record', string='审批流程',
|
||||
compute='_compute_workflow_record_ids')
|
||||
purpose_id = fields.Many2one('yuthon.resource.purpose', string="管理类型", compute="compute_information_fields", store=True)
|
||||
is_meter = fields.Boolean(string="水表")
|
||||
live_room = fields.Many2one('yuthon.live.room', string="居住用房")
|
||||
@@ -97,6 +114,42 @@ class YuthonProperty(models.Model):
|
||||
property_type = fields.Selection([('property', '物业'), ('guanggao', '广告位'), ('guanzi', '管子间'), ('room', '机房'), ('base_station','基站'), ('other', '其他')], string="资产类型", default='property')
|
||||
is_new = fields.Boolean(string="是否最新物业", compute= 'compute_is_new', default=True, store=True)
|
||||
kingdee_property_id = fields.Char(string="金蝶云物业资产id")
|
||||
# 基本信息补充(建筑 / 土地)
|
||||
building_structure = fields.Char(string='建筑结构')
|
||||
rental_floor = fields.Char(string='出租楼层')
|
||||
land_nature = fields.Char(string='土地性质')
|
||||
land_acquisition = fields.Selection([('transfer', '出让'), ('allocation', '划拨')], string='取得方式')
|
||||
land_end_date = fields.Date(string='土地终止日期')
|
||||
construction_year = fields.Char(string='建筑年代')
|
||||
shared_land_area = fields.Float(string='共用地面积(㎡)', digits=(16, 4))
|
||||
|
||||
def _evaluation_date_deadline(self):
|
||||
"""评估基准日对应区间的最后一天:基准日「年份 +1」的同一天再减 1 天
|
||||
|
||||
例:基准日 2026-09-24 → 区间 2026-09-24 ~ 2027-09-23
|
||||
(2 月 29 日在次年不存在时按 2 月 28 日算)
|
||||
"""
|
||||
self.ensure_one()
|
||||
base = self.evaluation_date
|
||||
if not base:
|
||||
return False
|
||||
try:
|
||||
next_year = base.replace(year=base.year + 1)
|
||||
except ValueError:
|
||||
next_year = base.replace(year=base.year + 1, day=28)
|
||||
return next_year - timedelta(days=1)
|
||||
|
||||
@api.onchange('evaluation_date')
|
||||
def _onchange_evaluation_date(self):
|
||||
"""修改评估基准日后:若当前日期已超出「基准日 ~ 次年同日 - 1 天」这个区间,
|
||||
物业业务状态回到「待评估」;业务状态是「已签约」的物业不做处理。
|
||||
"""
|
||||
for record in self:
|
||||
if not record.evaluation_date or record.business_state == 'signed':
|
||||
continue
|
||||
deadline = record._evaluation_date_deadline()
|
||||
if deadline and fields.Date.context_today(record) > deadline:
|
||||
record.business_state = 'to'
|
||||
|
||||
def update_property_state(self):
|
||||
today = date.today()
|
||||
@@ -197,17 +250,115 @@ class YuthonProperty(models.Model):
|
||||
record.is_new = has_valid_line
|
||||
|
||||
|
||||
@api.onchange('reference_ids')
|
||||
# 「审批流程」页只显示这几类单据(评估审批 / 挂牌审批 / 成交确认书审批)
|
||||
WORKFLOW_PAGE_MODELS = (
|
||||
'property.evaluate.application', # 评估审批(物业评估申请)
|
||||
'property.rental.form', # 挂牌审批(挂牌申报表)
|
||||
'property.confirm', # 成交确认书审批
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _get_workflow_source_paths(self, only_models=None):
|
||||
"""自动发现「挂了工作流、且能关联到本物业」的单据,返回 [(模型名, 到物业的域路径)]
|
||||
|
||||
覆盖两层:
|
||||
① 单据上有指向 yuthon.property 的 Many2one 字段(如 property.split.approval.property_id);
|
||||
② 单据的明细行(One2many / Many2many)上有指向 yuthon.property 的 Many2one(如
|
||||
property.rental.form.property_rental_form_line_ids.property_id、
|
||||
property.confirm.lease_ids.address_id)。
|
||||
|
||||
:param only_models: 只在这些模型里找(传 None 表示全库扫描)
|
||||
"""
|
||||
mixin_cls = self.env['soong.workflow.mixin'].__class__
|
||||
paths = []
|
||||
for model_name in list(self.env.registry):
|
||||
if model_name not in self.env:
|
||||
continue
|
||||
if only_models and model_name not in only_models:
|
||||
continue
|
||||
cls = self.env.registry[model_name]
|
||||
try:
|
||||
if not issubclass(cls, mixin_cls):
|
||||
continue
|
||||
except TypeError:
|
||||
continue
|
||||
for fname, field in self.env[model_name]._fields.items():
|
||||
if field.type == 'many2one' and field.comodel_name == 'yuthon.property':
|
||||
paths.append((model_name, fname))
|
||||
elif field.type in ('one2many', 'many2many') and field.comodel_name in self.env:
|
||||
for sname, sfield in self.env[field.comodel_name]._fields.items():
|
||||
if sfield.type == 'many2one' and sfield.comodel_name == 'yuthon.property':
|
||||
paths.append((model_name, '%s.%s' % (fname, sname)))
|
||||
return paths
|
||||
|
||||
@api.depends()
|
||||
def _compute_workflow_record_ids(self):
|
||||
"""本物业相关的审批流程记录:只取「评估审批 / 挂牌审批 / 成交确认书审批」且**审批已结束**的"""
|
||||
Workflow = self.env['soong.workflow.record'].sudo()
|
||||
paths = {}
|
||||
for model_name, path in self._get_workflow_source_paths(self.WORKFLOW_PAGE_MODELS):
|
||||
paths.setdefault(model_name, []).append(path)
|
||||
|
||||
for record in self:
|
||||
found_ids = set()
|
||||
for model_name, model_paths in paths.items():
|
||||
source = self.env[model_name].sudo()
|
||||
res_ids = set()
|
||||
for path in model_paths:
|
||||
res_ids.update(source.search([(path, '=', record.id)]).ids)
|
||||
if res_ids:
|
||||
found_ids.update(Workflow.search([
|
||||
('res_model', '=', model_name), ('res_id', 'in', list(res_ids)),
|
||||
('approve_state', '=', 'end'),
|
||||
]).ids)
|
||||
record.workflow_record_ids = Workflow.browse(list(found_ids))
|
||||
|
||||
def _get_valid_reference_line(self):
|
||||
"""取出租参考条件明细行:优先取 [开始日期, 结束日期] 覆盖当天的那条;
|
||||
没有命中时回退到原有行为(最后一条)。"""
|
||||
self.ensure_one()
|
||||
lines = self.reference_ids
|
||||
if not lines:
|
||||
return lines
|
||||
today = fields.Date.context_today(self)
|
||||
valid = lines.filtered(
|
||||
lambda line: (not line.start_date or line.start_date <= today)
|
||||
and (not line.end_date or line.end_date >= today)
|
||||
)
|
||||
if not valid:
|
||||
return lines[-1:]
|
||||
# 多条命中时取开始日期最新的一条
|
||||
return valid.sorted(lambda line: line.start_date or today, reverse=True)[:1]
|
||||
|
||||
def _get_valid_title_line(self):
|
||||
"""取产权信息明细行:优先取 [生效日期, 失效日期] 覆盖当天的那条;
|
||||
没有命中时回退到最后一条。"""
|
||||
self.ensure_one()
|
||||
lines = self.title_information_ids
|
||||
if not lines:
|
||||
return lines
|
||||
today = fields.Date.context_today(self)
|
||||
valid = lines.filtered(
|
||||
lambda line: (not line.start_time or line.start_time <= today)
|
||||
and (not line.end_date or line.end_date >= today)
|
||||
)
|
||||
if not valid:
|
||||
return lines[-1:]
|
||||
# 多条命中时取生效日期最新的一条
|
||||
return valid.sorted(lambda line: line.start_time or today, reverse=True)[:1]
|
||||
|
||||
@api.onchange('reference_ids', 'reference_ids.start_date', 'reference_ids.end_date')
|
||||
def _onchange_reference_ids(self):
|
||||
for record in self:
|
||||
if record.reference_ids:
|
||||
record.lease_period = record.reference_ids[-1].lease_period
|
||||
record.rent_period = record.reference_ids[-1].rent_period
|
||||
record.first_years_begin = record.reference_ids[-1].first_years_begin
|
||||
record.increments_few_years = record.reference_ids[-1].increments_few_years
|
||||
record.annual_increase_percentage = record.reference_ids[-1].annual_increase_percentage
|
||||
record.annual_increase_amount = record.reference_ids[-1].annual_increase_amount
|
||||
record.rent_unit = record.reference_ids[-1].rent_unit
|
||||
line = record._get_valid_reference_line()
|
||||
if line:
|
||||
record.lease_period = line.lease_period
|
||||
record.rent_period = line.rent_period
|
||||
record.first_years_begin = line.first_years_begin
|
||||
record.increments_few_years = line.increments_few_years
|
||||
record.annual_increase_percentage = line.annual_increase_percentage
|
||||
record.annual_increase_amount = line.annual_increase_amount
|
||||
record.rent_unit = line.rent_unit
|
||||
else:
|
||||
record.lease_period = 0.0
|
||||
record.rent_period = 0.0
|
||||
@@ -386,6 +537,13 @@ class YuthonProperty(models.Model):
|
||||
evaluation_rent_total = fields.Float(string='评估总价(元•月,散租)')
|
||||
evaluation_whole_rent_price = fields.Float(string='评估整租单价(元/m² •月)')
|
||||
evaluation_whole_rent_total = fields.Float(string='评估总价(元•月,整租)')
|
||||
# 评估信息页(物业表单「评估信息」tab)
|
||||
evaluation_date = fields.Date(string='基准日')
|
||||
evaluation_info = fields.Char(string='评估信息')
|
||||
evaluation_agency = fields.Char(string='评估机构')
|
||||
evaluation_code = fields.Char(string='评估编号')
|
||||
evaluation_method = fields.Char(string='评估方法')
|
||||
evaluation_remark = fields.Char(string='备注')
|
||||
|
||||
def update_precinct(self):
|
||||
property_ids = self.env["yuthon.property"].browse(
|
||||
@@ -397,6 +555,12 @@ class YuthonProperty(models.Model):
|
||||
def put_declare_approval(self):
|
||||
property_ids = self.env["yuthon.property"].browse(
|
||||
self._context.get('active_ids', self._context.get('active_id')))
|
||||
# 已进入签约流程(待签约/签约中/已签约)的物业不放进挂牌申报表
|
||||
form_model = self.env['property.rental.form']
|
||||
property_ids = form_model._get_listable_properties(property_ids)
|
||||
if not property_ids:
|
||||
raise UserError('所选物业均已进入签约流程(待签约/签约中/已签约),不能在挂牌申报表中提交')
|
||||
form_model._check_listing_available(property_ids)
|
||||
form_id = self.env.ref('yuthon_property_approval.view_property_rental_form_form').id
|
||||
property_list = []
|
||||
for record in property_ids:
|
||||
@@ -498,9 +662,10 @@ class YuthonProperty(models.Model):
|
||||
|
||||
def put_assessment_approval(self):
|
||||
for record in self:
|
||||
ref_line = record._get_valid_reference_line()
|
||||
self.env['property.evaluation.plan'].create({
|
||||
'name': record.name,
|
||||
'property_ids': [(6, 0, [record.id])],
|
||||
'property_id': record.id,
|
||||
'evaluation_area': record.rent_area,
|
||||
'owner_id': record.owner_id.id if record.owner_id else None,
|
||||
'registered_id': record.registered_id.id if record.registered_id else None,
|
||||
@@ -509,7 +674,7 @@ class YuthonProperty(models.Model):
|
||||
'increments_few_years': record.increments_few_years,
|
||||
'annual_increase_amount': record.annual_increase_amount,
|
||||
'rent_unit': record.rent_unit,
|
||||
'annual_increase_percentage': record.reference_ids[-1].annual_increase_percentage if record.reference_ids else None,
|
||||
'annual_increase_percentage': ref_line.annual_increase_percentage if ref_line else None,
|
||||
'assessment_category': 'rent_money',
|
||||
'remake': record.remark,
|
||||
})
|
||||
@@ -525,11 +690,12 @@ class YuthonProperty(models.Model):
|
||||
property_ids = self.env["yuthon.property"].browse(
|
||||
self._context.get('active_ids', self._context.get('active_id')))
|
||||
for record in property_ids:
|
||||
if record.property_leasing == 'ing':
|
||||
if record.business_state == 'ing':
|
||||
raise UserError(f'{record.name}该物业已在评估,请勿重复提交')
|
||||
ref_line = record._get_valid_reference_line()
|
||||
self.env['property.evaluation.plan'].create({
|
||||
'name': record.name,
|
||||
'property_ids': [(6, 0, [record.id])],
|
||||
'property_id': record.id,
|
||||
'evaluation_area': record.rent_area,
|
||||
'owner_id': record.owner_id.id if record.owner_id else None,
|
||||
'registered_id': record.registered_id.id if record.registered_id else None,
|
||||
@@ -538,11 +704,11 @@ class YuthonProperty(models.Model):
|
||||
'increments_few_years': record.increments_few_years,
|
||||
'annual_increase_amount': record.annual_increase_amount,
|
||||
'rent_unit': record.rent_unit,
|
||||
'annual_increase_percentage': record.reference_ids[-1].annual_increase_percentage if record.reference_ids else None,
|
||||
'annual_increase_percentage': ref_line.annual_increase_percentage if ref_line else None,
|
||||
'assessment_category': 'rent_money',
|
||||
'remake': record.remark,
|
||||
})
|
||||
record.write({'property_leasing': 'ing'})
|
||||
record.write({'business_state': 'ing'})
|
||||
return {
|
||||
"name": "物业评估方案",
|
||||
'type': 'ir.actions.act_window',
|
||||
@@ -612,12 +778,13 @@ class YuthonProperty(models.Model):
|
||||
precinct_id = self.env["yuthon.precinct.message"].search([('employee_id.name', '=', record.owner_id.name)])
|
||||
owner_id = record.title_information_ids[-1].owner_id.id if record.title_information_ids else ''
|
||||
company_id = record.manage_information_ids[-1].company_id.id if record.manage_information_ids else ''
|
||||
lease_period = record.reference_ids[-1].lease_period if record.reference_ids else ''
|
||||
rent_period = record.reference_ids[-1].rent_period if record.reference_ids else ''
|
||||
first_years_begin = record.reference_ids[-1].first_years_begin if record.reference_ids else ''
|
||||
increments_few_years = record.reference_ids[-1].increments_few_years if record.reference_ids else ''
|
||||
annual_increase_amount = record.reference_ids[-1].annual_increase_amount if record.reference_ids else ''
|
||||
rent_unit = record.reference_ids[-1].rent_unit if record.reference_ids else ''
|
||||
ref_line = record._get_valid_reference_line()
|
||||
lease_period = ref_line.lease_period if ref_line else ''
|
||||
rent_period = ref_line.rent_period if ref_line else ''
|
||||
first_years_begin = ref_line.first_years_begin if ref_line else ''
|
||||
increments_few_years = ref_line.increments_few_years if ref_line else ''
|
||||
annual_increase_amount = ref_line.annual_increase_amount if ref_line else ''
|
||||
rent_unit = ref_line.rent_unit if ref_line else ''
|
||||
title_address = record.title_information_ids[-1].property_message_id if record.title_information_ids else ''
|
||||
title_deed = record.title_information_ids[-1].title_certificate if record.title_information_ids else ''
|
||||
registered_id = record.title_information_ids[-1].registered_id.id if record.title_information_ids else ''
|
||||
@@ -905,6 +1072,8 @@ class YuthonReferenceConditions(models.Model):
|
||||
_description = '出租参考条件'
|
||||
|
||||
yuthon_property_id = fields.Many2one('yuthon.property', string="关联物业信息")
|
||||
start_date = fields.Date(string='开始日期')
|
||||
end_date = fields.Date(string='结束日期')
|
||||
lease_period = fields.Integer(string='租赁期限(<=)')
|
||||
rent_period = fields.Integer(string="免租期(<=)")
|
||||
first_years_begin = fields.Integer(string='第几年开始')
|
||||
|
||||
@@ -104,4 +104,6 @@ access_task_push_wizard,task.push.wizard,model_task_push_wizard,base.group_user,
|
||||
access_utility_meter_wizard,utility.meter.wizard,model_utility_meter_wizard,base.group_user,1,1,1,1
|
||||
access_invoicing_information_line,invoicing.information.line,model_invoicing_information_line,base.group_user,1,1,1,1
|
||||
access_tenant_import_wizard,tenant.import.wizard,model_tenant_import_wizard,base.group_user,1,1,1,1
|
||||
access_batch_invoice_export_wizard,batch.invoice.export.wizard,model_batch_invoice_export_wizard,base.group_user,1,1,1,1
|
||||
access_batch_invoice_export_wizard,batch.invoice.export.wizard,model_batch_invoice_export_wizard,base.group_user,1,1,1,1
|
||||
access_property_import_wizard,property.import.wizard,model_property_import_wizard,base.group_user,1,1,1,1
|
||||
access_property_evaluation_import_wizard,property.evaluation.import.wizard,model_property_evaluation_import_wizard,base.group_user,1,1,1,1
|
||||
|
@@ -24,6 +24,22 @@
|
||||
parent="yuthon_property_menu"
|
||||
action="view_property_evaluation_plan_action"
|
||||
/>
|
||||
<!-- 评估方案子级:按状态拆成三个菜单 -->
|
||||
<menuitem id="yuthon_property_evaluation_plan_menu_todo"
|
||||
sequence="1"
|
||||
name="待评估"
|
||||
parent="yuthon_property_evaluation_plan_menu"
|
||||
action="view_property_evaluation_plan_action_todo"/>
|
||||
<menuitem id="yuthon_property_evaluation_plan_menu_ing"
|
||||
sequence="2"
|
||||
name="评估中"
|
||||
parent="yuthon_property_evaluation_plan_menu"
|
||||
action="view_property_evaluation_plan_action_ing"/>
|
||||
<menuitem id="yuthon_property_evaluation_plan_menu_done"
|
||||
sequence="3"
|
||||
name="已评估"
|
||||
parent="yuthon_property_evaluation_plan_menu"
|
||||
action="view_property_evaluation_plan_action_done"/>
|
||||
<menuitem id="yuthon_property_evaluation_experts_menu"
|
||||
sequence="20"
|
||||
name="评估专家库(二期)"
|
||||
|
||||
@@ -23,6 +23,34 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- 对账(贷)专用列表:交易时间倒序 + 挂账的排最前面。
|
||||
独立 primary 视图(inherit_id 为空),避免污染其它菜单用的 view_property_bank_statements_tree -->
|
||||
<record id="view_property_bank_statements_tree_loan" model="ir.ui.view">
|
||||
<field name="name">property.bank.statements.tree.loan</field>
|
||||
<field name="model">property.bank.statements</field>
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority">99</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="对账" editable="bottom" js_class="custom_statements_list_button" edit="1" delete="false" limit="15"
|
||||
default_order="bank_state_sort, start_time desc"
|
||||
decoration-warning="bank_state == 'registered'">
|
||||
<field name="bank_state_sort" column_invisible="1"/>
|
||||
<field name="start_time" />
|
||||
<field name="original_amount"/>
|
||||
<field name="yes_amount" string="已对账"/>
|
||||
<field name="no_amount"/>
|
||||
<field name="remake"/>
|
||||
<field name="extract"/>
|
||||
<field name="name_account" />
|
||||
<field name="your_own_unit"/>
|
||||
<field name="property_ids" widget="many2many_tags" column_invisible="1"/>
|
||||
<field name="bank_state" />
|
||||
<field name="workflow_record_id" column_invisible="1"/>
|
||||
<button name="open_record" string="流程" type="object" class="btn-primary" invisible="not workflow_record_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_property_bank_statements_tree1" model="ir.ui.view">
|
||||
<field name="name">property.bank.statements.tree</field>
|
||||
<field name="model">property.bank.statements</field>
|
||||
@@ -98,7 +126,7 @@
|
||||
<field name="view_mode">tree,search</field>
|
||||
<field name="domain">[('bank_state', 'in', ['not_reconciled', 'registered']), ('lending_direction', '=', 'loan')]</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_property.view_property_bank_statements_tree')})]"/>
|
||||
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_property.view_property_bank_statements_tree_loan')})]"/>
|
||||
</record>
|
||||
<record id="view_property_bank_statements_action_borrow" model="ir.actions.act_window">
|
||||
<field name="name">对账(借)</field>
|
||||
|
||||
@@ -8,22 +8,19 @@
|
||||
<header>
|
||||
<button name="put_assessment_approval" type="object" string="提交评估申请" class="btn btn-primary"/>
|
||||
</header>
|
||||
<field name="name"/>
|
||||
<field name="property_ids" widget="many2many_tags" optional="hide" options="{'no_create': True}"/>
|
||||
<field name="property_id" optional="hide" options="{'no_create': True}"/>
|
||||
<field name="evaluation_area"/>
|
||||
<field name="assessment_category"/>
|
||||
<field name="owner_id"/>
|
||||
<field name="registered_id"/>
|
||||
<field name="registered_id2"/>
|
||||
<field name="lease_period"/>
|
||||
<field name="rent_id"/>
|
||||
<field name="first_years_begin"/>
|
||||
<field name="increments_few_years"/>
|
||||
<field name="annual_increase_amount"/>
|
||||
<field name="rent_unit"/>
|
||||
<field name="annual_increase_percentage"/>
|
||||
<field name="owner_id"/>
|
||||
<field name="registered_id"/>
|
||||
<field name="registered_id2"/>
|
||||
<field name="remake"/>
|
||||
<field name="property_state"/>
|
||||
<field name="lease_form" optional="hide"/>
|
||||
<button name="button_source" type="object" string="详情" class="btn btn-blue"/>
|
||||
</tree>
|
||||
@@ -49,6 +46,93 @@
|
||||
<field name="name">物业评估方案</field>
|
||||
<field name="res_model">property.evaluation.plan</field>
|
||||
<field name="view_mode">tree,search</field>
|
||||
<!-- 显式清空:升级只更新 XML 中出现的字段,直接删掉不会清 DB 里的旧 context(该字段 NOT NULL,不能写 False) -->
|
||||
<field name="context">{}</field>
|
||||
</record>
|
||||
<!-- ⚠️ 列表 header 按钮的显隐只能按「列表上下文」整体评估(不逐行),且 Odoo 合并视图时
|
||||
会把所有继承视图一并叠加(_get_inheriting_views 无 mode 过滤),继承方案会污染基础视图。
|
||||
因此「评估中 / 已评估」必须用 **独立视图**(不设 inherit_id),各自定义 header 按钮。 -->
|
||||
<!-- 评估中:无任何 header 按钮 -->
|
||||
<record id="view_property_evaluation_plan_tree_ing" model="ir.ui.view">
|
||||
<field name="name">property.evaluation.plan.tree.ing</field>
|
||||
<field name="model">property.evaluation.plan</field>
|
||||
<field name="inherit_id" eval="False"/>
|
||||
<field name="mode">primary</field>
|
||||
<!-- 优先级调后,避免被 default_view 选为模型默认列表视图 -->
|
||||
<field name="priority">99</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="物业评估方案" editable="bottom" create="false" js_class="custom_evaluation_button">
|
||||
<field name="property_id" optional="hide" options="{'no_create': True}"/>
|
||||
<field name="evaluation_area"/>
|
||||
<field name="assessment_category"/>
|
||||
<field name="lease_period"/>
|
||||
<field name="first_years_begin"/>
|
||||
<field name="increments_few_years"/>
|
||||
<field name="annual_increase_amount"/>
|
||||
<field name="rent_unit"/>
|
||||
<field name="annual_increase_percentage"/>
|
||||
<field name="owner_id"/>
|
||||
<field name="registered_id"/>
|
||||
<field name="registered_id2"/>
|
||||
<field name="remake"/>
|
||||
<field name="lease_form" optional="hide"/>
|
||||
<button name="button_source" type="object" string="详情" class="btn btn-blue"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<!-- 已评估:header 提供「挂牌招租」 -->
|
||||
<record id="view_property_evaluation_plan_tree_done" model="ir.ui.view">
|
||||
<field name="name">property.evaluation.plan.tree.done</field>
|
||||
<field name="model">property.evaluation.plan</field>
|
||||
<field name="inherit_id" eval="False"/>
|
||||
<field name="mode">primary</field>
|
||||
<!-- 优先级调后,避免被 default_view 选为模型默认列表视图 -->
|
||||
<field name="priority">99</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="物业评估方案" editable="bottom" create="false" js_class="custom_evaluation_button">
|
||||
<header>
|
||||
<button name="action_listing_rental" type="object" string="挂牌招租" class="btn btn-primary"/>
|
||||
</header>
|
||||
<field name="business_state" optional="show"/>
|
||||
<field name="property_id" optional="hide" options="{'no_create': True}"/>
|
||||
<field name="evaluation_area"/>
|
||||
<field name="assessment_category"/>
|
||||
<field name="lease_period"/>
|
||||
<field name="first_years_begin"/>
|
||||
<field name="increments_few_years"/>
|
||||
<field name="annual_increase_amount"/>
|
||||
<field name="rent_unit"/>
|
||||
<field name="annual_increase_percentage"/>
|
||||
<field name="owner_id"/>
|
||||
<field name="registered_id"/>
|
||||
<field name="registered_id2"/>
|
||||
<field name="remake"/>
|
||||
<field name="lease_form" optional="hide"/>
|
||||
<button name="button_source" type="object" string="详情" class="btn btn-blue"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<!-- 按状态拆分的三个子菜单:待评估 / 评估中 / 已评估 -->
|
||||
<record id="view_property_evaluation_plan_action_todo" model="ir.actions.act_window">
|
||||
<field name="name">待评估</field>
|
||||
<field name="res_model">property.evaluation.plan</field>
|
||||
<field name="view_mode">tree,search</field>
|
||||
<field name="domain">[('property_state', '=', 'to')]</field>
|
||||
<field name="view_id" ref="view_property_evaluation_plan_tree"/>
|
||||
</record>
|
||||
<record id="view_property_evaluation_plan_action_ing" model="ir.actions.act_window">
|
||||
<field name="name">评估中</field>
|
||||
<field name="res_model">property.evaluation.plan</field>
|
||||
<field name="view_mode">tree,search</field>
|
||||
<field name="domain">[('property_state', '=', 'ing')]</field>
|
||||
<field name="view_id" ref="view_property_evaluation_plan_tree_ing"/>
|
||||
</record>
|
||||
<record id="view_property_evaluation_plan_action_done" model="ir.actions.act_window">
|
||||
<field name="name">已评估</field>
|
||||
<field name="res_model">property.evaluation.plan</field>
|
||||
<field name="view_mode">tree,search</field>
|
||||
<field name="domain">[('property_state', '=', 'done')]</field>
|
||||
<field name="view_id" ref="view_property_evaluation_plan_tree_done"/>
|
||||
</record>
|
||||
<record id="all_property_update_message" model="ir.actions.server">
|
||||
<field name="name">更新信息</field>
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
<tree string="物业管理" multi_edit="1">
|
||||
<header>
|
||||
<button name="put_merge_approval" string="合并" type="object" class="btn btn-primary"/>
|
||||
<button name="%(action_property_import_wizard)d" string="物业导入" type="action" class="btn btn-primary"/>
|
||||
<button name="%(action_property_evaluation_import_wizard)d" string="评估信息导入" type="action" class="btn btn-primary"/>
|
||||
<button name="put_split_approval" type="object" string="拆分" class="btn btn-primary"/>
|
||||
<button name="put_property_handover" string="物业移交" type="object" class="btn btn-primary"/>
|
||||
<button name="put_declare_approval" type="object" string="提交出租申报" class="btn btn-primary"/>
|
||||
<button name="all_assessment_approval" string="评估推送" type="object" class="btn btn-blue"/>
|
||||
<button name="all_assessment_approval" string="评估推送" type="object" class="btn btn-primary"/>
|
||||
<button name="put_property_management" string="物业变更" type="object" class="btn btn-primary"/>
|
||||
</header>
|
||||
<field name="name" required="1" class="list_name_width"/>
|
||||
@@ -20,14 +22,14 @@
|
||||
<field name="title_deed"/>
|
||||
<field name="title_address" optional="hide"/>
|
||||
<field name="property_state"/>
|
||||
<field name="property_leasing"/>
|
||||
<field name="business_state"/>
|
||||
<field name="purpose_id"/>
|
||||
<field name="property_type"/>
|
||||
<field name="is_new" column_invisible="1"/>
|
||||
<field name="first_letter" column_invisible="1"/>
|
||||
<button name="put_assessment_approval" type="object" string="评估推送" class="btn btn-primary" invisible="property_leasing == 'ing'"/>
|
||||
<button name="put_offline_approval" type="object" string="提交线下审批" class="btn btn-primary" invisible="property_leasing != 'done'"/>
|
||||
<button name="put_adjustment_approval" type="object" string="调整出租条件" class="btn btn-primary" invisible="property_leasing != 'done'"/>
|
||||
<button name="put_assessment_approval" type="object" string="评估推送" class="btn btn-primary" invisible="business_state in ('ing', 'done')"/>
|
||||
<button name="put_offline_approval" type="object" string="提交线下审批" class="btn btn-primary" invisible="business_state not in ('to_list', 'listing', 'listed', 'to_sign', 'signing', 'signed')"/>
|
||||
<button name="put_adjustment_approval" type="object" string="调整出租条件" class="btn btn-primary" invisible="business_state not in ('to_list', 'listing', 'listed', 'to_sign', 'signing', 'signed')"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -66,10 +68,17 @@
|
||||
<field name="financial_address"/>
|
||||
<field name="effective_date"/>
|
||||
<field name="kingdee_property_id"/>
|
||||
<field name="building_structure"/>
|
||||
<field name="rental_floor"/>
|
||||
<field name="land_nature"/>
|
||||
<field name="land_acquisition"/>
|
||||
<field name="land_end_date"/>
|
||||
<field name="construction_year"/>
|
||||
<field name="shared_land_area"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="property_state" groups="soong_workflow.group_soong_workflow_manager"/>
|
||||
<field name="property_leasing" groups="soong_workflow.group_soong_workflow_manager"/>
|
||||
<field name="business_state" groups="soong_workflow.group_soong_workflow_manager"/>
|
||||
<field name="purpose_id" groups="soong_workflow.group_soong_workflow_manager"/>
|
||||
<field name="property_type" groups="soong_workflow.group_soong_workflow_manager"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
@@ -208,6 +217,8 @@
|
||||
<field name="annual_increase_amount"/>
|
||||
<field name="rent_unit"/>
|
||||
<field name="annual_increase_percentage"/>
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
@@ -268,6 +279,38 @@
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="评估信息" name="evaluation_info_page">
|
||||
<group>
|
||||
<group>
|
||||
<field name="evaluation_date"/>
|
||||
<field name="evaluation_rent_price" string="分租单价"/>
|
||||
<field name="evaluation_rent_total" string="分租总价"/>
|
||||
<field name="evaluation_whole_rent_price" string="整租单价"/>
|
||||
<field name="evaluation_whole_rent_total" string="整租总价"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="evaluation_info"/>
|
||||
<field name="evaluation_agency"/>
|
||||
<field name="evaluation_code"/>
|
||||
<field name="evaluation_method"/>
|
||||
<field name="evaluation_remark"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="审批流程" name="workflow_record_page">
|
||||
<field name="workflow_record_ids" nolabel="1">
|
||||
<tree create="false" edit="false" delete="false"
|
||||
default_order="create_date desc">
|
||||
<field name="code" string="流水号"/>
|
||||
<field name="workflow_id" string="审批类型"/>
|
||||
<field name="name" string="名称"/>
|
||||
<field name="create_uid" string="发起人"/>
|
||||
<field name="create_date" string="发起时间"/>
|
||||
<field name="display_end_time" string="办结时间"/>
|
||||
<button name="open_record" type="object" string="详情" class="btn btn-primary"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<!-- <div class="oe_chatter">-->
|
||||
@@ -289,17 +332,23 @@
|
||||
<group expand="0">
|
||||
<filter name="group_property_floor" string="楼层" context="{'group_by': 'property_floor_id'}"/>
|
||||
<filter name="group_statu" string="租赁状态" context="{'group_by': 'property_state'}"/>
|
||||
<filter name="group_property_leasing" string="评估状态" context="{'group_by': 'property_leasing'}"/>
|
||||
<filter name="group_business_state" string="业务状态" context="{'group_by': 'business_state'}"/>
|
||||
<filter name="purpose_id" string="管理类型" context="{'group_by': 'purpose_id'}"/>
|
||||
<filter name="admin_id" string="管理员" context="{'group_by': 'admin_id'}"/>
|
||||
<filter name="title_address" string="产权地址" context="{'group_by': 'title_address'}"/>
|
||||
<filter name="referred_id" string="地段" context="{'group_by': 'referred_id'}"/>
|
||||
</group>
|
||||
<header_search>
|
||||
<quick_filter string="状态">
|
||||
<filter string="待评估" name="property_state" domain="[('property_leasing', '=', 'to')]" />
|
||||
<filter string="评估中" name="done" domain="[('property_leasing', '=', 'ing')]"/>
|
||||
<filter string="已评估" name="to_do" domain="[('property_leasing', '=', 'done')]" />
|
||||
<quick_filter string="业务状态">
|
||||
<filter string="待评估" name="bs_to" domain="[('business_state', '=', 'to')]" />
|
||||
<filter string="评估中" name="bs_ing" domain="[('business_state', '=', 'ing')]"/>
|
||||
<filter string="已评估" name="bs_done" domain="[('business_state', '=', 'done')]" />
|
||||
<filter string="待挂牌" name="bs_to_list" domain="[('business_state', '=', 'to_list')]" />
|
||||
<filter string="挂牌中" name="bs_listing" domain="[('business_state', '=', 'listing')]" />
|
||||
<filter string="已挂牌" name="bs_listed" domain="[('business_state', '=', 'listed')]" />
|
||||
<filter string="待签约" name="bs_to_sign" domain="[('business_state', '=', 'to_sign')]" />
|
||||
<filter string="签约中" name="bs_signing" domain="[('business_state', '=', 'signing')]" />
|
||||
<filter string="已签约" name="bs_signed" domain="[('business_state', '=', 'signed')]" />
|
||||
</quick_filter>
|
||||
</header_search>
|
||||
<searchpanel>
|
||||
|
||||
@@ -13,3 +13,5 @@ from . import task_push_wizard
|
||||
from . import utility_meter_wizard
|
||||
from . import tenant_import_wizard
|
||||
from . import batch_invoice_export_wizard
|
||||
from . import property_import_wizard
|
||||
from . import property_evaluation_import_wizard
|
||||
|
||||
@@ -6,26 +6,18 @@ class EvaluationPlanWizard(models.Model):
|
||||
_name = 'evaluation.plan.wizard'
|
||||
_description = '评估方案弹窗'
|
||||
|
||||
property_ids = fields.Many2many('yuthon.property', string='物业')
|
||||
# 一个评估方案对应一个物业(原为多选 m2m,已改为 m2o)
|
||||
property_id = fields.Many2one('yuthon.property', string='物业')
|
||||
|
||||
def confirm(self):
|
||||
evaluation_area = 0
|
||||
name_list = []
|
||||
first_id = self.property_ids[0]
|
||||
for i in self.property_ids:
|
||||
name = i.name
|
||||
name_list.append(name)
|
||||
evaluation_area += i.evaluation_area
|
||||
name = ','.join(name_list)
|
||||
self.ensure_one()
|
||||
prop = self.property_id
|
||||
if not prop:
|
||||
raise UserError('请先选择物业')
|
||||
self.env['property.evaluation.plan'].create({
|
||||
'name': name,
|
||||
'name': prop.name,
|
||||
'property_id': prop.id,
|
||||
'assessment_category': 'rent_money',
|
||||
'evaluation_area': evaluation_area,
|
||||
'owner_id': first_id.title_information_ids[0].owner_id.id if first_id.title_information_ids else None,
|
||||
'registered_id': first_id.title_information_ids[0].registered_id.id if first_id.title_information_ids else None,
|
||||
'rent_id': first_id.reference_ids[0].rent_id.id if first_id.reference_ids else None,
|
||||
'lease_period': first_id.reference_ids[0].lease_period if first_id.reference_ids else None,
|
||||
'annual_increase_percentage': first_id.reference_ids[0].annual_increase_percentage if first_id.reference_ids else None,
|
||||
'remake': first_id.remark,
|
||||
'property_ids': self.property_ids.ids
|
||||
})
|
||||
'evaluation_area': prop.evaluation_area,
|
||||
'remake': prop.remark,
|
||||
})
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="property_ids" widget="many2many_tags" domain="[('property_state', '=', 'to')]"/>
|
||||
<field name="property_id" options="{'no_create': True}"
|
||||
domain="[('property_state', '=', 'to')]"/>
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
|
||||
@@ -66,4 +66,18 @@
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
</record>
|
||||
<!-- 合同执行中、距实际结束日期不足 3 个月 → 物业业务状态回到「待评估」(每份合同只推送一次) -->
|
||||
<record forcecreate="True" id="cron_contract_expire_to_evaluate" model="ir.cron">
|
||||
<field name="name">定时任务:合同到期前3个月推送物业评估</field>
|
||||
<field name="model_id" ref="model_property_lease_contract"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.task_contract_expire_to_evaluate()</field>
|
||||
<field eval="True" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="nextcall" eval="(datetime.now() + relativedelta(hour=9, minute=10, second=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -125,9 +125,8 @@ class YuthonProperty(models.Model):
|
||||
for record in self.search([('property_state', '=', 'to'), ('property_contract_ids', '=', False)]):
|
||||
contracts = self.env['property.evaluation.plan'].search([])
|
||||
for contract in contracts:
|
||||
for property_id in contract.property_ids:
|
||||
if property_id.id not in unique_property_ids:
|
||||
unique_property_ids.add(property_id.id)
|
||||
if contract.property_id and contract.property_id.id not in unique_property_ids:
|
||||
unique_property_ids.add(contract.property_id.id)
|
||||
if record.id not in unique_property_ids:
|
||||
record.put_assessment_approval()
|
||||
else:
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, fields, api, exceptions, _
|
||||
from odoo import models, fields, api, exceptions, _, Command
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class PropertyConfirm(models.Model):
|
||||
@@ -36,12 +37,116 @@ class PropertyConfirm(models.Model):
|
||||
# 递增百分比
|
||||
annual_increase_percentage = fields.Float(string="递增百分比", digits=(6, 2))
|
||||
|
||||
# 「经办人 / 审批状态」取自本单据的工作流记录(口径同 property.lease.contract)
|
||||
employee_ids = fields.Many2many('hr.employee', string="当前经办人", compute='_compute_workflow')
|
||||
approve_state = fields.Selection([("start", "草稿"), ("pending", "审批中"), ("end", "结束")],
|
||||
string="审批状态", compute='_compute_workflow')
|
||||
# 业务状态:取物业明细「第一个」行的物业业务状态(只读展示)
|
||||
business_state = fields.Selection(
|
||||
related='lease_ids.address_id.business_state', string='业务状态')
|
||||
|
||||
@api.depends('code')
|
||||
def _compute_workflow(self):
|
||||
for record in self:
|
||||
workflow = record._get_active_workflow_record()
|
||||
record.employee_ids = workflow.employee_ids if workflow else False
|
||||
record.approve_state = workflow.approve_state if workflow else False
|
||||
|
||||
@api.depends('lease_ids', 'lease_ids.property_area')
|
||||
def _compute_property_area(self):
|
||||
# 汇总每条成交确认单下所有物业明细行的可租面积
|
||||
for rec in self:
|
||||
rec.property_area = sum(rec.lease_ids.mapped('property_area'))
|
||||
|
||||
@staticmethod
|
||||
def _x2m_lease_property_ids(commands):
|
||||
"""从「物业列表」的一对多命令里取出物业 id(address_id)"""
|
||||
ids = set()
|
||||
for cmd in commands or []:
|
||||
if cmd[0] == Command.CREATE and len(cmd) > 2 and cmd[2].get('address_id'):
|
||||
ids.add(cmd[2]['address_id'])
|
||||
return list(ids)
|
||||
|
||||
@api.model
|
||||
def _check_no_duplicate_application(self, properties):
|
||||
"""前置校验:同一物业不能重复生成成交确认书
|
||||
(已存在「未进回收站」的成交确认书即视为已申请过)"""
|
||||
records = self.env['property.confirm'].search([('lease_ids.address_id', 'in', properties.ids)])
|
||||
alive = records.filtered(lambda rec: rec._has_active_workflow_record())
|
||||
duplicated = alive.mapped('lease_ids.address_id') & properties
|
||||
if duplicated:
|
||||
raise UserError('以下物业已生成过成交确认书(未进回收站),不能重复申请:%s'
|
||||
% '、'.join(duplicated.mapped('name')))
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
# 先校验(在建表之前):同一物业不能重复生成成交确认书
|
||||
prop_ids = []
|
||||
for vals in vals_list:
|
||||
prop_ids += self._x2m_lease_property_ids(vals.get('lease_ids'))
|
||||
if prop_ids:
|
||||
self._check_no_duplicate_application(self.env['yuthon.property'].browse(set(prop_ids)))
|
||||
|
||||
records = super().create(vals_list)
|
||||
# 保存时业务状态为「待签约」;推进到「签约中」是在**转交**时(见 _on_workflow_transfer)
|
||||
# 只向前推进:已处于「待签约/签约中/已签约」的不再改动,避免把流程回退
|
||||
props = records.mapped('lease_ids.address_id').filtered(
|
||||
lambda prop: prop.business_state not in ('to_sign', 'signing', 'signed'))
|
||||
if props:
|
||||
props.write({'business_state': 'to_sign'})
|
||||
return records
|
||||
|
||||
def _on_workflow_transfer(self):
|
||||
"""转交(工作流弹窗触发,仅挂牌申报表/成交确认书):关联物业业务状态「待签约」→「签约中」"""
|
||||
props = self.mapped('lease_ids.address_id').filtered(
|
||||
lambda prop: prop.business_state in (False, '', 'to_sign'))
|
||||
if props:
|
||||
props.write({'business_state': 'signing'})
|
||||
|
||||
def action_create_contract(self):
|
||||
"""创建合同:把成交确认书的物业与条件带进租赁合同
|
||||
|
||||
字段对应关系:
|
||||
- 物业 → 合同「物业信息明细行」(`lease_ids.address_id`)
|
||||
- 面积 → 合同「总面积」(`total_area`)
|
||||
- 租期年限 → 合同「租期(年)」(`lease_term_year`)
|
||||
- 免租天数 → 合同「免租期 月/日」;**31 天算 1 个月**(如 32 天 → 1 月 1 日)
|
||||
- 每 X 年递增 Y% → 合同「每几年递增」+ 递增方式=百分比 + 递增值
|
||||
(对应合同递增组合字段的后半部分,不涉及「自第 X 年开始」)
|
||||
"""
|
||||
self.ensure_one()
|
||||
# 只有审批状态为「结束」的成交确认书才能创建合同(列表按钮同样按此显隐)
|
||||
if self.approve_state != 'end':
|
||||
raise UserError('只有审批状态为「结束」的成交确认书才能创建合同')
|
||||
properties = self.lease_ids.mapped('address_id')
|
||||
if not properties:
|
||||
raise UserError('该成交确认书未关联物业,无法创建合同')
|
||||
form_id = self.env.ref('yuthon_property_approval.view_property_lease_contract_form').id
|
||||
# 免租天数换算成「月/日」:31 天 = 1 个月;余数为 0 时借一个月,避免出现「X月0日」
|
||||
# 例:31 天 → 0月31日;32 天 → 1月1日;62 天 → 1月31日
|
||||
free_months, free_days = divmod(self.rent_free_days or 0, 31)
|
||||
if free_months and not free_days:
|
||||
free_months -= 1
|
||||
free_days = 31
|
||||
return {
|
||||
'name': '合同审批',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'property.lease.contract',
|
||||
'view_mode': 'form',
|
||||
'views': [(form_id, 'form')],
|
||||
'target': 'current',
|
||||
'context': {
|
||||
'default_lease_ids': [(0, 0, {'address_id': prop.id}) for prop in properties],
|
||||
'default_total_area': self.property_area,
|
||||
'default_lease_term_year': self.lease_term_years,
|
||||
'default_rent_free_period_month': free_months,
|
||||
'default_rent_free_period_days': free_days,
|
||||
'default_increments_every_few_years': self.increments_few_years,
|
||||
'default_increment_method': 'percentage',
|
||||
'default_increment_value': self.annual_increase_percentage,
|
||||
},
|
||||
}
|
||||
|
||||
def _get_default_name_title(self):
|
||||
return '成交确认书'
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
from email.policy import default
|
||||
import base64
|
||||
import io
|
||||
from copy import copy
|
||||
|
||||
from openpyxl import load_workbook
|
||||
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
|
||||
from odoo.tools.misc import file_path
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
@@ -26,7 +33,6 @@ class PropertyEvaluateApplication(models.Model):
|
||||
active = fields.Boolean('Active', default=True, tracking=True)
|
||||
date_assessment = fields.Date(string='评估基准日')
|
||||
evaluation_company_id = fields.Many2one('evaluation.company', string='评估公司')
|
||||
quote = fields.Float(string='报价')
|
||||
service_money = fields.Float(string='评估服务费总额')
|
||||
pay_time = fields.Date(string='支付时间')
|
||||
pay_way = fields.Char(string='支付方式')
|
||||
@@ -62,6 +68,261 @@ class PropertyEvaluateApplication(models.Model):
|
||||
}
|
||||
}
|
||||
|
||||
# ================= 导出「资产评估结果明细表」 =================
|
||||
# 模板:static/others/assessment_result_template.xlsx(单工作表「租金-结果明细表」,25 列)
|
||||
# 结构:第 1~3 行 标题/评估基准日/委托人;第 4 行 评估编号·评估方法·评估信息(评估机构填写);
|
||||
# 第 5~6 行 两级表头;其后为数据区,再依次是「合计」行、备注说明、调整表、落款(评估人员/评估机构)。
|
||||
# 行号不写死:运行时按「A 列为『序号』的第一行」定位表头、「A 列为『合计』」定位合计行。
|
||||
EXPORT_TEMPLATE_PATH = 'yuthon_property_approval/static/others/assessment_result_template.xlsx'
|
||||
EXPORT_COL_COUNT = 25
|
||||
|
||||
@staticmethod
|
||||
def _find_assessment_data_start(sheet, max_row):
|
||||
"""定位数据区起始行:A 列为「序号」的是第一级表头,其下含「证载面积」的是第二级表头"""
|
||||
first_header = None
|
||||
for row in range(1, max_row + 1):
|
||||
if str(sheet.cell(row, 1).value or '').strip() == '序号':
|
||||
first_header = row
|
||||
break
|
||||
if first_header is None:
|
||||
raise UserError('导出模板格式异常:未找到表头行')
|
||||
for row in range(first_header, max_row + 1):
|
||||
if '证载面积' in str(sheet.cell(row, 5).value or ''):
|
||||
return row + 1
|
||||
return first_header + 2
|
||||
|
||||
@staticmethod
|
||||
def _fmt_num(value):
|
||||
"""整数去掉小数点,其余按 %g 输出"""
|
||||
if value in (False, None, ''):
|
||||
return ''
|
||||
try:
|
||||
number = float(value)
|
||||
except (TypeError, ValueError):
|
||||
return str(value)
|
||||
return str(int(number)) if number == int(number) else ('%g' % number)
|
||||
|
||||
@api.model
|
||||
def _selection_label(self, model_name, field_name, value):
|
||||
if not value:
|
||||
return ''
|
||||
field = self.env[model_name]._fields.get(field_name)
|
||||
if not field:
|
||||
return value
|
||||
return dict(field._description_selection(self.env)).get(value, value)
|
||||
|
||||
@api.model
|
||||
def _increase_text(self, ref_line):
|
||||
"""租金递增文案,取自物业「出租参考条件」中覆盖当天的那行。
|
||||
|
||||
递增金额 / 递增单价 / 递增百分比 **只会填其中一个**,按填了哪个拼单位;
|
||||
若历史数据里同时填了多个,按 百分比 → 金额 → 单价 的优先级取第一个。
|
||||
每几年递增 > 1 时写「每N年递增」,否则写「每年递增」。
|
||||
"""
|
||||
if not ref_line:
|
||||
return ''
|
||||
if ref_line.annual_increase_percentage:
|
||||
value, unit = ref_line.annual_increase_percentage, '%'
|
||||
elif ref_line.annual_increase_amount:
|
||||
value, unit = ref_line.annual_increase_amount, '元'
|
||||
elif ref_line.rent_unit:
|
||||
value, unit = ref_line.rent_unit, '元/㎡'
|
||||
else:
|
||||
return ''
|
||||
few_years = ref_line.increments_few_years or 0
|
||||
every = '每年' if few_years <= 1 else '每%s年' % few_years
|
||||
text = '%s递增%s%s' % (every, self._fmt_num(value), unit)
|
||||
if ref_line.first_years_begin:
|
||||
text = '第%s年起%s' % (ref_line.first_years_begin, text)
|
||||
return text
|
||||
|
||||
def _build_assessment_export_rows(self):
|
||||
"""按物业明细行生成 25 列的数据行;最后 5 列(分租/整租单价总价、备注)留空"""
|
||||
rows = []
|
||||
for index, line in enumerate(self.property_ids, start=1):
|
||||
prop = line.property_id
|
||||
ref_line = prop._get_valid_reference_line() if prop else prop
|
||||
title_line = prop._get_valid_title_line() if prop else prop
|
||||
certificate = ''
|
||||
if title_line and title_line.property_message_id:
|
||||
certificate = title_line.property_message_id.title_certificate or ''
|
||||
rows.append([
|
||||
index, # A 序号
|
||||
prop.name or '', # B 地址及位置
|
||||
title_line.owner_id.name if title_line and title_line.owner_id else '', # C 权属人/产权持有者
|
||||
certificate, # D 权证编号
|
||||
title_line.equity_area if title_line and title_line.equity_area else None, # E 证载面积(㎡)
|
||||
title_line.registered_id.name if title_line and title_line.registered_id else '', # F 证载用途
|
||||
prop.building_structure or '', # G 建筑结构
|
||||
prop.rental_floor or '', # H 出租楼层
|
||||
prop.property_floor_id.show_name or '' if prop.property_floor_id else '', # I 总楼层
|
||||
prop.construction_year or '', # J 建筑年代
|
||||
prop.shared_land_area or None, # K 共用地面积(㎡)
|
||||
prop.land_nature or '', # L 土地性质
|
||||
self._selection_label('yuthon.property', 'land_acquisition', prop.land_acquisition), # M 取得方式
|
||||
self._selection_label('yuthon.property', 'property_type', prop.property_type), # N 用途
|
||||
prop.land_end_date or None, # O 土地终止日期
|
||||
line.evaluation_area or None, # P 出租/评估面积(㎡)
|
||||
prop.asset_type_id.name or '' if prop.asset_type_id else '', # Q 出租用途
|
||||
'不超过%s年' % self._fmt_num(ref_line.lease_period) if ref_line and ref_line.lease_period else '', # R 租期
|
||||
'不超过%s天' % self._fmt_num(ref_line.rent_period) if ref_line and ref_line.rent_period else '', # S 免租期
|
||||
self._increase_text(ref_line), # T 租金递增
|
||||
None, None, None, None, None, # U~Y 留空(评估结果 + 备注)
|
||||
])
|
||||
return rows
|
||||
|
||||
def action_export_assessment_result(self):
|
||||
"""导出资产评估结果明细表(按模板生成 xlsx 并下载)"""
|
||||
self.ensure_one()
|
||||
if not self.property_ids:
|
||||
raise UserError('该评估申请没有物业明细行,无法导出')
|
||||
|
||||
try:
|
||||
template_path = file_path(self.EXPORT_TEMPLATE_PATH)
|
||||
except FileNotFoundError:
|
||||
raise UserError('未找到导出模板文件,请检查模块文件是否完整')
|
||||
workbook = load_workbook(template_path)
|
||||
sheet = workbook[workbook.sheetnames[0]]
|
||||
|
||||
col_count = self.EXPORT_COL_COUNT
|
||||
max_row = sheet.max_row
|
||||
|
||||
# ① 定位表头(A 列为「序号」的是第一级表头行,其下含「证载面积」的是第二级表头行),
|
||||
# 数据区紧随表头之后;再定位「合计」行,其后的内容(备注说明 / 调整表 / 落款)整体搬移
|
||||
data_start = self._find_assessment_data_start(sheet, max_row)
|
||||
total_row = None
|
||||
for row in range(data_start, max_row + 1):
|
||||
if str(sheet.cell(row, 1).value or '').strip() == '合计':
|
||||
total_row = row
|
||||
break
|
||||
if total_row is None:
|
||||
raise UserError('导出模板格式异常:未找到「合计」行')
|
||||
|
||||
data_styles = {col: copy(sheet.cell(data_start, col)._style) for col in range(1, col_count + 1)}
|
||||
total_styles = {col: copy(sheet.cell(total_row, col)._style) for col in range(1, col_count + 1)}
|
||||
|
||||
# 以「合计」行为原点 0,把它和它之后的内容(备注说明 / 调整表 / 落款)整体记录、稍后搬移
|
||||
tail = {} # (相对行, 列) -> (值, 样式)
|
||||
tail_heights = {} # 相对行 -> 行高
|
||||
tail_last_offset = 0
|
||||
for offset, row in enumerate(range(total_row, max_row + 1)):
|
||||
tail_heights[offset] = sheet.row_dimensions[row].height
|
||||
for col in range(1, col_count + 1):
|
||||
cell = sheet.cell(row, col)
|
||||
tail[(offset, col)] = (cell.value, copy(cell._style))
|
||||
if cell.value is not None:
|
||||
tail_last_offset = offset
|
||||
tail_merges = [
|
||||
(r.min_row - total_row, r.min_col, r.max_row - total_row, r.max_col)
|
||||
for r in sheet.merged_cells.ranges
|
||||
if r.min_row >= total_row
|
||||
]
|
||||
|
||||
# ② 解除数据区以后的合并,并彻底清空数据区到表尾
|
||||
for name in [str(r) for r in sheet.merged_cells.ranges if r.min_row >= data_start]:
|
||||
sheet.unmerge_cells(name)
|
||||
for row in range(data_start, max_row + 1):
|
||||
for col in range(1, col_count + 1):
|
||||
cell = sheet.cell(row, col)
|
||||
cell.value = None
|
||||
cell.border = Border()
|
||||
cell.fill = PatternFill()
|
||||
cell.font = Font()
|
||||
cell.alignment = Alignment()
|
||||
|
||||
# ③ 写入数据行(每行一个物业,样式沿用模板数据行,边框统一补齐)
|
||||
data_rows = self._build_assessment_export_rows()
|
||||
side = Side(style='thin')
|
||||
full_border = Border(left=side, right=side, top=side, bottom=side)
|
||||
for offset, values in enumerate(data_rows):
|
||||
row = data_start + offset
|
||||
for col in range(1, col_count + 1):
|
||||
cell = sheet.cell(row, col)
|
||||
cell._style = copy(data_styles[col])
|
||||
cell.value = values[col - 1]
|
||||
cell.border = full_border
|
||||
if not sheet.row_dimensions[row].height or sheet.row_dimensions[row].height < 20:
|
||||
sheet.row_dimensions[row].height = 30
|
||||
|
||||
# ④ 把「合计」行及其后的固定内容整体下移到数据行之后
|
||||
total_target = data_start + len(data_rows)
|
||||
for (offset, col), (value, style) in tail.items():
|
||||
cell = sheet.cell(total_target + offset, col)
|
||||
cell._style = copy(style)
|
||||
cell.value = value
|
||||
for offset, height in tail_heights.items():
|
||||
if height:
|
||||
sheet.row_dimensions[total_target + offset].height = height
|
||||
for min_row, min_col, max_row_, max_col in tail_merges:
|
||||
sheet.merge_cells(
|
||||
start_row=total_target + min_row, start_column=min_col,
|
||||
end_row=total_target + max_row_, end_column=max_col)
|
||||
|
||||
# ⑤ 合计行的面积两列按实际行数求和(金额列留空)
|
||||
last_data_row = data_start + len(data_rows) - 1
|
||||
for col in range(1, col_count + 1):
|
||||
sheet.cell(total_target, col)._style = copy(total_styles[col])
|
||||
sheet.cell(total_target, 1, value='合计')
|
||||
sheet.cell(total_target, 5, value='=SUM(E%s:E%s)' % (data_start, last_data_row))
|
||||
sheet.cell(total_target, 16, value='=SUM(P%s:P%s)' % (data_start, last_data_row))
|
||||
|
||||
# ⑥ 删掉模板残留的空行,避免表格下方拖出一大片空白
|
||||
tail_len = tail_last_offset + 1
|
||||
first_empty_row = total_target + tail_len
|
||||
if sheet.max_row >= first_empty_row:
|
||||
sheet.delete_rows(first_empty_row, sheet.max_row - first_empty_row + 1)
|
||||
|
||||
# ⑦ 文档级信息:评估基准日 / 委托人(取表单上的,模板里的示例值一律覆盖)
|
||||
base_date = self.date_assessment
|
||||
sheet.cell(2, 1, value='评估基准日:%s' % (base_date.strftime('%Y年%m月%d日') if base_date else ''))
|
||||
sheet.cell(3, 1, value='委托人:%s' % (self.users_id.name or ''))
|
||||
|
||||
# ⑧ 落款:评估人员留空(由评估机构填),评估机构取表单上的「评估公司」
|
||||
company_name = self.evaluation_company_id.name or ''
|
||||
for row in range(total_target, sheet.max_row + 1):
|
||||
for col in range(1, col_count + 1):
|
||||
value = sheet.cell(row, col).value
|
||||
if not isinstance(value, str):
|
||||
continue
|
||||
if value.startswith('评估人员:'):
|
||||
sheet.cell(row, col, value='评估人员:')
|
||||
elif value.startswith('评估机构:'):
|
||||
sheet.cell(row, col, value='评估机构:%s' % company_name)
|
||||
|
||||
output = io.BytesIO()
|
||||
workbook.save(output)
|
||||
output.seek(0)
|
||||
file_name = '资产评估结果明细表-%s.xlsx' % (self.code or self.name or self.id)
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'name': file_name,
|
||||
'type': 'binary',
|
||||
'datas': base64.b64encode(output.read()),
|
||||
'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
})
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/web/content/%s?download=true' % attachment.id,
|
||||
'target': 'self',
|
||||
}
|
||||
|
||||
def _on_workflow_transfer(self):
|
||||
"""「转交」(含非退回的加签)成功后的业务钩子。
|
||||
|
||||
与挂牌申报表 / 成交确认书同一口径:**保存只置「待X」,转交才置「X中」**。
|
||||
评估申请真正流转时才把关联的评估方案与物业置为「评估中」,单纯保存不算。
|
||||
触发点见 soong_workflow/wizard/soong_workflow_comment_wizard.py。
|
||||
"""
|
||||
for application in self:
|
||||
plans = application.property_ids.mapped('property_evaluation_id')
|
||||
waiting_plans = plans.filtered(lambda plan: plan.property_state == 'to')
|
||||
if waiting_plans:
|
||||
waiting_plans.write({'property_state': 'ing'})
|
||||
# 物业自己的业务状态同步为「评估中」,不动已是「评估中/已评估及之后」的物业
|
||||
waiting_props = plans.mapped('property_id').filtered(
|
||||
lambda prop: prop.business_state in (False, '', 'to'))
|
||||
if waiting_props:
|
||||
waiting_props.write({'business_state': 'ing'})
|
||||
|
||||
@api.onchange('is_issues')
|
||||
def _onchange_is_issues(self):
|
||||
if self.is_issues:
|
||||
@@ -84,7 +345,7 @@ class YuhtonPropertyLine(models.Model):
|
||||
|
||||
property_message_id = fields.Many2one('property.evaluate.application', string='关联评估申请')
|
||||
property_evaluation_id = fields.Many2one('property.evaluation.plan', string="关联物业评估方案")
|
||||
property_ids = fields.Many2many(related='property_evaluation_id.property_ids', string="物业信息")
|
||||
property_id = fields.Many2one(related='property_evaluation_id.property_id', string="物业信息")
|
||||
owner_id = fields.Many2one('yuthon.owner.message', related='property_evaluation_id.owner_id', string="权属人")
|
||||
assessment_category = fields.Selection(related='property_evaluation_id.assessment_category', string="评估类别")
|
||||
lease_period = fields.Integer(related='property_evaluation_id.lease_period', string='租期期限(<=)')
|
||||
@@ -103,7 +364,7 @@ class YuhtonPropertyLine(models.Model):
|
||||
"view_mode": "tree",
|
||||
"res_model": "yuthon.property",
|
||||
"target": "new",
|
||||
"domain": [('id', 'in', self.property_ids.ids)],
|
||||
"domain": [('id', '=', self.property_id.id)],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ from numpy.ma.core import default_filler
|
||||
import re
|
||||
from odoo import models, fields, api, exceptions, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PropertyContractLine(models.Model):
|
||||
@@ -216,6 +219,9 @@ class PropertyLeaseContract(models.Model):
|
||||
term_end_time = fields.Date(string="合约结束时间")
|
||||
actual_start_time = fields.Date(string="实际开始时间")
|
||||
actual_end_time = fields.Date(string="实际结束时间")
|
||||
# 距实际结束日期不足 3 个月时,每日任务把物业业务状态推回「待评估」;
|
||||
# 该字段记录推送日期,保证每份合同只推送一次(不会被反复重置)
|
||||
evaluation_pushed_date = fields.Date(string="评估推送日期", copy=False)
|
||||
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)
|
||||
@@ -1006,6 +1012,13 @@ class PropertyLeaseContract(models.Model):
|
||||
'actual_start_time': self.term_start_time,
|
||||
'actual_end_time': self.term_end_time,
|
||||
})
|
||||
# 审批结束:合同明细行上的物业 -> 已签约
|
||||
signed_props = self.mapped('lease_ids.address_id').filtered(
|
||||
lambda addr: addr.id and addr.business_state != 'signed')
|
||||
if signed_props:
|
||||
signed_props.write({'business_state': 'signed'})
|
||||
_logger.info('合同审批结束:物业置为已签约 %s', signed_props.ids)
|
||||
|
||||
old_contract_id = self.env['property.lease.contract'].search([
|
||||
('contract_code', '=', self.contract_code),
|
||||
('id', '!=', self.id),
|
||||
@@ -2265,6 +2278,35 @@ class PropertyLeaseContract(models.Model):
|
||||
for contract in executing_contracts:
|
||||
contract.update_contact_state()
|
||||
|
||||
def task_contract_expire_to_evaluate(self):
|
||||
"""每日任务:正在执行、且距「实际结束时间」不足 3 个月的合同,
|
||||
把其「物业信息明细行」上物业的业务状态置为「待评估」。
|
||||
|
||||
- 只找 contact_state = 执行中 的合同;
|
||||
- 实际结束日期落在 [今天, 今天 + 90 天] 之内(已到期的合同不处理);
|
||||
- 每份合同只推送一次(用 evaluation_pushed_date 记录),避免物业已经进入
|
||||
评估 / 挂牌流程后又被每天重置回「待评估」。
|
||||
"""
|
||||
today = fields.Date.today()
|
||||
deadline = today + timedelta(days=90)
|
||||
contracts = self.search([
|
||||
('contact_state', '=', 'ing'),
|
||||
('actual_end_time', '!=', False),
|
||||
('actual_end_time', '>=', today),
|
||||
('actual_end_time', '<=', deadline),
|
||||
('evaluation_pushed_date', '=', False),
|
||||
])
|
||||
if not contracts:
|
||||
return
|
||||
for contract in contracts:
|
||||
properties = contract.lease_ids.mapped('address_id')
|
||||
if properties:
|
||||
properties.write({'business_state': 'to'})
|
||||
contract.evaluation_pushed_date = today
|
||||
_logger.info('合同到期前评估推送:合同 %s(实际结束 %s)→ 物业 %s 置为待评估',
|
||||
contract.contract_code or contract.id, contract.actual_end_time,
|
||||
properties.ids)
|
||||
|
||||
def update_contact_state(self):
|
||||
for contract in self:
|
||||
receivable_records = self.env['property.accounts.receivable'].search([
|
||||
@@ -2500,7 +2542,7 @@ class PropertyLeaseContract(models.Model):
|
||||
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)],
|
||||
'property_id': property_ids[0] if property_ids else None,
|
||||
'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,
|
||||
@@ -2531,7 +2573,7 @@ class PropertyLeaseContract(models.Model):
|
||||
rent_area += lease_id.address_id.rent_area
|
||||
self.env['property.evaluation.plan'].create({
|
||||
'property_name': record.property_name,
|
||||
'property_ids': property_ids,
|
||||
'property_id': property_ids[0] if property_ids else None,
|
||||
'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,
|
||||
|
||||
@@ -47,9 +47,17 @@ class PropertyMeetingApproval(models.Model):
|
||||
})
|
||||
|
||||
def button_rental(self):
|
||||
# 已进入签约流程(待签约/签约中/已签约)的物业不放进挂牌申报表
|
||||
form_model = self.env['property.rental.form']
|
||||
lines = self.meeting_line_ids.filtered(
|
||||
lambda line: line.property_id
|
||||
and form_model._get_listable_properties(line.property_id))
|
||||
if not lines:
|
||||
raise UserError('所选物业均已进入签约流程(待签约/签约中/已签约),不能在挂牌申报表中提交')
|
||||
form_model._check_listing_available(lines.mapped('property_id'))
|
||||
property_list = []
|
||||
form_id = self.env.ref('yuthon_property_approval.view_property_rental_form_form').id
|
||||
for record in self.meeting_line_ids:
|
||||
for record in lines:
|
||||
property_list.append([0, 0, {
|
||||
'property_id': record.property_id.id,
|
||||
}])
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
from odoo import models, fields, api
|
||||
from odoo import models, fields, api, Command
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
# 已进入签约流程的业务状态:这些物业不再放入挂牌申报表
|
||||
SIGN_FLOW_STATES = ('to_sign', 'signing', 'signed')
|
||||
|
||||
|
||||
class PropertyRentalForm(models.Model):
|
||||
@@ -29,6 +34,120 @@ class PropertyRentalForm(models.Model):
|
||||
|
||||
name_title = fields.Char(default=_get_default_name_title)
|
||||
self_users_ids = fields.Many2many('res.users', string="当前经办人", default=lambda self: [(6, 0, [self.env.user.id])])
|
||||
# 「经办人 / 审批状态」取自本单据的工作流记录(口径同 property.lease.contract)
|
||||
employee_ids = fields.Many2many('hr.employee', string="当前经办人", compute='_compute_workflow')
|
||||
approve_state = fields.Selection([("start", "草稿"), ("pending", "审批中"), ("end", "结束")],
|
||||
string="审批状态", compute='_compute_workflow')
|
||||
# 业务状态:取物业明细「第一个」行的物业业务状态(只读展示)
|
||||
business_state = fields.Selection(
|
||||
related='property_rental_form_line_ids.property_id.business_state', string='业务状态')
|
||||
|
||||
@api.depends('code')
|
||||
def _compute_workflow(self):
|
||||
for record in self:
|
||||
workflow = record._get_active_workflow_record()
|
||||
record.employee_ids = workflow.employee_ids if workflow else False
|
||||
record.approve_state = workflow.approve_state if workflow else False
|
||||
|
||||
@api.model
|
||||
def _get_listable_properties(self, properties):
|
||||
"""剔除已进入签约流程(待签约 / 签约中 / 已签约)的物业 —— 这些不放进挂牌申报表"""
|
||||
return properties.filtered(lambda prop: prop.business_state not in SIGN_FLOW_STATES)
|
||||
|
||||
@api.model
|
||||
def _check_listing_available(self, properties):
|
||||
"""挂牌申请的前置校验:
|
||||
① 物业尚未进入挂牌流程(业务状态为 挂牌中/已挂牌 时不可再提交;待签约/签约中/已签约
|
||||
已由 `_get_listable_properties()` 在入口处剔除,此处作兜底);
|
||||
② 同一物业不能重复申请 —— 已存在「未进回收站」的挂牌申报表即视为已申请过。
|
||||
"""
|
||||
labels = dict(self.env['yuthon.property']._fields['business_state'].selection)
|
||||
locked_states = ('listing', 'listed', 'to_sign', 'signing', 'signed')
|
||||
not_ready = properties.filtered(lambda prop: prop.business_state in locked_states)
|
||||
if not_ready:
|
||||
detail = '、'.join(
|
||||
'%s(%s)' % (prop.name, labels.get(prop.business_state) or '未设置')
|
||||
for prop in not_ready)
|
||||
raise UserError('以下物业已进入挂牌/签约流程,不能提交挂牌申请:%s' % detail)
|
||||
|
||||
duplicated = self._find_duplicated_properties(properties)
|
||||
if duplicated:
|
||||
raise UserError('以下物业已提交过挂牌申请(未进回收站),不能重复申请:%s'
|
||||
% '、'.join(duplicated.mapped('name')))
|
||||
|
||||
@api.model
|
||||
def _find_duplicated_properties(self, properties):
|
||||
"""已存在「未进回收站」的挂牌申报表的物业(判断依据:该单据有有效的工作流记录)"""
|
||||
forms = self.env['property.rental.form'].search([
|
||||
('property_rental_form_line_ids.property_id', 'in', properties.ids),
|
||||
])
|
||||
alive = forms.filtered(lambda form: form._has_active_workflow_record())
|
||||
return alive.mapped('property_rental_form_line_ids.property_id') & properties
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
# 已进入签约流程(待签约 / 签约中 / 已签约)的物业**不放进挂牌申报表**:在建表前直接剔除这些明细行
|
||||
prop_ids = []
|
||||
for vals in vals_list:
|
||||
cmds = vals.get('property_rental_form_line_ids') or []
|
||||
kept = []
|
||||
for cmd in cmds:
|
||||
if cmd[0] == Command.CREATE and len(cmd) > 2 and cmd[2].get('property_id'):
|
||||
prop = self.env['yuthon.property'].browse(cmd[2]['property_id'])
|
||||
if prop.business_state in SIGN_FLOW_STATES:
|
||||
continue
|
||||
prop_ids.append(prop.id)
|
||||
kept.append(cmd)
|
||||
if len(kept) != len(cmds):
|
||||
if not kept:
|
||||
raise UserError('所选物业均已进入签约流程(待签约/签约中/已签约),不能在挂牌申报表中提交')
|
||||
vals['property_rental_form_line_ids'] = kept
|
||||
# 再校验(在建表之前):已进入挂牌/签约流程的物业不能提交
|
||||
if prop_ids:
|
||||
self._check_listing_available(self.env['yuthon.property'].browse(prop_ids))
|
||||
|
||||
forms = super().create(vals_list)
|
||||
# 保存时业务状态为「待挂牌」;推进到「挂牌中」是在**转交**时(见 _on_workflow_transfer)
|
||||
props = forms.mapped('property_rental_form_line_ids.property_id')
|
||||
waiting = props.filtered(
|
||||
lambda prop: prop.business_state not in ('listing', 'listed', 'to_sign', 'signing', 'signed'))
|
||||
if waiting:
|
||||
waiting.write({'business_state': 'to_list'})
|
||||
return forms
|
||||
|
||||
def _on_workflow_transfer(self):
|
||||
"""转交(工作流弹窗触发,仅挂牌申报表/成交确认书):关联物业业务状态「待挂牌」→「挂牌中」"""
|
||||
props = self.mapped('property_rental_form_line_ids.property_id')
|
||||
waiting = props.filtered(lambda prop: prop.business_state in (False, '', 'to_list'))
|
||||
if waiting:
|
||||
waiting.write({'business_state': 'listing'})
|
||||
|
||||
def end_approval(self):
|
||||
"""审批结束:关联物业的业务状态推进为「已挂牌」"""
|
||||
props = self.mapped('property_rental_form_line_ids.property_id')
|
||||
waiting = props.filtered(lambda prop: prop.business_state in (False, '', 'listing'))
|
||||
if waiting:
|
||||
waiting.write({'business_state': 'listed'})
|
||||
|
||||
def action_transaction_confirmed(self):
|
||||
"""成交确认书:把本单的物业带进「成交确认审批」(property.confirm) 的物业列表"""
|
||||
self.ensure_one()
|
||||
properties = self.property_rental_form_line_ids.mapped('property_id')
|
||||
if not properties:
|
||||
raise UserError('该挂牌申报表未关联物业,无法生成成交确认书')
|
||||
form_id = self.env.ref('yuthon_property_approval.view_property_confirm_form').id
|
||||
return {
|
||||
'name': '成交确认审批',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'property.confirm',
|
||||
'view_mode': 'form',
|
||||
'views': [(form_id, 'form')],
|
||||
'target': 'current',
|
||||
'context': {
|
||||
'default_lease_ids': [(0, 0, {'address_id': prop.id}) for prop in properties],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class PropertyRentalFormLine(models.Model):
|
||||
_name = 'property.rental.form.line'
|
||||
|
||||
@@ -4,8 +4,12 @@ from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class PropertyTransactionConfirmed(models.Model):
|
||||
"""⚠️ 已作废:成交确认书业务已迁到 property.confirm(成交确认审批)。
|
||||
模型与视图保留仅供历史数据查看,菜单/按钮均已不再指向本模型,
|
||||
挂牌状态推进与「创建合同」逻辑已移除(现由 property.confirm 负责)。
|
||||
"""
|
||||
_name = 'property.transaction.confirmed'
|
||||
_description = '成交确认书'
|
||||
_description = '成交确认书(已作废)'
|
||||
_inherit = ['mail.thread.main.attachment', 'mail.activity.mixin', 'soong.workflow.mixin']
|
||||
|
||||
name = fields.Char(string='标题')
|
||||
@@ -32,6 +36,17 @@ class PropertyTransactionConfirmed(models.Model):
|
||||
|
||||
name_title = fields.Char(default=_get_default_name_title)
|
||||
self_users_ids = fields.Many2many('res.users', string="当前经办人", default=lambda self: [(6, 0, [self.env.user.id])])
|
||||
# 「经办人 / 审批状态」取自本单据的工作流记录(口径同 property.lease.contract)
|
||||
employee_ids = fields.Many2many('hr.employee', string="当前经办人", compute='_compute_workflow')
|
||||
approve_state = fields.Selection([("start", "草稿"), ("pending", "审批中"), ("end", "结束")],
|
||||
string="审批状态", compute='_compute_workflow')
|
||||
|
||||
@api.depends('code')
|
||||
def _compute_workflow(self):
|
||||
for record in self:
|
||||
workflow = record._get_active_workflow_record()
|
||||
record.employee_ids = workflow.employee_ids if workflow else False
|
||||
record.approve_state = workflow.approve_state if workflow else False
|
||||
|
||||
@api.onchange('is_issues')
|
||||
def _onchange_is_issues(self):
|
||||
|
||||
@@ -5,8 +5,18 @@
|
||||
<field name="model">property.confirm</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="成交确认">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="name" string="标题"/>
|
||||
<field name="property_name"/>
|
||||
<field name="lessee"/>
|
||||
<field name="property_area"/>
|
||||
<field name="lease_term_years"/>
|
||||
<field name="rent_free_days"/>
|
||||
<field name="employee_ids" widget="many2many_tags"/>
|
||||
<field name="business_state" string="业务状态"/>
|
||||
<field name="approve_state" string="审批状态" widget="badge"
|
||||
decoration-danger="approve_state == 'end'" decoration-success="approve_state == 'pending'"/>
|
||||
<button name="action_create_contract" type="object" string="创建合同" class="btn btn-primary"
|
||||
invisible="approve_state != 'end'"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -50,21 +50,30 @@
|
||||
<group>
|
||||
<field name="users_id" invisible="1"/>
|
||||
<field name="department_id" invisible="1"/>
|
||||
<field name="quote"/>
|
||||
<field name="evaluation_company_id"/>
|
||||
<field name="date_assessment"/>
|
||||
<field name="service_money"/>
|
||||
<field name="pay_time"/>
|
||||
<field name="pay_way"/>
|
||||
<!-- 左右两栏:左 3 个 / 右 3 个 -->
|
||||
<group>
|
||||
<field name="evaluation_company_id"/>
|
||||
<field name="date_assessment"/>
|
||||
<field name="service_money"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="pay_time"/>
|
||||
<field name="pay_way"/>
|
||||
</group>
|
||||
<field name="is_experts"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="待评估物业出租方案明细表">
|
||||
<div class="mb-2">
|
||||
<button name="action_export_assessment_result" type="object"
|
||||
string="导出" class="btn-primary"
|
||||
invisible="not property_ids"/>
|
||||
</div>
|
||||
<field name="property_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="property_message_id" column_invisible="1"/>
|
||||
<field name="property_evaluation_id" class="list_width" string="物业"/>
|
||||
<field name="property_ids" widget="many2many_tags" optional="hide"/>
|
||||
<field name="property_id" optional="hide"/>
|
||||
<field name="evaluation_area"/>
|
||||
<field name="assessment_category"/>
|
||||
<field name="owner_id"/>
|
||||
|
||||
@@ -151,13 +151,6 @@
|
||||
action="property_confirm_action"
|
||||
sequence="5"/>
|
||||
|
||||
<menuitem
|
||||
id="property_assess_approval_menu"
|
||||
parent="yuthon_property.yuthon_property_menu"
|
||||
name="评估申请"
|
||||
action="property_evaluate_application_action"
|
||||
sequence="7"/>
|
||||
|
||||
<menuitem
|
||||
id="property_record_approval_menu"
|
||||
parent="yuthon_property.yuthon_property_menu"
|
||||
@@ -200,11 +193,12 @@
|
||||
parent="yuthon_property.yuthon_property_menu_data"
|
||||
action="property_offline_registration_action"/>
|
||||
|
||||
<!-- 成交确认书菜单指向「成交确认审批」(property.confirm);原 property.transaction.confirmed 已作废 -->
|
||||
<menuitem id="yuthon_property_confirmation_of_transaction_menu"
|
||||
sequence="5"
|
||||
name="成交确认书"
|
||||
parent="yuthon_property.yuthon_property_menu_data"
|
||||
action="action_property_transaction_confirmed"/>
|
||||
action="property_confirm_action"/>
|
||||
|
||||
<menuitem id="property_rental_conditions_menu"
|
||||
sequence="10"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<field name="name">property.merge.approval.tree</field>
|
||||
<field name="model">property.merge.approval</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="合并管理">
|
||||
<tree string="合并管理" default_order="start_date desc">
|
||||
<field name="property_name"/>
|
||||
<field name="code"/>
|
||||
<field name="merge_asset_type_id"/>
|
||||
|
||||
@@ -5,8 +5,14 @@
|
||||
<field name="model">property.rental.form</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="挂牌申报表">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="listing_method"/>
|
||||
<field name="business_state" string="业务状态"/>
|
||||
<field name="employee_ids" widget="many2many_tags"/>
|
||||
<field name="approve_state" string="审批状态" widget="badge"
|
||||
decoration-danger="approve_state == 'end'" decoration-success="approve_state == 'pending'"/>
|
||||
<button name="action_transaction_confirmed" type="object" string="成交确认书" class="btn btn-primary"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -51,7 +57,8 @@
|
||||
<field name="property_rental_form_line_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="property_rental_form_id" column_invisible="1"/>
|
||||
<field name="property_id" options="{'no_create': True}"/>
|
||||
<field name="property_id" options="{'no_create': True}"
|
||||
domain="[('business_state', 'not in', ['to_sign', 'signing', 'signed'])]"/>
|
||||
<field name="registered_id" optional="hide"/>
|
||||
<field name="evaluation_area" optional="hide"/>
|
||||
<field name="current_status"/>
|
||||
@@ -77,5 +84,9 @@
|
||||
<field name="name">挂牌申报表</field>
|
||||
<field name="res_model">property.rental.form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<!-- 已进入签约阶段(待签约 / 签约中 / 已签约)的不在挂牌申报表列表显示;
|
||||
尚未填明细行的空单仍然显示,避免刚保存的表单从列表消失 -->
|
||||
<field name="domain">['|', ('property_rental_form_line_ids', '=', False),
|
||||
('business_state', 'not in', ['to_sign', 'signing', 'signed'])]</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -4,7 +4,7 @@
|
||||
<field name="name">property.split.approval.tree</field>
|
||||
<field name="model">property.split.approval</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="物业拆分审批">
|
||||
<tree string="物业拆分审批" default_order="effective_date desc">
|
||||
<field name="property_id"/>
|
||||
<field name="code"/>
|
||||
<field name="units"/>
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<field name="name"/>
|
||||
<field name="lessee_id"/>
|
||||
<field name="entity_type"/>
|
||||
<field name="employee_ids" widget="many2many_tags"/>
|
||||
<field name="approve_state" string="审批状态" widget="badge"
|
||||
decoration-danger="approve_state == 'end'" decoration-success="approve_state == 'pending'"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -316,6 +316,50 @@ class PropertyReconciliationWizard(models.Model):
|
||||
line.color = 'success'
|
||||
|
||||
|
||||
def _get_precinct_admin_employees(self, properties):
|
||||
"""取物业对应的片区管理员(hr.employee):
|
||||
优先用物业上的「片区管理员」反查员工,取不到时退回物业「管理信息明细」里片区配置的管理员。
|
||||
只保留配置了企业微信 userid 的员工。
|
||||
"""
|
||||
employees = self.env['hr.employee'].sudo()
|
||||
users = properties.mapped('admin_id')
|
||||
if users:
|
||||
employees = employees.search([('user_id', 'in', users.ids)])
|
||||
if not employees:
|
||||
employees = properties.mapped('manage_information_ids.precinct_id.employee_id')
|
||||
return employees.filtered(lambda emp: emp.wecom_userid)
|
||||
|
||||
def _notify_registered(self, reconciled_amount):
|
||||
"""对账变成「挂账」时,给对应片区管理员发企业微信提醒"""
|
||||
self.ensure_one()
|
||||
bank = self.bank_id
|
||||
contracts = self.contract_ids
|
||||
properties = contracts.mapped('lease_ids.address_id') or bank.property_ids
|
||||
employees = self._get_precinct_admin_employees(properties)
|
||||
if not employees:
|
||||
_logger.info('挂账提醒:银行流水 %s 未找到可通知的片区管理员(物业 %s)', bank.id, properties.ids)
|
||||
return
|
||||
lessee_names = '、'.join(sorted(set(contracts.mapped('lessee_id.name')))) or (bank.name_account or '')
|
||||
contract_codes = '、'.join(sorted(set(contracts.mapped('contract_code')))) or '-'
|
||||
description = (
|
||||
'承租方:%s<br/>'
|
||||
'合同:%s<br/>'
|
||||
'交易金额:%.2f<br/>'
|
||||
'对账金额:%.2f<br/>'
|
||||
'未对金额:%.2f<br/>'
|
||||
'请核对'
|
||||
) % (lessee_names, contract_codes, bank.original_amount or 0.0,
|
||||
reconciled_amount or 0.0, bank.registered or 0.0)
|
||||
url = 'https://oa.thtzjt.com/web#id=%s&model=property.bank.statements&view_type=form&cids=1' % bank.id
|
||||
for employee in employees:
|
||||
try:
|
||||
self.sudo().env['wecom.apps'].sync_send_message_textcard(
|
||||
category='oa', user_id=employee.wecom_userid,
|
||||
title='银行对账挂账提醒', description=description,
|
||||
url=url, btntxt='查看对账')
|
||||
except Exception:
|
||||
_logger.exception('挂账提醒发送失败:银行流水 %s → 员工 %s', bank.id, employee.name)
|
||||
|
||||
def confirm(self):
|
||||
collection_type_mapping = {
|
||||
'deposit': '押金',
|
||||
@@ -331,6 +375,7 @@ class PropertyReconciliationWizard(models.Model):
|
||||
'other': '其他'
|
||||
}
|
||||
for rec in self:
|
||||
was_registered = rec.bank_id.bank_state == 'registered'
|
||||
bank_lines = self.env['property.bank.line'].search([
|
||||
('bank', '=', rec.bank_id.name),
|
||||
('tenant_id.name', '!=', False)
|
||||
@@ -525,6 +570,10 @@ class PropertyReconciliationWizard(models.Model):
|
||||
rec.bank_id.yes_amount = rec.bank_id.yes_amount + yes_amount
|
||||
rec.bank_id.change_amount = sum(line.amount for line in rec.reconciliation_line_ids)
|
||||
|
||||
# 本次对账后状态变为「挂账」→ 给对应片区管理员发提醒(原本已是挂账的不重复发)
|
||||
if rec.bank_id.bank_state == 'registered' and not was_registered:
|
||||
rec._notify_registered(remaining_amount)
|
||||
|
||||
all_contracts = self.contract_ids
|
||||
remaining_contract_ids = []
|
||||
for contract in all_contracts:
|
||||
|
||||
Reference in New Issue
Block a user