diff --git a/yuthon_property/models/property_bank_statements.py b/yuthon_property/models/property_bank_statements.py
index 5ac230d57..0b29dfc2a 100644
--- a/yuthon_property/models/property_bank_statements.py
+++ b/yuthon_property/models/property_bank_statements.py
@@ -205,6 +205,7 @@ class PropertyBankStatements(models.Model):
if accounts:
rec.reconciliation_time = accounts[0].reconciliation_time
+
class ReceivableModifyLine(models.Model):
_name = 'receivable.modify.line'
_description = '修改应收账单明细'
@@ -212,7 +213,8 @@ class ReceivableModifyLine(models.Model):
bank_id = fields.Many2one('property.bank.statements', string='关联银行对账单')
receivable_id = fields.Many2one('property.accounts.receivable', string='应收账单')
is_write = fields.Boolean()
- is_penalty = fields.Boolean()
+ reconciliation_date = fields.Date(string='对账日期')
+ is_penalty = fields.Boolean(string='是否为违约金')
amount = fields.Float(string='修改金额')
diff --git a/yuthon_property/views/property_bank_statements_views.xml b/yuthon_property/views/property_bank_statements_views.xml
index 5b087a397..4da9ac61c 100644
--- a/yuthon_property/views/property_bank_statements_views.xml
+++ b/yuthon_property/views/property_bank_statements_views.xml
@@ -4,7 +4,9 @@
property.bank.statements.tree
property.bank.statements
-
+
@@ -21,7 +23,9 @@
property.bank.statements.tree
property.bank.statements
-
+
@@ -54,6 +58,8 @@
+
+
@@ -88,7 +94,7 @@
银行流水
property.bank.statements
- tree,search
+ tree,form,search
[('is_company', '=', True)]
{'group_by': 'bank_state'}
-
-
+
diff --git a/yuthon_property_approval/wizard/penalty_line_wizard.py b/yuthon_property_approval/wizard/penalty_line_wizard.py
index fb1ffabd7..3d47a5a4f 100644
--- a/yuthon_property_approval/wizard/penalty_line_wizard.py
+++ b/yuthon_property_approval/wizard/penalty_line_wizard.py
@@ -2,10 +2,11 @@ from email.policy import default
from odoo import models, fields, api, _, SUPERUSER_ID
from odoo.exceptions import ValidationError, UserError
+import calendar
import xlrd
import base64
import datetime
-from datetime import datetime
+from datetime import datetime, timedelta, date
import pandas as pd
@@ -14,6 +15,7 @@ class PenaltyLineWizard(models.Model):
_description = '合同违约金明细'
rent_collected = fields.Float(string='未收租金')
+ receivable_id = fields.Many2one('property.accounts.receivable', string='应收账单')
days_overdue = fields.Integer(string='逾期天数')
total_penalty = fields.Float(string='违约金总额')
collected_penalty = fields.Float(string='已收违约金')
@@ -25,6 +27,114 @@ class PenaltyLineWizard(models.Model):
penalty_line_ids = fields.One2many('penalty.compute.line.wizard', 'penalty_id', string='违约金明细')
pay_line_ids = fields.One2many('penalty.pay.line', 'penalty_id', string='违约金支付明细')
+ @api.onchange('receivable_id')
+ def _onchange_penalty_line_ids(self):
+ receivable = self.receivable_id
+ due_date = receivable.due_date
+ if not due_date:
+ return
+
+ rent_pay_count = 0 # 租金支付次数
+ penalty_pay_count = 0 # 违约金支付次数
+
+ # 2. 计算关键日期:应收月、应收月最后一天、次月1号(违约金起算日)
+ due_year = due_date.year
+ due_month = due_date.month
+ # 应收月最后一天(如2025-10月最后一天是2025-10-31)
+ last_day_of_due_month = date(due_year, due_month, calendar.monthrange(due_year, due_month)[1])
+ # 次月1号(违约金开始计算的日期)
+ next_month = due_month + 1 if due_month < 12 else 1
+ next_year = due_year if due_month < 12 else due_year + 1
+ penalty_start_date = date(next_year, next_month, 1)
+
+ modify_lines = self.env['receivable.modify.line'].search([
+ ('receivable_id', '=', receivable.id),
+ ('reconciliation_date', '!=', False),
+ ('is_penalty', '=', False)
+ ], order='reconciliation_date asc')
+
+ initial_line = self.penalty_line_ids[0]
+ prev_date = initial_line.date # 初始日期
+ prev_principal = initial_line.remaining_principal # 初始剩余本金
+ new_penalty_lines = []
+
+ new_pay_lines = []
+ rent_type = self.env['yuthon.management.type'].search([('name', '=', '租金')], limit=1)
+ penalty_type = self.env['yuthon.management.type'].search([('name', '=', '违约金')], limit=1)
+
+ for pay_line in modify_lines:
+ pay_amount = pay_line.amount # 付款金额
+ pay_date = pay_line.reconciliation_date # 付款日期
+ is_penalty = pay_line.is_penalty # 是否是违约金
+
+ if is_penalty:
+ penalty_pay_count += 1
+ current_idx = penalty_pay_count
+ type_name = "违约金"
+ else:
+ rent_pay_count += 1
+ current_idx = rent_pay_count
+ type_name = "租金"
+
+ if prev_date <= last_day_of_due_month:
+ days_overdue = 0
+ current_penalty = 0.0
+ else:
+ days_diff = (pay_date - penalty_start_date).days - 1
+ days_overdue = max(0, days_diff)
+ current_penalty = prev_principal * days_overdue * 0.001 if days_overdue > 0 else 0.0 # 违约金
+
+ # 计算剩余本金
+ remaining_principal = max(0, prev_principal - pay_amount)
+
+ new_penalty_lines.append((0, 0, {
+ 'date': pay_date,
+ 'days_overdue': days_overdue,
+ 'remaining_principal': remaining_principal,
+ 'penalty': round(current_penalty, 2),
+ }))
+
+ prev_date = pay_date
+ prev_principal = remaining_principal
+
+ management_type = penalty_type if is_penalty else rent_type
+ pay_remake = f'第{current_idx}次支付{type_name},金额:¥{pay_amount}'
+ new_pay_lines.append((0, 0, {
+ 'date': pay_date,
+ 'amount': pay_amount,
+ 'management_type_id': management_type.id if management_type else False,
+ 'remake': pay_remake,
+ }))
+
+ if new_penalty_lines:
+ last_line_dict = new_penalty_lines[-1][2]
+ last_line_principal = last_line_dict.get('remaining_principal', 0)
+ else:
+ last_line_principal = 0
+
+ if last_line_principal > 0:
+ current_date = date.today()
+ last_pay_date = last_line_dict.get('date')
+
+ # 计算逾期天数:当前日期 - 最后一次支付日期 - 1(避免计算当天)
+ if last_pay_date and current_date > last_pay_date:
+ days_overdue = (current_date - last_pay_date).days - 1
+ days_overdue = max(0, days_overdue) # 确保非负
+ current_penalty = last_line_principal * days_overdue * 0.001
+
+ # 生成补充明细行
+ new_penalty_lines.append((0, 0, {
+ 'date': current_date,
+ 'days_overdue': days_overdue,
+ 'remaining_principal': last_line_principal,
+ 'remake': '当前未支付金额违约金',
+ 'penalty': round(current_penalty, 2),
+ }))
+
+
+ self.penalty_line_ids = new_penalty_lines
+ self.pay_line_ids = new_pay_lines
+
class PenaltyComputeLineWizard(models.Model):
_name = 'penalty.compute.line.wizard'
@@ -36,7 +146,7 @@ class PenaltyComputeLineWizard(models.Model):
remake = fields.Text(string='说明')
days_overdue = fields.Integer(string='逾期天数')
remaining_principal = fields.Float(string='剩余本金')
- calculation_formula = fields.Text(string='计算公式')
+ penalty = fields.Float(string='违约金')
class PenaltyPayLine(models.Model):
diff --git a/yuthon_property_approval/wizard/penalty_line_wizard_viwes.xml b/yuthon_property_approval/wizard/penalty_line_wizard_viwes.xml
index 07f329af8..e139a5d33 100644
--- a/yuthon_property_approval/wizard/penalty_line_wizard_viwes.xml
+++ b/yuthon_property_approval/wizard/penalty_line_wizard_viwes.xml
@@ -8,6 +8,7 @@
+
@@ -33,7 +34,7 @@
-
+
diff --git a/yuthon_property_approval/wizard/property_reconciliation_wizard.py b/yuthon_property_approval/wizard/property_reconciliation_wizard.py
index 39f10f685..81c345d86 100644
--- a/yuthon_property_approval/wizard/property_reconciliation_wizard.py
+++ b/yuthon_property_approval/wizard/property_reconciliation_wizard.py
@@ -28,7 +28,6 @@ class PropertyReconciliationWizard(models.Model):
@api.onchange('reconciliation_line_ids', 'actual_total')
def _onchange_amount_reconciled(self):
for rec in self:
- rec.amount_been_reconciled = sum(line.amount for line in rec.reconciliation_line_ids)
rec.amount_reconciled = rec.actual_total - rec.amount_been_reconciled
@api.depends('reconciliation_line_ids')
@@ -409,6 +408,7 @@ class PropertyReconciliationWizard(models.Model):
})
self.env['receivable.modify.line'].create({
'bank_id': rec.bank_id.id,
+ 'reconciliation_date': rec.actual_date if line.amount != 0 else None,
'receivable_id': receivable.id,
'is_write': True,
'is_penalty': False,
@@ -465,6 +465,7 @@ class PropertyReconciliationWizard(models.Model):
'remark': line.remake,
'bank_id': rec.bank_id.id if line.amount != 0 else None,
'is_write': True if line.amount != 0 else None,
+ 'reconciliation_date': rec.actual_date if line.amount != 0 else None,
'reconciliation_time': fields.Datetime.now(),
})
self.env['receivable.modify.line'].create({