# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import models, fields, api, exceptions, _ from datetime import datetime, timedelta, date from dateutil.relativedelta import relativedelta from odoo.exceptions import UserError import calendar class PropertyLeaseRenewal(models.Model): _name = 'property.lease.renewal' _description = '租赁变更审批' _inherit = ['mail.thread.main.attachment', 'mail.activity.mixin', 'soong.workflow.mixin'] _rec_name = 'name' name = fields.Char(string='标题') user_id = fields.Many2one('res.users', string="申请人", default=lambda self: self.env.user) department_id = fields.Many2one(related='user_id.department_id', string="部门") company_id = fields.Many2one(related='user_id.company_id', string="公司") code = fields.Char(string="编号", default=lambda self: self.env['ir.sequence'].next_by_code('property_lease_renewal_code')) renewal_type = fields.Selection([('lease', '承租方变更'), ('extend', '延长租期')], string='变更类型') urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度', default='normal') is_chapter = fields.Selection([('yes', '是'), ('no', '否')], string="是否用章") type_stamp_ids = fields.Many2many('seals.type.setting', string='用章类型', domain="['&', ('start_date', '<=', context_today()), '|', ('end_date', '=', False), ('end_date', '>=', context_today())]") def _get_default_name_title(self): return self._description if self._description else self._name name_title = fields.Char(default=_get_default_name_title) self_users_ids = fields.Many2many('res.users', string="当前经办人", default=lambda self: [(6, 0, [self.env.user.id])]) # 原合同信息 lessee_id = fields.Many2one(related="contract_id.lessee_id", string='原承租方') hide = fields.Boolean(string="隐藏") display_name = fields.Char(string='原承租方') contract_id = fields.Many2one('property.lease.contract', string='租赁合同') monthly_rent = fields.Float(related="contract_id.rent", string='租金') cycle_dict = fields.Selection(related="contract_id.cycle_dict", string='租金支付周期') original_start_date = fields.Date(related="contract_id.term_start_time", string='原租赁期开始时间') original_end_date = fields.Date(related="contract_id.term_end_time", string='原租赁期结束时间') property_name = fields.Char(related="contract_id.property_name", string='物业信息') # 变更信息 new_lessee_id = fields.Many2one("property.tenant.information", string="承租方", store=True) contact = fields.Char(string='联系人') phone = fields.Char(string='联系电话') card = fields.Char(string='身份证号') effective_date = fields.Date(string='生效日期', required=True) entity_type = fields.Selection([('person', '个人'), ('company', '企业')], string='承租方类型') reason = fields.Text(string='变更原因') fee = fields.Float(string='变更费用') document_ids = fields.Many2many('ir.attachment', string="变更附件") active = fields.Boolean('Active', default=True, tracking=True) work_end = fields.Boolean(string="结束") # 延长租期 actual_end_date = fields.Date(string='实际结束日期') breakdown_ids = fields.One2many('property.rent.breakdown.line', 'renewal_id', string='租金明细') def _get_tenant_by_contact(self, contact): """按联系人或姓名获取承租方信息""" if not contact: return False return self.env['property.tenant.information'].search([ '|', ('contact', '=', contact), ('name', '=', contact) ], limit=1) def _get_employee_by_contact(self, contact): """按联系人姓名匹配员工,拿身份证号等信息""" if not contact: return False return self.env['hr.employee'].search([('name', '=', contact)], limit=1) @api.onchange('new_lessee_id') def _onchange_new_lessee_id_fill_contact(self): """选择新承租方时,不再自动带出联系人/电话/身份证号,由联系人字段驱动。""" # 需求调整:不做自动填充,保留用户手动输入的联系人 return @api.onchange('contact') def _onchange_contact_fill_info(self): """填写联系人后,按员工信息带出身份证号与联系电话;不再从承租方带出。""" for rec in self: if not rec.contact: continue employee = rec._get_employee_by_contact(rec.contact) if employee: rec.phone = employee.mobile_phone or False rec.card = employee.identification_id or False else: # 未找到员工时,清空以避免沿用旧值,由用户手填 rec.phone = False rec.card = False def action_print_lease_renewal(self): """打印租赁变更审批""" self.ensure_one() report = self.env.ref('yuthon_property_approval.action_property_lease_renewal_report') return report.report_action(self) def end_approval(self): for rec in self: if rec.renewal_type == 'extend': # 延长租期:修改原合同实际结束日期,并将租金明细写入原合同 if rec.actual_end_date: rec.contract_id.write({ 'actual_end_time': rec.actual_end_date }) if rec.breakdown_ids: for line in rec.breakdown_ids: self.env['property.rent.breakdown.line'].create({ 'breakdown_id': rec.contract_id.id, 'unit_price': line.unit_price, 'monthly_amount': line.monthly_amount, 'start_data': line.start_data, 'end_data': line.end_data, 'month_number': line.month_number, 'period_rent': line.period_rent, }) elif rec.renewal_type == 'lease': origin_contract = rec.contract_id contract_actual_end = origin_contract.actual_end_time if origin_contract else None effective_date = fields.Date.to_date(rec.effective_date) last_day = calendar.monthrange(effective_date.year, effective_date.month)[1] month_end_date = date(effective_date.year, effective_date.month, last_day) contract_actual_end_date = fields.Date.to_date(contract_actual_end) if contract_actual_end else None if not contract_actual_end_date or contract_actual_end_date >= month_end_date: rec.hide = True origin_contract = rec.contract_id original_lessee_id = origin_contract.lessee_id.id if origin_contract.lessee_id else False version_str = origin_contract.version.lstrip('V') main_version = version_str.split('.')[0] new_main_version = int(main_version) + 1 new_version = f"V{new_main_version}.0" # 删除原承租方的物业合同信息 contract_to_unlink = origin_contract.lessee_id.property_contract_ids.filtered( lambda line: line.lease_contract_id.id == rec.contract_id.id ) if contract_to_unlink: contract_to_unlink.unlink() # 新增新承租方的物业合同信息 property_contract_list = [] origin_contract.write({ 'version': new_version, 'lessee_id': rec.new_lessee_id.id }) origin_contract.is_lessee = True self.env['lessee.alteration.line'].create({ 'contract_id': origin_contract.id, 'lessee_id': original_lessee_id, 'start_date': origin_contract.actual_start_time, 'end_date': rec.effective_date - relativedelta(days=1), }) origin_contract._compute_contract_count() property_contract_list.append([0, 0, { 'tenant_id': rec.new_lessee_id.id, 'lease_contract_id': rec.contract_id.id, 'rent': rec.contract_id.rent, }]) rec.new_lessee_id.write({'property_contract_ids': property_contract_list}) # 新增一条更名应收账单 management_type_id = rec.env["yuthon.management.type"].search([('name', '=', '更名费')]) if not management_type_id: management_type_id = rec.env["yuthon.management.type"].create({'name': '更名费'}) if rec.fee > 0: self.env['property.accounts.receivable'].create({ 'contract_id': origin_contract.id, 'original_amount': rec.fee, 'management_type_id': management_type_id.id, 'due_date': rec.effective_date, }) @api.onchange('contract_id', 'renewal_type') def _onchange_name(self): if self.contract_id and self.renewal_type: renewal_type_text = dict(self._fields['renewal_type'].selection).get(self.renewal_type, '') self.name = f"{self.contract_id.name} + {renewal_type_text}" elif self.contract_id: self.name = self.contract_id.property_name else: self.name = False class EarlyReceivablesLine(models.Model): _name = 'early.receivables.line' _description = '应收明细' checkout_id = fields.Many2one('property.tenant.checkout', string='关联退租表', ondelete='cascade') receivable_id = fields.Many2one('property.accounts.receivable', string='应收账单') management_type_id = fields.Many2one(related='receivable_id.management_type_id', string='类型') is_reconciliation = fields.Boolean(string='对账') due_date = fields.Date(related='receivable_id.due_date', string='应收日期') original_amount = fields.Float(string='金额', compute='_compute_original_amount') reduction_amount = fields.Float(string='减免金额') refund_amount = fields.Float(string='退款金额') void = fields.Boolean(string='作废', default=False) @api.depends('receivable_id') def _compute_original_amount(self): for line in self: line.original_amount = line.receivable_id.original_amount - line.receivable_id.discount_amount