diff --git a/yuthon_property/models/property_accounts_receivable.py b/yuthon_property/models/property_accounts_receivable.py index 38625f0bb..b1040c87b 100644 --- a/yuthon_property/models/property_accounts_receivable.py +++ b/yuthon_property/models/property_accounts_receivable.py @@ -233,6 +233,16 @@ class PropertyAccountsReceivable(models.Model): next_year = due_year if due_month < 12 else due_year + 1 penalty_start_date = date(next_year, next_month, 1) + contract = record.contract_id + renewal = self.env['property.lease.renewal'].search([ + ('contract_id', '=', contract.id), + ('renewal_type', '=', 'early_checkout'), + ('work_end', '=', True), + ], limit=1) + + if renewal: + penalty_start_date = renewal.early_quitting_date + # 1. 获取正常支付明细 modify_lines = self.env['receivable.modify.line'].search([ ('receivable_id', '=', record.id), @@ -258,18 +268,11 @@ class PropertyAccountsReceivable(models.Model): penalty_pay_count = len(penalty_lines.filtered(lambda l: l.is_penalty)) deposit_penalty_count = 0 - contract = record.contract_id - if contract: - renewal = self.env['property.lease.renewal'].search([ - ('contract_id', '=', contract.id), - ('renewal_type', '=', 'early_checkout'), - ('work_end', '=', True), - ], limit=1) - if renewal: - if not renewal.is_full_refund: - penalty_amount = renewal.deposit - renewal.refund_amount - renewal.total_receivables - if penalty_amount > 0: - deposit_penalty_count = 1 + if renewal: + if renewal.deposit_treatment != 'refund': + penalty_amount = renewal.deposit - renewal.refund_amount - renewal.total_receivables + if penalty_amount > 0: + deposit_penalty_count = 1 if record.management_type_id.name == '押金': record.number_of_payments = rent_pay_count + deposit_penalty_count diff --git a/yuthon_property_approval/models/property_lease_renewal.py b/yuthon_property_approval/models/property_lease_renewal.py index ff1a11e13..84f9b70b4 100644 --- a/yuthon_property_approval/models/property_lease_renewal.py +++ b/yuthon_property_approval/models/property_lease_renewal.py @@ -61,7 +61,6 @@ class PropertyLeaseRenewal(models.Model): work_end = fields.Boolean(string="结束") deposit = fields.Float(string="原押金") - is_full_refund = fields.Boolean(string="是否全额退押", default=True) 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") @@ -349,7 +348,7 @@ class PropertyLeaseRenewal(models.Model): ('workflow_record_id', '=', workflow_record_id.id), ], limit=1) if not exist_bill: - self.env['property.bills.list'].create({ + 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, @@ -358,6 +357,8 @@ class PropertyLeaseRenewal(models.Model): '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([ @@ -388,6 +389,8 @@ class PropertyLeaseRenewal(models.Model): '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', '=', '违约金') @@ -407,6 +410,57 @@ class PropertyLeaseRenewal(models.Model): '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: diff --git a/yuthon_property_approval/wizard/penalty_line_wizard.py b/yuthon_property_approval/wizard/penalty_line_wizard.py index 2dc23d3d7..385e560da 100644 --- a/yuthon_property_approval/wizard/penalty_line_wizard.py +++ b/yuthon_property_approval/wizard/penalty_line_wizard.py @@ -173,7 +173,7 @@ class PenaltyLineWizard(models.Model): ], limit=1) if renewal: # 2. 判断条件:未全额退款 - if not renewal.is_full_refund: + if renewal.deposit_treatment != 'refund': # 3. 计算没收押金金额 penalty_amount = renewal.deposit - renewal.refund_amount - renewal.total_receivables if penalty_amount > 0: diff --git a/yuthon_property_approval/wizard/property_bills_wizard.py b/yuthon_property_approval/wizard/property_bills_wizard.py index 37031d2b2..0d3d7d96f 100644 --- a/yuthon_property_approval/wizard/property_bills_wizard.py +++ b/yuthon_property_approval/wizard/property_bills_wizard.py @@ -20,19 +20,27 @@ class PropertyBillsWizard(models.TransientModel): if any(not receivable.is_reconciliation for receivable in receivable_ids): raise UserError("该合同下存在未对账的应收单,无法退金额") + self._do_pay() + + def _do_pay(self): + """实际支付业务效果:实付日期=所选日期、标记账单已付、回写对应应收单(实付日期/支付状态)、结清合同。 + 从 confirm 抽出,供正常支付弹窗与程序化支付(如退租全额没收)共用,确保支付逻辑只有一份。 + 注意:全额没收等程序化场景直接调用本方法,会跳过 confirm 的“未对账应收单”校验。""" if self.receivables_id: self.receivables_id.deposit_state = 'yes' if self.actual_date: self.bills_list_id.actual_date = self.actual_date self.bills_list_id.pay_state = 'paid' - receivable = self.env['property.accounts.receivable'].search([('contract_id', '=', self.bills_list_id.contract_id.id), - ('management_type_id', '=', self.bills_list_id.management_type_id.id) - ]) + receivable = self.env['property.accounts.receivable'].search([ + ('contract_id', '=', self.bills_list_id.contract_id.id), + ('management_type_id', '=', self.bills_list_id.management_type_id.id), + ], limit=1) if receivable: - receivable.write({'has_date': self.actual_date, - 'deposit_state': 'yes' - }) + receivable.write({ + 'has_date': self.actual_date, + 'deposit_state': 'yes', + }) self.bills_list_id.contract_id.contact_state = 'settle' self.bills_list_id.contract_id.compute_is_has()