添加功能

This commit is contained in:
李鹏宇
2026-09-20 20:25:00 +08:00
parent d3ed3e49bc
commit 63e93b39d6
16 changed files with 468 additions and 430 deletions
@@ -126,9 +126,8 @@ class PropertyAccountsReceivable(models.Model):
is_ended = True is_ended = True
is_forfeited_deposit = False is_forfeited_deposit = False
if contract: if contract:
renewal = self.env['property.lease.renewal'].search([ renewal = self.env['property.tenant.checkout'].search([
('contract_id', '=', contract.id), ('contract_id', '=', contract.id),
('renewal_type', '=', 'early_checkout'),
('work_end', '=', True), ('work_end', '=', True),
], limit=1) ], limit=1)
if renewal: if renewal:
@@ -234,9 +233,8 @@ class PropertyAccountsReceivable(models.Model):
penalty_start_date = date(next_year, next_month, 1) penalty_start_date = date(next_year, next_month, 1)
contract = record.contract_id contract = record.contract_id
renewal = self.env['property.lease.renewal'].search([ renewal = self.env['property.tenant.checkout'].search([
('contract_id', '=', contract.id), ('contract_id', '=', contract.id),
('renewal_type', '=', 'early_checkout'),
('work_end', '=', True), ('work_end', '=', True),
], limit=1) ], limit=1)
+1
View File
@@ -80,6 +80,7 @@
'views/property_evaluate_application_views.xml', 'views/property_evaluate_application_views.xml',
'views/contract_accounts_receivable_viwes.xml', 'views/contract_accounts_receivable_viwes.xml',
'views/inherit_yuthon_property_views.xml', 'views/inherit_yuthon_property_views.xml',
'views/receivable_unpaid_views.xml',
'views/property_project_works_viwes.xml', 'views/property_project_works_viwes.xml',
'views/property_sporadic_works_views.xml', 'views/property_sporadic_works_views.xml',
'views/property_service_approval_views.xml', 'views/property_service_approval_views.xml',
@@ -68,3 +68,4 @@ from . import yuthon_property_task
from . import property_tenant_checkout from . import property_tenant_checkout
from . import property_tenant_checkout_inspection from . import property_tenant_checkout_inspection
from . import property_tenant_checkout_fee from . import property_tenant_checkout_fee
from . import property_accounts_receivable_summary
@@ -783,13 +783,10 @@ class PropertyLeaseContract(models.Model):
def action_open_contract(self): def action_open_contract(self):
self.ensure_one() self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("yuthon_property_approval.property_accounts_receivable_action") action = self.env["ir.actions.actions"]._for_xml_id(
"yuthon_property_approval.property_accounts_receivable_contract_action")
action.update({ action.update({
'domain': [('contract_id', '=', self.id)], 'domain': [('contract_id', '=', self.id)],
'context': {
'search_default_group_by_management_type_id': 1,
'group_by': 'management_type_id'
}
}) })
return action return action
@@ -20,7 +20,7 @@ class PropertyLeaseRenewal(models.Model):
company_id = fields.Many2one(related='user_id.company_id', string="公司") company_id = fields.Many2one(related='user_id.company_id', string="公司")
code = fields.Char(string="编号", code = fields.Char(string="编号",
default=lambda self: self.env['ir.sequence'].next_by_code('property_lease_renewal_code')) default=lambda self: self.env['ir.sequence'].next_by_code('property_lease_renewal_code'))
renewal_type = fields.Selection([('early_checkout', '合同终止'), ('lease', '承租方变更'), ('extend', '延长租期')], string='变更类型') renewal_type = fields.Selection([('lease', '承租方变更'), ('extend', '延长租期')], string='变更类型')
urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度', urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度',
default='normal') default='normal')
is_chapter = fields.Selection([('yes', '是'), ('no', '否')], string="是否用章") is_chapter = fields.Selection([('yes', '是'), ('no', '否')], string="是否用章")
@@ -53,20 +53,10 @@ class PropertyLeaseRenewal(models.Model):
fee = fields.Float(string='变更费用') fee = fields.Float(string='变更费用')
document_ids = fields.Many2many('ir.attachment', string="变更附件") document_ids = fields.Many2many('ir.attachment', string="变更附件")
early_explain = fields.Text(string="退租说明")
early_reason = fields.Selection([('normal', '正常退租'), ('agree', '协商一致'), ('settle', '违约结清'), ('other', '其他')], string="退租原因")
early_quitting_date = fields.Date(string="退租日期")
active = fields.Boolean('Active', default=True, tracking=True) active = fields.Boolean('Active', default=True, tracking=True)
work_end = fields.Boolean(string="结束") work_end = fields.Boolean(string="结束")
deposit = fields.Float(string="原押金")
refund_amount = fields.Float(string="退款金额")
deposit_treatment = fields.Selection([('refund', '全额退押'), ('part_refund', '部分退租'), ('no_refund', '全额没收')], string="押金处理方式", default='refund')
total_receivables = fields.Float(string="应收金额", compute="_compute_total_receivables")
forfeit = fields.Float(string="没收金额")
receivables_ids = fields.One2many('early.receivables.line', 'renewal_id', string='应收明细')
# 延长租期 # 延长租期
actual_end_date = fields.Date(string='实际结束日期') actual_end_date = fields.Date(string='实际结束日期')
@@ -92,11 +82,6 @@ class PropertyLeaseRenewal(models.Model):
# 需求调整:不做自动填充,保留用户手动输入的联系人 # 需求调整:不做自动填充,保留用户手动输入的联系人
return return
@api.onchange('refund_amount')
def _onchange_forfeit(self):
for rec in self:
rec.forfeit = rec.deposit - rec.refund_amount
@api.onchange('contact') @api.onchange('contact')
def _onchange_contact_fill_info(self): def _onchange_contact_fill_info(self):
"""填写联系人后,按员工信息带出身份证号与联系电话;不再从承租方带出。""" """填写联系人后,按员工信息带出身份证号与联系电话;不再从承租方带出。"""
@@ -114,14 +99,6 @@ class PropertyLeaseRenewal(models.Model):
rec.phone = False rec.phone = False
rec.card = False rec.card = False
@api.depends('receivables_ids', 'receivables_ids.original_amount', 'receivables_ids.reduction_amount',
'receivables_ids.void')
def _compute_total_receivables(self):
for renewal in self:
valid_receivable_lines = renewal.receivables_ids.filtered(lambda line: not line.void and line.is_reconciliation == False)
total = sum(line.original_amount - line.reduction_amount for line in valid_receivable_lines)
renewal.total_receivables = total
def action_print_lease_renewal(self): def action_print_lease_renewal(self):
"""打印租赁变更审批""" """打印租赁变更审批"""
self.ensure_one() self.ensure_one()
@@ -201,305 +178,6 @@ class PropertyLeaseRenewal(models.Model):
'management_type_id': management_type_id.id, 'management_type_id': management_type_id.id,
'due_date': rec.effective_date, 'due_date': rec.effective_date,
}) })
elif rec.renewal_type == 'early_checkout':
workflow_record_id = self.env["soong.workflow.record"].search(
[("res_model", "=", self._name), ("res_id", "=", self.id)])
lessor_id = rec.contract_id.lessor_id
company_id = self.env['res.company'].search([('name', '=', '集团本部')], limit=1)
if lessor_id.referred in ['天河投资', '天投城建']:
company_id = self.env['res.company'].search([('name', '=', '集团本部')], limit=1)
elif lessor_id.referred in ['天河物管']:
company_id = self.env['res.company'].browse(3)
elif lessor_id.referred in ['天河粮储']:
company_id = self.env['res.company'].search([('name', '=', '天河粮储')], limit=1)
# 取本审批最后一条流程日志的操作时间,作为账单 due_date
operation_log = self.env['soong.workflow.log'].search([
('model', '=', self._name),
('res_id', '=', rec.id),
], order='lase_click_date DESC, id DESC', limit=1)
operation_date = operation_log.lase_click_date.date() if operation_log.lase_click_date else fields.Date.today()
# 非押金退款明细(除押金外的退款,原逻辑保留)
if rec.receivables_ids:
refund_lines = rec.receivables_ids.filtered(lambda l: l.refund_amount > 0 and l.management_type_id.name != '押金')
bills_line_list = []
if refund_lines:
for refund_line in refund_lines:
bills_line_list.append({
'lessor_id': rec.contract_id.lessor_id.id,
'due_date': operation_date,
'management_type_id': refund_line.management_type_id.id,
'amount': refund_line.refund_amount,
'contract_id': rec.contract_id.id,
'receivables_id': refund_line.id,
'remake': rec.early_explain
})
if bills_line_list:
self.env['property.bills.list'].create(bills_line_list)
# 押金处理(根据押金处理方式分三种,先判断去重再创建)
deposit_management_type = self.env['yuthon.management.type'].search(
[('name', '=', '押金')], limit=1)
if rec.deposit_treatment == 'refund':
# 全额退押金:仅创建 bills.list,金额 = 原押金 deposit
if rec.deposit > 0:
exist_bill = self.env['property.bills.list'].search([
('contract_id', '=', rec.contract_id.id),
('management_type_id', '=', deposit_management_type.id),
('amount', '=', rec.deposit),
('workflow_record_id', '=', workflow_record_id.id),
], limit=1)
if not exist_bill:
self.env['property.bills.list'].create({
'lessor_id': rec.contract_id.lessor_id.id,
'due_date': operation_date,
'management_type_id': deposit_management_type.id,
'amount': rec.deposit,
'contract_id': rec.contract_id.id,
'remake': rec.early_explain,
'workflow_record_id': workflow_record_id.id,
})
elif rec.deposit_treatment == 'part_refund':
# 部分退租:bills.list 金额 = refund_amount;bank.statements 和 receivable 金额 = forfeit
if rec.refund_amount > 0:
exist_bill = self.env['property.bills.list'].search([
('contract_id', '=', rec.contract_id.id),
('management_type_id', '=', deposit_management_type.id),
('amount', '=', rec.refund_amount),
('workflow_record_id', '=', workflow_record_id.id),
], limit=1)
if not exist_bill:
self.env['property.bills.list'].create({
'lessor_id': rec.contract_id.lessor_id.id,
'due_date': operation_date,
'management_type_id': deposit_management_type.id,
'amount': rec.refund_amount,
'contract_id': rec.contract_id.id,
'remake': rec.early_explain,
'workflow_record_id': workflow_record_id.id,
})
if rec.forfeit > 0:
start_time = datetime.combine(rec.early_quitting_date, datetime.min.time().replace(hour=8, minute=00))
existing_bank_statement = self.env['property.bank.statements'].sudo().search([
('start_time', '=', start_time),
('original_amount', '=', rec.forfeit),
('name_account', '=', rec.lessee_id.name),
('remake', '=', rec.early_explain),
('workflow_record_id', '=', workflow_record_id.id),
], limit=1)
if not existing_bank_statement:
bank_accounts = self.env['property.bank.line'].search([
('tenant_id', '=', rec.lessee_id.id)
]).mapped('bank')
filtered_accounts = [acc for acc in bank_accounts if acc]
bank_accounts = filtered_accounts[0] if filtered_accounts else None
rec.contract_id.write({
'actual_end_time': rec.early_quitting_date
})
bank_id = self.env['property.bank.statements'].sudo().create({
'start_time': start_time,
'name': bank_accounts,
'original_amount': rec.forfeit,
'name_account': rec.lessee_id.name,
'remake': rec.early_explain,
'lending_direction': 'loan',
'workflow_record_id': workflow_record_id.id,
'company_id': company_id.id,
'your_own_unit': lessor_id.referred,
'your_own_account': lessor_id.rent_account,
})
bank_id._compute_property_employee_id()
management_type_id = self.env['yuthon.management.type'].search([
('name', '=', '违约金')
], limit=1)
exist_receivable = self.env['property.accounts.receivable'].search([
('contract_id', '=', rec.contract_id.id),
('management_type_id', '=', management_type_id.id),
('due_date', '=', rec.early_quitting_date),
('original_amount', '=', rec.forfeit),
], limit=1)
if not exist_receivable:
self.env['property.accounts.receivable'].create({
'contract_id': rec.contract_id.id,
'management_type_id': management_type_id.id,
'due_date': rec.early_quitting_date,
'original_amount': rec.forfeit,
'remark': rec.early_explain,
})
deposit_id = self.env['property.accounts.receivable'].search([
('contract_id', '=', rec.contract_id.id),
('management_type_id.name', '=', '押金'),
], limit=1)
if deposit_id:
deposit_id.write({
'workflow_record_id': workflow_record_id.id
})
elif rec.deposit_treatment == 'no_refund':
# 全额没收:bills.list、bank.statements、receivable 金额均为 deposit
if rec.deposit > 0:
exist_bill = self.env['property.bills.list'].search([
('contract_id', '=', rec.contract_id.id),
('management_type_id', '=', deposit_management_type.id),
('amount', '=', rec.deposit),
('workflow_record_id', '=', workflow_record_id.id),
], limit=1)
if not exist_bill:
bill = self.env['property.bills.list'].create({
'lessor_id': rec.contract_id.lessor_id.id,
'due_date': operation_date,
'management_type_id': deposit_management_type.id,
'amount': rec.deposit,
'contract_id': rec.contract_id.id,
'remake': rec.early_explain,
'workflow_record_id': workflow_record_id.id,
})
else:
bill = exist_bill
start_time = datetime.combine(rec.early_quitting_date, datetime.min.time().replace(hour=8, minute=00))
existing_bank_statement = self.env['property.bank.statements'].sudo().search([
('start_time', '=', start_time),
('original_amount', '=', rec.deposit),
('name_account', '=', rec.lessee_id.name),
('remake', '=', rec.early_explain),
('workflow_record_id', '=', workflow_record_id.id),
], limit=1)
if not existing_bank_statement:
bank_accounts = self.env['property.bank.line'].search([
('tenant_id', '=', rec.lessee_id.id)
]).mapped('bank')
filtered_accounts = [acc for acc in bank_accounts if acc]
bank_accounts = filtered_accounts[0] if filtered_accounts else None
rec.contract_id.write({
'actual_end_time': rec.early_quitting_date
})
bank_id = self.env['property.bank.statements'].sudo().create({
'start_time': start_time,
'name': bank_accounts,
'original_amount': rec.deposit,
'name_account': rec.lessee_id.name,
'remake': rec.early_explain,
'lending_direction': 'loan',
'workflow_record_id': workflow_record_id.id,
'company_id': company_id.id,
'your_own_unit': lessor_id.referred,
'your_own_account': lessor_id.rent_account,
})
bank_id._compute_property_employee_id()
else:
bank_id = existing_bank_statement
management_type_id = self.env['yuthon.management.type'].search([
('name', '=', '违约金')
], limit=1)
exist_receivable = self.env['property.accounts.receivable'].search([
('contract_id', '=', rec.contract_id.id),
('management_type_id', '=', management_type_id.id),
('due_date', '=', rec.early_quitting_date),
('original_amount', '=', rec.deposit),
], limit=1)
if not exist_receivable:
self.env['property.accounts.receivable'].create({
'contract_id': rec.contract_id.id,
'management_type_id': management_type_id.id,
'due_date': rec.early_quitting_date,
'original_amount': rec.deposit,
'remark': rec.early_explain,
})
# ===== 触发 action_pay + button_reconciliation 的真实逻辑 =====
# 说明:action_pay / button_reconciliation 按钮本身只是返回向导弹窗的方法,
# 真正的业务在各自 wizard 的 confirm / 对账逻辑里。这里不再手工 write 子集字段,
# 而是直接调用 property.bills.wizard 的 _do_pay()(与弹窗 confirm 共用同一份支付逻辑),
# 以及复现银行流水对账的核心效果,保证与人工点“核销/对账”完全一致。
deposit_receivable = self.env['property.accounts.receivable'].search([
('contract_id', '=', rec.contract_id.id),
('management_type_id', '=', deposit_management_type.id),
], limit=1)
# 1) 支付效果(action_pay):调用支付弹窗的真实逻辑 —— 实付日期=应付日期、标记已付、
# 回写押金应收单实付日期与支付状态、结清合同。全额没收场景下跳过 confirm 的未对账校验。
if bill and not bill.actual_date:
bill.amount_actual = bill.amount
pay_wizard = self.env['property.bills.wizard'].create({
'bills_list_id': bill.id,
'actual_date': bill.due_date,
'receivables_id': deposit_receivable.id if deposit_receivable else False,
})
pay_wizard._do_pay()
# 2) 对账效果(button_reconciliation):bank.statements 与押金应收对账
if bank_id and bank_id.bank_state != 'reconciled':
bank_id.bank_state = 'reconciled'
bank_id.yes_amount = bank_id.original_amount
bank_id.reconciliation_time = fields.Datetime.now() - timedelta(hours=8)
# 自动核销违约金
if exist_receivable:
exist_receivable.write({
'has_date': bill.due_date if bill else operation_date,
'is_reconciliation': True,
'payout_date': operation_date,
'delivered1': rec.deposit,
'deposit_state': 'paid',
'reconciliation_time': fields.Datetime.now(),
'bank_id': bank_id.id,
})
self.env['receivable.modify.line'].create({
'bank_id': bank_id.id,
'not_collected': rec.deposit,
'reconciliation_date': operation_date,
'receivable_id': exist_receivable.id,
'is_write': True,
'operation_date': fields.Date.today(),
'is_penalty': True,
'remake1': rec.early_explain,
'amount': rec.deposit,
})
rec.contract_id.contact_state = 'settle'
rec.contract_id.compute_is_has()
# 原有作废/减免逻辑保留
for receivable_id in rec.receivables_ids:
if receivable_id.void:
receivable_id.receivable_id.write({
'is_invalid': True,
'void_date': operation_date
})
if receivable_id.reduction_amount:
receivable_id.receivable_id.discount_amount = receivable_id.reduction_amount
receivable_id.receivable_id.discount_date = operation_date
@api.onchange('early_quitting_date', 'contract_id')
def _onchange_early_quitting_date(self):
self.receivables_ids = [(5, 0, 0)]
receivable_ids = self.env['property.accounts.receivable'].search([
('due_date', '<=', self.early_quitting_date),
('contract_id', '=', self.contract_id.id),
])
deposit_receivables = self.env['property.accounts.receivable']
normal_receivables = self.env['property.accounts.receivable']
for rec in receivable_ids:
if rec.management_type_id and rec.management_type_id.name == '押金':
deposit_receivables |= rec
else:
normal_receivables |= rec
if deposit_receivables:
self.deposit = sum(rec.original_amount for rec in deposit_receivables)
receivable_list = []
for rec in normal_receivables:
receivable_list.append([0, 0, {
'receivable_id': rec.id,
'is_reconciliation': rec.is_reconciliation,
}])
self.receivables_ids = receivable_list
@api.onchange('contract_id', 'renewal_type') @api.onchange('contract_id', 'renewal_type')
def _onchange_name(self): def _onchange_name(self):
@@ -511,15 +189,11 @@ class PropertyLeaseRenewal(models.Model):
else: else:
self.name = False self.name = False
class PropertyEarlyQuitting(models.Model):
_name = 'property.early.quitting'
class EarlyReceivablesLine(models.Model): class EarlyReceivablesLine(models.Model):
_name = 'early.receivables.line' _name = 'early.receivables.line'
_description = '应收明细' _description = '应收明细'
renewal_id = fields.Many2one('property.lease.renewal', string='关联续租审批') checkout_id = fields.Many2one('property.tenant.checkout', string='关联退租表', ondelete='cascade')
receivable_id = fields.Many2one('property.accounts.receivable', string='应收账单') receivable_id = fields.Many2one('property.accounts.receivable', string='应收账单')
management_type_id = fields.Many2one(related='receivable_id.management_type_id', string='类型') management_type_id = fields.Many2one(related='receivable_id.management_type_id', string='类型')
is_reconciliation = fields.Boolean(string='对账') is_reconciliation = fields.Boolean(string='对账')
@@ -534,3 +208,4 @@ class EarlyReceivablesLine(models.Model):
for line in self: for line in self:
line.original_amount = line.receivable_id.original_amount - line.receivable_id.discount_amount line.original_amount = line.receivable_id.original_amount - line.receivable_id.discount_amount
@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta, date
import calendar
from odoo import models, fields, api from odoo import models, fields, api
from odoo.exceptions import UserError from odoo.exceptions import UserError
@@ -33,7 +36,7 @@ class PropertyTenantCheckout(models.Model):
'checkout_id', 'attachment_id', string="附件") # 附件 'checkout_id', 'attachment_id', string="附件") # 附件
# ===== 关联租赁合同 ===== # ===== 关联租赁合同 =====
contract_id = fields.Many2one('property.lease.contract', string='关联租赁合同') # 从合同列表「退租」按钮发起时自动带入 contract_id = fields.Many2one('property.lease.contract', string='租赁合同') # 从合同列表「退租」按钮发起时自动带入
# ===== 合同信息(关联后自动带出,只读参考)===== # ===== 合同信息(关联后自动带出,只读参考)=====
tenant_unit = fields.Char(string='租户单元') # 如:4F东边01单元(关联合同后自动带出,可改) tenant_unit = fields.Char(string='租户单元') # 如:4F东边01单元(关联合同后自动带出,可改)
@@ -46,11 +49,12 @@ class PropertyTenantCheckout(models.Model):
rent_address = fields.Char(string='租赁地址') # 租赁地址(关联合同后自动带出,可改) rent_address = fields.Char(string='租赁地址') # 租赁地址(关联合同后自动带出,可改)
# ===== 节点一:发起申请 ===== # ===== 节点一:发起申请 =====
checkout_deadline = fields.Date(string='退租截止日期') # 退租截止日期 # 退租原因(同时兼任「合同办理方式」):驱动终止逻辑与表单显隐;early_quitting_date 为终止统一日期(退租截止日期与退租日期统一使用此字段)
contract_handle_type = fields.Selection([ early_reason = fields.Selection([
('checkout', '退租'), ('renew', '续租'), ('transfer', '转租'), ('other', '其他') ('normal', '正常退租'), ('agree', '协商一致'), ('settle', '违约结清'), ('other', '其他')
], string='合同办理方式', default='checkout') # 合同办理方式 ], string='退租原因', default='normal')
checkout_reason = fields.Text(string='退租原因') # 退租原因 early_quitting_date = fields.Date(string='退租日期') # 实际退租日期(终止逻辑统一使用此字段)
early_explain = fields.Text(string='退租说明') # 退租说明(进入终止/押金处理逻辑)
other_note = fields.Text(string='其它(补充说明)') # 其它补充说明 other_note = fields.Text(string='其它(补充说明)') # 其它补充说明
# ===== 节点二:工程组检查与确认 ===== # ===== 节点二:工程组检查与确认 =====
@@ -70,8 +74,8 @@ class PropertyTenantCheckout(models.Model):
fee_ids = fields.One2many('property.tenant.checkout.fee', 'checkout_id', string='应收费用明细') # 应收费用明细 fee_ids = fields.One2many('property.tenant.checkout.fee', 'checkout_id', string='应收费用明细') # 应收费用明细
total_fee = fields.Float(string='应收合计', compute='_compute_total_fee', store=True) # 应收费用合计(自动求和) total_fee = fields.Float(string='应收合计', compute='_compute_total_fee', store=True) # 应收费用合计(自动求和)
deposit_treatment = fields.Selection([ deposit_treatment = fields.Selection([
('full', '退还全额'), ('part', '部分退还'), ('none', '不退还') ('refund', '全额退押'), ('part_refund', '部分退租'), ('no_refund', '全额没收')
], string='保证金处理方式') # 保证金处理方式 ], string='保证金处理方式') # 保证金处理方式(与租赁变更审批·合同终止逻辑一致)
settlement_sign = fields.Binary(string='结算复核签字') # 结算复核签字(PNG) settlement_sign = fields.Binary(string='结算复核签字') # 结算复核签字(PNG)
# ===== 节点五:财务处理 ===== # ===== 节点五:财务处理 =====
@@ -80,6 +84,13 @@ class PropertyTenantCheckout(models.Model):
finance_person_in_charge = fields.Char(string='财务部负责人') # 财务部负责人 finance_person_in_charge = fields.Char(string='财务部负责人') # 财务部负责人
finance_date = fields.Date(string='财务日期') # 财务部日期 finance_date = fields.Date(string='财务日期') # 财务部日期
# ===== 合同终止相关(从租赁变更审批·合同终止迁移)=====
deposit = fields.Float(string='原押金') # 原押金(关联合同押金类应收汇总,onchange 带出)
refund_amount = fields.Float(string='退款金额') # 退款金额(部分退押时填写)
forfeit = fields.Float(string='没收金额') # 没收金额(= 原押金 - 退款金额)
total_receivables = fields.Float(string='应收金额', compute='_compute_total_receivables', store=True) # 应收金额(部分退押时汇总)
receivables_ids = fields.One2many('early.receivables.line', 'checkout_id', string='应收明细') # 截至退租日的应收账单(可作废/减免/退款)
@api.depends('fee_ids', 'fee_ids.amount') @api.depends('fee_ids', 'fee_ids.amount')
def _compute_total_fee(self): def _compute_total_fee(self):
"""应收合计 = 所有费用明细金额之和""" """应收合计 = 所有费用明细金额之和"""
@@ -96,11 +107,336 @@ class PropertyTenantCheckout(models.Model):
@api.onchange('contract_id') @api.onchange('contract_id')
def _onchange_contract_id(self): def _onchange_contract_id(self):
"""关联合同后自动带出公司名称/物业信息/租赁地址(可再手工修改);其余信息以 related 只读展示""" """关联合同后自动带出公司名称/物业信息/租赁地址(可再手工修改);其余信息以 related 只读展示。
正常退租时,自动带出合同结束日期作为退租日期。"""
if self.contract_id: if self.contract_id:
self.tenant_unit = self.contract_id.property_name # 租户单元=合同物业信息 self.tenant_unit = self.contract_id.property_name # 租户单元=合同物业信息
# 租赁地址=合同物业明细首行对应的物业地址名称(无物业明细则为空) # 租赁地址=合同物业明细首行对应的物业地址名称(无物业明细则为空)
self.rent_address = self.contract_id.lease_ids[:1].address_id.name self.rent_address = self.contract_id.lease_ids[:1].address_id.name
# 正常退租:退租日期默认取合同结束日期(可再手工修改)
if self.early_reason == 'normal' and self.contract_id.term_end_time:
self.early_quitting_date = self.contract_id.term_end_time
@api.depends('receivables_ids', 'receivables_ids.original_amount', 'receivables_ids.reduction_amount',
'receivables_ids.void')
def _compute_total_receivables(self):
"""应收金额 = 未被作废且未对账的应收明细(原金额 - 减免金额)之和"""
for rec in self:
valid_lines = rec.receivables_ids.filtered(lambda line: not line.void and not line.is_reconciliation)
rec.total_receivables = sum(line.original_amount - line.reduction_amount for line in valid_lines)
@api.onchange('refund_amount')
def _onchange_forfeit(self):
"""部分退押时:没收金额 = 原押金 - 退款金额"""
for rec in self:
rec.forfeit = rec.deposit - rec.refund_amount
@api.onchange('early_quitting_date', 'contract_id')
def _onchange_early_quitting_date(self):
"""退租日期/合同变化:拉取截至退租日的应收账单生成应收明细,并汇总原押金"""
self.receivables_ids = [(5, 0, 0)]
if not self.early_quitting_date or not self.contract_id:
self.deposit = 0
return
receivable_ids = self.env['property.accounts.receivable'].search([
('due_date', '<=', self.early_quitting_date),
('contract_id', '=', self.contract_id.id),
])
deposit_receivables = self.env['property.accounts.receivable']
normal_receivables = self.env['property.accounts.receivable']
for rec in receivable_ids:
if rec.management_type_id and rec.management_type_id.name == '押金':
deposit_receivables |= rec
else:
normal_receivables |= rec
if deposit_receivables:
self.deposit = sum(rec.original_amount for rec in deposit_receivables)
receivable_list = []
for rec in normal_receivables:
receivable_list.append([0, 0, {
'receivable_id': rec.id,
'is_reconciliation': rec.is_reconciliation,
}])
self.receivables_ids = receivable_list
def end_approval(self):
"""合同终止终审逻辑(从租赁变更审批·合同终止 early_checkout 分支迁移)。
统一以 early_quitting_date 作为终止日期;按 early_reason 区分退租方式由前端/配置决定,
此处对所有退租场景统一处理:押金处理 + 应收作废/减免 + 银行流水与对账。"""
for rec in self:
contract = rec.contract_id
if not contract:
continue
workflow_record_id = self.env["soong.workflow.record"].search(
[("res_model", "=", self._name), ("res_id", "=", self.id)])
lessor_id = contract.lessor_id
if lessor_id.referred in ['天河投资', '天投城建']:
company_id = self.env['res.company'].search([('name', '=', '集团本部')], limit=1)
elif lessor_id.referred in ['天河物管']:
company_id = self.env['res.company'].browse(3)
elif lessor_id.referred in ['天河粮储']:
company_id = self.env['res.company'].search([('name', '=', '天河粮储')], limit=1)
else:
company_id = self.env['res.company'].search([('name', '=', '集团本部')], limit=1)
# 取本审批最后一条流程日志的操作时间,作为账单 due_date
operation_log = self.env['soong.workflow.log'].search([
('model', '=', self._name),
('res_id', '=', rec.id),
], order='lase_click_date DESC, id DESC', limit=1)
operation_date = operation_log.lase_click_date.date() if operation_log and operation_log.lase_click_date else fields.Date.today()
# 非押金退款明细(除押金外的退款,原逻辑保留)
if rec.receivables_ids:
refund_lines = rec.receivables_ids.filtered(
lambda l: l.refund_amount > 0 and l.management_type_id.name != '押金')
bills_line_list = []
if refund_lines:
for refund_line in refund_lines:
bills_line_list.append({
'lessor_id': contract.lessor_id.id,
'due_date': operation_date,
'management_type_id': refund_line.management_type_id.id,
'amount': refund_line.refund_amount,
'contract_id': contract.id,
'receivables_id': refund_line.id,
'remake': rec.early_explain
})
if bills_line_list:
self.env['property.bills.list'].create(bills_line_list)
# 押金处理(根据押金处理方式分三种,先判断去重再创建)
deposit_management_type = self.env['yuthon.management.type'].search(
[('name', '=', '押金')], limit=1)
if rec.deposit_treatment == 'refund':
# 全额退押金:仅创建 bills.list,金额 = 原押金 deposit
if rec.deposit > 0:
exist_bill = self.env['property.bills.list'].search([
('contract_id', '=', contract.id),
('management_type_id', '=', deposit_management_type.id),
('amount', '=', rec.deposit),
('workflow_record_id', '=', workflow_record_id.id),
], limit=1)
if not exist_bill:
self.env['property.bills.list'].create({
'lessor_id': contract.lessor_id.id,
'due_date': operation_date,
'management_type_id': deposit_management_type.id,
'amount': rec.deposit,
'contract_id': contract.id,
'remake': rec.early_explain,
'workflow_record_id': workflow_record_id.id,
})
elif rec.deposit_treatment == 'part_refund':
# 部分退租:bills.list 金额 = refund_amount;bank.statements 和 receivable 金额 = forfeit
forfeit = rec.deposit - rec.refund_amount
if rec.refund_amount > 0:
exist_bill = self.env['property.bills.list'].search([
('contract_id', '=', contract.id),
('management_type_id', '=', deposit_management_type.id),
('amount', '=', rec.refund_amount),
('workflow_record_id', '=', workflow_record_id.id),
], limit=1)
if not exist_bill:
self.env['property.bills.list'].create({
'lessor_id': contract.lessor_id.id,
'due_date': operation_date,
'management_type_id': deposit_management_type.id,
'amount': rec.refund_amount,
'contract_id': contract.id,
'remake': rec.early_explain,
'workflow_record_id': workflow_record_id.id,
})
if forfeit > 0:
start_time = datetime.combine(rec.early_quitting_date, datetime.min.time().replace(hour=8, minute=00))
existing_bank_statement = self.env['property.bank.statements'].sudo().search([
('start_time', '=', start_time),
('original_amount', '=', forfeit),
('name_account', '=', rec.lessee_id.name),
('remake', '=', rec.early_explain),
('workflow_record_id', '=', workflow_record_id.id),
], limit=1)
if not existing_bank_statement:
bank_accounts = self.env['property.bank.line'].search([
('tenant_id', '=', rec.lessee_id.id)
]).mapped('bank')
filtered_accounts = [acc for acc in bank_accounts if acc]
bank_accounts = filtered_accounts[0] if filtered_accounts else None
contract.write({
'actual_end_time': rec.early_quitting_date
})
bank_id = self.env['property.bank.statements'].sudo().create({
'start_time': start_time,
'name': bank_accounts,
'original_amount': forfeit,
'name_account': rec.lessee_id.name,
'remake': rec.early_explain,
'lending_direction': 'loan',
'workflow_record_id': workflow_record_id.id,
'company_id': company_id.id,
'your_own_unit': lessor_id.referred,
'your_own_account': lessor_id.rent_account,
})
bank_id._compute_property_employee_id()
management_type_id = self.env['yuthon.management.type'].search([
('name', '=', '违约金')
], limit=1)
exist_receivable = self.env['property.accounts.receivable'].search([
('contract_id', '=', contract.id),
('management_type_id', '=', management_type_id.id),
('due_date', '=', rec.early_quitting_date),
('original_amount', '=', forfeit),
], limit=1)
if not exist_receivable:
self.env['property.accounts.receivable'].create({
'contract_id': contract.id,
'management_type_id': management_type_id.id,
'due_date': rec.early_quitting_date,
'original_amount': forfeit,
'remark': rec.early_explain,
})
deposit_id = self.env['property.accounts.receivable'].search([
('contract_id', '=', contract.id),
('management_type_id.name', '=', '押金'),
], limit=1)
if deposit_id:
deposit_id.write({
'workflow_record_id': workflow_record_id.id
})
elif rec.deposit_treatment == 'no_refund':
# 全额没收:bills.list、bank.statements、receivable 金额均为 deposit
if rec.deposit > 0:
exist_bill = self.env['property.bills.list'].search([
('contract_id', '=', contract.id),
('management_type_id', '=', deposit_management_type.id),
('amount', '=', rec.deposit),
('workflow_record_id', '=', workflow_record_id.id),
], limit=1)
if not exist_bill:
bill = self.env['property.bills.list'].create({
'lessor_id': contract.lessor_id.id,
'due_date': operation_date,
'management_type_id': deposit_management_type.id,
'amount': rec.deposit,
'contract_id': contract.id,
'remake': rec.early_explain,
'workflow_record_id': workflow_record_id.id,
})
else:
bill = exist_bill
start_time = datetime.combine(rec.early_quitting_date, datetime.min.time().replace(hour=8, minute=00))
existing_bank_statement = self.env['property.bank.statements'].sudo().search([
('start_time', '=', start_time),
('original_amount', '=', rec.deposit),
('name_account', '=', rec.lessee_id.name),
('remake', '=', rec.early_explain),
('workflow_record_id', '=', workflow_record_id.id),
], limit=1)
if not existing_bank_statement:
bank_accounts = self.env['property.bank.line'].search([
('tenant_id', '=', rec.lessee_id.id)
]).mapped('bank')
filtered_accounts = [acc for acc in bank_accounts if acc]
bank_accounts = filtered_accounts[0] if filtered_accounts else None
contract.write({
'actual_end_time': rec.early_quitting_date
})
bank_id = self.env['property.bank.statements'].sudo().create({
'start_time': start_time,
'name': bank_accounts,
'original_amount': rec.deposit,
'name_account': rec.lessee_id.name,
'remake': rec.early_explain,
'lending_direction': 'loan',
'workflow_record_id': workflow_record_id.id,
'company_id': company_id.id,
'your_own_unit': lessor_id.referred,
'your_own_account': lessor_id.rent_account,
})
bank_id._compute_property_employee_id()
else:
bank_id = existing_bank_statement
management_type_id = self.env['yuthon.management.type'].search([
('name', '=', '违约金')
], limit=1)
exist_receivable = self.env['property.accounts.receivable'].search([
('contract_id', '=', contract.id),
('management_type_id', '=', management_type_id.id),
('due_date', '=', rec.early_quitting_date),
('original_amount', '=', rec.deposit),
], limit=1)
if not exist_receivable:
self.env['property.accounts.receivable'].create({
'contract_id': contract.id,
'management_type_id': management_type_id.id,
'due_date': rec.early_quitting_date,
'original_amount': rec.deposit,
'remark': rec.early_explain,
})
# ===== 触发 action_pay + button_reconciliation 的真实逻辑 =====
deposit_receivable = self.env['property.accounts.receivable'].search([
('contract_id', '=', contract.id),
('management_type_id', '=', deposit_management_type.id),
], limit=1)
# 1) 支付效果(action_pay):调用支付弹窗的真实逻辑
if bill and not bill.actual_date:
bill.amount_actual = bill.amount
pay_wizard = self.env['property.bills.wizard'].create({
'bills_list_id': bill.id,
'actual_date': bill.due_date,
'receivables_id': deposit_receivable.id if deposit_receivable else False,
})
pay_wizard._do_pay()
# 2) 对账效果(button_reconciliation):bank.statements 与押金应收对账
if bank_id and bank_id.bank_state != 'reconciled':
bank_id.bank_state = 'reconciled'
bank_id.yes_amount = bank_id.original_amount
bank_id.reconciliation_time = fields.Datetime.now() - timedelta(hours=8)
if exist_receivable:
exist_receivable.write({
'has_date': bill.due_date if bill else operation_date,
'is_reconciliation': True,
'payout_date': operation_date,
'delivered1': rec.deposit,
'deposit_state': 'paid',
'reconciliation_time': fields.Datetime.now(),
'bank_id': bank_id.id,
})
self.env['receivable.modify.line'].create({
'bank_id': bank_id.id,
'not_collected': rec.deposit,
'reconciliation_date': operation_date,
'receivable_id': exist_receivable.id,
'is_write': True,
'operation_date': fields.Date.today(),
'is_penalty': True,
'remake1': rec.early_explain,
'amount': rec.deposit,
})
contract.contact_state = 'settle'
contract.compute_is_has()
# 原有作废/减免逻辑保留
for receivable_id in rec.receivables_ids:
if receivable_id.void:
receivable_id.receivable_id.write({
'is_invalid': True,
'void_date': operation_date
})
if receivable_id.reduction_amount:
receivable_id.receivable_id.discount_amount = receivable_id.reduction_amount
receivable_id.receivable_id.discount_date = operation_date
def action_print_tenant_checkout(self): def action_print_tenant_checkout(self):
"""打印租户退租表""" """打印租户退租表"""
@@ -44,8 +44,7 @@
<tr> <tr>
<td class="header-cell"><strong>变更类型</strong></td> <td class="header-cell"><strong>变更类型</strong></td>
<td> <td>
<t t-if="doc.renewal_type == 'early_checkout'">合同终止</t> <t t-if="doc.renewal_type == 'lease'">承租方变更</t>
<t t-elif="doc.renewal_type == 'lease'">承租方变更</t>
<t t-elif="doc.renewal_type == 'extend'">延长租期</t> <t t-elif="doc.renewal_type == 'extend'">延长租期</t>
</td> </td>
<td class="header-cell"><strong>紧急程度</strong></td> <td class="header-cell"><strong>紧急程度</strong></td>
@@ -69,8 +69,14 @@
<tr> <tr>
<td class="header-cell"><strong>退租截止日期</strong></td> <td class="header-cell"><strong>退租截止日期</strong></td>
<td class="text-left"><span t-esc="doc.checkout_deadline and doc.checkout_deadline.strftime('%Y-%m-%d') or ''"/></td> <td class="text-left"><span t-esc="doc.checkout_deadline and doc.checkout_deadline.strftime('%Y-%m-%d') or ''"/></td>
<td class="header-cell"><strong>合同办理方式</strong></td> <td class="header-cell"><strong>退租方式</strong></td>
<td class="text-left"><span t-esc="doc.contract_handle_type or ''"/></td> <td class="text-left">
<t t-if="doc.early_reason == 'normal'">正常退租</t>
<t t-elif="doc.early_reason == 'agree'">协商一致</t>
<t t-elif="doc.early_reason == 'settle'">违约结清</t>
<t t-elif="doc.early_reason == 'other'">其他</t>
<t t-else=""><span t-esc="doc.early_reason or ''"/></t>
</td>
</tr> </tr>
<tr> <tr>
<td class="header-cell"><strong>退租原因</strong></td> <td class="header-cell"><strong>退租原因</strong></td>
@@ -148,4 +148,5 @@ access_yuthon_property_task,yuthon_property_task,model_yuthon_property_task,base
access_yuthon_property_task_confirm,yuthon_property_task_confirm,model_yuthon_property_task_confirm,base.group_user,1,1,1,1 access_yuthon_property_task_confirm,yuthon_property_task_confirm,model_yuthon_property_task_confirm,base.group_user,1,1,1,1
access_property_tenant_checkout,property_tenant_checkout,model_property_tenant_checkout,base.group_user,1,1,1,1 access_property_tenant_checkout,property_tenant_checkout,model_property_tenant_checkout,base.group_user,1,1,1,1
access_property_tenant_checkout_inspection,property_tenant_checkout_inspection,model_property_tenant_checkout_inspection,base.group_user,1,1,1,1 access_property_tenant_checkout_inspection,property_tenant_checkout_inspection,model_property_tenant_checkout_inspection,base.group_user,1,1,1,1
access_property_tenant_checkout_fee,property_tenant_checkout_fee,model_property_tenant_checkout_fee,base.group_user,1,1,1,1 access_property_tenant_checkout_fee,property_tenant_checkout_fee,model_property_tenant_checkout_fee,base.group_user,1,1,1,1
access_property_accounts_receivable_summary,property_accounts_receivable_summary,model_property_accounts_receivable_summary,base.group_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
148
149
150
151
152
@@ -505,6 +505,55 @@
<field name="context">{'search_default_group_company_id': True, 'group_by': ['company_id', 'due_date:year', 'due_date:month'], 'orderedBy': [{'name': 'due_date', 'asc': False}]}</field> <field name="context">{'search_default_group_company_id': True, 'group_by': ['company_id', 'due_date:year', 'due_date:month'], 'orderedBy': [{'name': 'due_date', 'asc': False}]}</field>
<field name="search_view_id" ref="view_property_accounts_receivable_search"/> <field name="search_view_id" ref="view_property_accounts_receivable_search"/>
<field name="view_id" ref="yuthon_property.view_property_accounts_receivable_tree"/> <field name="view_id" ref="yuthon_property.view_property_accounts_receivable_tree"/>
</record>
<!-- 账单详情跳转专用:独立 tree + action,避免客户在「应收账单」菜单保存的默认分组影响 -->
<record id="view_property_accounts_receivable_tree_contract" model="ir.ui.view">
<field name="name">property.accounts.receivable.tree.contract</field>
<field name="model">property.accounts.receivable</field>
<field name="inherit_id" ref="yuthon_property.view_property_accounts_receivable_tree"/>
<field name="arch" type="xml">
<xpath expr="//tree" position="attributes">
<attribute name="default_order">due_date desc</attribute>
</xpath>
<xpath expr="//tree" position="inside">
<header>
<button name="all_red" string="红冲" type="object" class="btn btn-primary"/>
<button name="update_lessee_id" string="更新" type="object" class="btn btn-primary"/>
<button name="action_fix" string="修复关联" type="object" class="btn btn-primary"/>
</header>
</xpath>
<xpath expr="//button[@name='receivable_log_wizard']" position="before">
<field name="penalty_ratio_value" string="违约金比例" readonly="1" optional="show"/>
<button name="penalty_wizard" string="收款明细" type="object" class="btn btn-blue" invisible="is_penalty"/>
<button name="button_accounts_wizard" string="操作" type="object" class="btn btn-blue"/>
</xpath>
<xpath expr="//field[@name='remark']" position="after">
<field name="charging_range"/>
<field name="is_penalty" column_invisible="1"/>
<field name="property_name" width="350px"/>
<field name="property_ids" options="{'no_create': True}" widget="many2many_tags" column_invisible="1"/>
<field name="lessee_id" options="{'no_create': True}" readonly="1"/>
<field name="title_deed" readonly="1"/>
<field name="company_id" column_invisible="1"/>
<field name="last_month_table" column_invisible="1"/>
<field name="this_month_table" column_invisible="1"/>
<field name="ratio" column_invisible="1"/>
<field name="billable_quantity" column_invisible="1"/>
<field name="contract_id" string="合同编号" context="{'form_view_ref': 'yuthon_property_approval.view_property_lease_contract_form1'}"/>
</xpath>
<xpath expr="//field[@name='receivable_log']" position="after">
<field name="penalty"/>
<field name="penalty_date"/>
</xpath>
</field>
</record>
<record id="property_accounts_receivable_contract_action" model="ir.actions.act_window">
<field name="name">账单详情</field>
<field name="res_model">property.accounts.receivable</field>
<field name="view_mode">tree</field>
<field name="context">{'search_default_group_by_management_type_id': 1, 'group_by': 'management_type_id', 'orderedBy': [{'name': 'due_date', 'asc': False}]}</field>
<field name="search_view_id" ref="view_property_accounts_receivable_search"/>
<field name="view_id" ref="view_property_accounts_receivable_tree_contract"/>
</record> </record>
<record id="property_accounts_penalty_action" model="ir.actions.act_window"> <record id="property_accounts_penalty_action" model="ir.actions.act_window">
<field name="name">违约金列表</field> <field name="name">违约金列表</field>
@@ -432,7 +432,6 @@
string="生成租金" class="btn-primary"/> string="生成租金" class="btn-primary"/>
<button name="button_lease_supplement" type="object" class="btn-primary" string="补充协议"/> <button name="button_lease_supplement" type="object" class="btn-primary" string="补充协议"/>
<button name="button_lease_change" type="object" class="btn-primary" string="变更"/> <button name="button_lease_change" type="object" class="btn-primary" string="变更"/>
<button name="refund_deposit" type="object" class="btn-primary" string="退押金"/>
<button name="action_copy_record1" type="object" string="拆分" class="btn-primary"/> <button name="action_copy_record1" type="object" string="拆分" class="btn-primary"/>
<button name="print_payment_notice" type="object" class="btn-primary" string="催缴单打印"/> <button name="print_payment_notice" type="object" class="btn-primary" string="催缴单打印"/>
<button name="unlink" string="删除" type="object" icon="fa-trash" groups="base.group_system" <button name="unlink" string="删除" type="object" icon="fa-trash" groups="base.group_system"
@@ -457,9 +456,7 @@
decoration-warning="deposit_status == 'no_paid'"/> decoration-warning="deposit_status == 'no_paid'"/>
<field name="not_tax_amount"/> <field name="not_tax_amount"/>
<field name="rent"/> <field name="rent"/>
<field name="not_received" decoration-muted="comparison == 'equal'" <field name="not_received" decoration-danger="not_received > 0"/>
decoration-danger="comparison == 'outweigh'"
decoration-success="comparison == 'less'"/>
<field name="comparison" column_invisible="1"/> <field name="comparison" column_invisible="1"/>
<field name="percentage_damages"/> <field name="percentage_damages"/>
<field name="total_receivable"/> <field name="total_receivable"/>
@@ -1143,7 +1140,7 @@
<field name="res_model">property.lease.contract</field> <field name="res_model">property.lease.contract</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="domain">[('admin_id', '=', uid)]</field> <field name="domain">[('admin_id', '=', uid)]</field>
<field name="context">{'search_default_group_by_admin_id': True}</field> <field name="context">{}</field>
<field name="view_ids" eval="[(5, 0, 0), <field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_property_approval.view_property_lease_contract_tree1')}), (0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_property_approval.view_property_lease_contract_tree1')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_property_approval.view_property_lease_contract_form1')})]"/> (0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_property_approval.view_property_lease_contract_form1')})]"/>
@@ -73,7 +73,6 @@
</group> </group>
<group string="变更信息" col="3"> <group string="变更信息" col="3">
<group> <group>
<field name="early_quitting_date" invisible="renewal_type != 'early_checkout'"/>
<field name="new_lessee_id" invisible="renewal_type != 'lease'"/> <field name="new_lessee_id" invisible="renewal_type != 'lease'"/>
<field name="fee" invisible="renewal_type != 'lease'"/> <field name="fee" invisible="renewal_type != 'lease'"/>
<field name="actual_end_date" invisible="renewal_type != 'extend'"/> <field name="actual_end_date" invisible="renewal_type != 'extend'"/>
@@ -91,56 +90,12 @@
<group> <group>
<!--<field name="reason"/>--> <!--<field name="reason"/>-->
<field name="effective_date" invisible="renewal_type != 'lease'"/> <field name="effective_date" invisible="renewal_type != 'lease'"/>
<field name="early_reason" invisible="renewal_type != 'early_checkout'"/>
<field name="deposit_treatment" invisible="renewal_type != 'early_checkout'"/>
<field name="deposit" invisible="renewal_type != 'early_checkout'"/>
</group>
</group>
<group col="4">
<group>
<field name="refund_amount" invisible="deposit_treatment != 'part_refund'"/>
</group>
<group/>
<group/>
</group>
<group col="4">
<group>
<field name="total_receivables" invisible="deposit_treatment != 'part_refund'"/>
</group>
<group/>
<group/>
</group>
<group col="4">
<group>
<field name="forfeit" invisible="deposit_treatment != 'part_refund'"/>
</group>
<group/>
<group/>
</group>
<group>
<group>
<field name="early_explain" invisible="renewal_type != 'early_checkout'"/>
</group> </group>
</group> </group>
<group> <group>
<field name="document_ids" widget="preview_many2many"/> <field name="document_ids" widget="preview_many2many"/>
</group> </group>
<notebook> <notebook>
<page string="应收明细" invisible="renewal_type != 'early_checkout'">
<field name="receivables_ids" string=" ">
<tree editable="bottom">
<field name="renewal_id" column_invisible="1"/>
<field name="receivable_id" column_invisible="1"/>
<field name="management_type_id"/>
<field name="due_date"/>
<field name="original_amount"/>
<field name="reduction_amount"/>
<field name="refund_amount"/>
<field name="is_reconciliation"/>
<field name="void"/>
</tree>
</field>
</page>
<page string="租金明细" invisible="renewal_type != 'extend' or ((state in ['分管领导', '总经理']) and (not work_end))"> <page string="租金明细" invisible="renewal_type != 'extend' or ((state in ['分管领导', '总经理']) and (not work_end))">
<field name="breakdown_ids" string=" "> <field name="breakdown_ids" string=" ">
<tree editable="bottom"> <tree editable="bottom">
@@ -62,7 +62,7 @@
<field name="open_rent_id" column_invisible="1"/> <field name="open_rent_id" column_invisible="1"/>
<field name="address_id"/> <field name="address_id"/>
<field name="admin_line_id" options="{'no_open': True}"/> <field name="admin_line_id" options="{'no_open': True}"/>
<field name="property_area"/> <field name="property_area" digits="[16, 4]"/>
<field name="evaluate_unit_price"/> <field name="evaluate_unit_price"/>
<field name="message_standard_id"/> <field name="message_standard_id"/>
<button name="open_record" string="详情" type="object" class="btn-primary"/> <button name="open_record" string="详情" type="object" class="btn-primary"/>
@@ -11,7 +11,7 @@
<field name="contract_id"/> <field name="contract_id"/>
<field name="tenant_unit"/> <field name="tenant_unit"/>
<field name="state"/> <field name="state"/>
<field name="checkout_deadline"/> <field name="early_quitting_date"/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -53,43 +53,61 @@
</group> </group>
</group> </group>
<!-- 已关联合同 · 关键信息自动带出(各节点均可见,只读参考) --> <!-- 已关联合同 · 关键信息自动带出(仅关联合同后显示,直接展开,全部只读) -->
<group string="已关联合同 · 关键信息自动带出" col="3"> <group string="已关联合同 · 关键信息自动带出" col="3" invisible="not contract_id">
<group> <group>
<field name="lessee_id"/> <field name="lessee_id" readonly="1"/>
<field name="tenant_unit"/> <field name="tenant_unit" readonly="1"/>
<field name="rent_address"/> <field name="rent_address" readonly="1"/>
</group> </group>
<group> <group>
<field name="lessor_id"/> <field name="lessor_id" readonly="1"/>
<field name="contract_code"/> <field name="contract_term_start" readonly="1"/>
<field name="contract_term_start"/>
</group> </group>
<group> <group>
<field name="contract_term_end"/> <field name="contract_term_end" readonly="1"/>
<field name="deposit_amount"/> <field name="deposit_amount" readonly="1"/>
</group> </group>
</group> </group>
<!-- ===== 节点一:申请人(state 为空或走到「申请人」节点时显示)===== --> <!-- ===== 节点一:申请人(state 为空或走到「申请人」节点时显示)===== -->
<group string="发起退租申请" invisible="state and state != '申请人'" col="2"> <group string="发起退租申请" invisible="state and state != '申请人'" col="1">
<group> <group>
<!-- 申请人:默认自动带出当前登录用户,可手选他人代提交;流程监控的「创建人」取系统 create_uid,与此字段无关 --> <!-- 申请人:默认自动带出当前登录用户,可手选他人代提交;流程监控的「创建人」取系统 create_uid,与此字段无关 -->
<field name="user_id" string="申请人" required="1"/> <field name="user_id" string="申请人" required="1"/>
<field name="contract_id" options="{'no_create': True}" required="1"/> <field name="contract_id" options="{'no_create': True}" required="1"/>
<field name="checkout_deadline" required="1"/> <!-- 退租原因(兼合同办理方式):驱动终止逻辑与表单显隐 -->
</group> <field name="early_reason" required="1" widget="radio"/>
<group> <!-- 退租日期:退租截止日期与退租日期统一使用此字段,所有退租原因均显示 -->
<field name="contract_handle_type" required="1"/> <field name="early_quitting_date" required="1"/>
</group> <!-- 退租说明:进入终止/押金处理逻辑 -->
<group> <field name="early_explain" widget="text"/>
<field name="checkout_reason" widget="text"/> <!-- 其��(补充说明) -->
</group>
<group>
<field name="other_note" widget="text"/> <field name="other_note" widget="text"/>
</group> </group>
</group> </group>
<!-- 应收明细(仅「协商一致」且已填退租日期时显示) -->
<div invisible="early_reason != 'agree' or not early_quitting_date">
<notebook>
<page string="应收明细">
<field name="receivables_ids" string=" ">
<tree editable="bottom">
<field name="checkout_id" column_invisible="1"/>
<field name="receivable_id" column_invisible="1"/>
<field name="management_type_id"/>
<field name="due_date"/>
<field name="original_amount"/>
<field name="reduction_amount"/>
<field name="refund_amount"/>
<field name="is_reconciliation"/>
<field name="void"/>
</tree>
</field>
</page>
</notebook>
</div>
<!-- ===== 节点二:工程组(工程组检查 + 租户签字,同一节点)===== --> <!-- ===== 节点二:工程组(工程组检查 + 租户签字,同一节点)===== -->
<group string="工程组检查与确认" invisible="state not in ['工程组']" col="1"> <group string="工程组检查与确认" invisible="state not in ['工程组']" col="1">
<group> <group>
@@ -140,6 +158,11 @@
</field> </field>
<field name="total_fee" readonly="1"/> <field name="total_fee" readonly="1"/>
<field name="deposit_treatment"/> <field name="deposit_treatment"/>
<field name="deposit" readonly="1"/>
<field name="refund_amount" invisible="deposit_treatment != 'part_refund'"/>
<field name="forfeit" readonly="1" invisible="deposit_treatment != 'part_refund'"/>
<field name="total_receivables" readonly="1" invisible="deposit_treatment != 'part_refund'"/>
<field name="early_explain" widget="text" readonly="1"/>
</group> </group>
<group col="2"> <group col="2">
<group> <group>
+1 -1
View File
@@ -21,7 +21,7 @@
sequence="1" sequence="1"
name="应收账单" name="应收账单"
parent="yuthon_property_approval.property_approval_report_menu" parent="yuthon_property_approval.property_approval_report_menu"
action="yuthon_property_approval.property_accounts_receivable_action"/> action="yuthon_property_approval.action_receivable_unpaid_server"/>
<!-- 经营收入 --> <!-- 经营收入 -->
<menuitem id="yuthon_property_income_menu" <menuitem id="yuthon_property_income_menu"
+6 -6
View File
@@ -65,11 +65,11 @@
parent="hr_menu_recruitment_process" parent="hr_menu_recruitment_process"
action="yuthon_hr_employee_approval.yuthon_recruitment_approval_action" action="yuthon_hr_employee_approval.yuthon_recruitment_approval_action"
sequence="1"/> sequence="1"/>
<menuitem id="hr_menu_intern_increment_root_board" <!-- <menuitem id="hr_menu_intern_increment_root_board"-->
name="实习生增员申请表" <!-- name="实习生增员申请表"-->
parent="hr_menu_recruitment_process" <!-- parent="hr_menu_recruitment_process"-->
action="yuthon_hr_employee_approval.yuthon_intern_increment_action" <!-- action="yuthon_hr_employee_approval.yuthon_intern_increment_action"-->
sequence="3"/> <!-- sequence="3"/>-->
<menuitem id="hr_menu_interview_approval_root_board" <menuitem id="hr_menu_interview_approval_root_board"
name="面试信息表" name="面试信息表"
parent="hr_menu_recruitment_process" parent="hr_menu_recruitment_process"
@@ -931,7 +931,7 @@
sequence="1" sequence="1"
name="应收账单" name="应收账单"
parent="yuthon_financial_management_menu" parent="yuthon_financial_management_menu"
action="yuthon_property_approval.property_accounts_receivable_action"/> action="yuthon_property_approval.action_receivable_unpaid_server"/>
<!-- <menuitem--> <!-- <menuitem-->
<!-- id="contract_accounts_receivable_menu"--> <!-- id="contract_accounts_receivable_menu"-->
<!-- parent="yuthon_financial_management_menu"--> <!-- parent="yuthon_financial_management_menu"-->