@@ -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='修改金额')
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
<field name="name">property.bank.statements.tree</field>
|
||||
<field name="model">property.bank.statements</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="对账" editable="bottom" js_class="custom_statements_list_button" edit="false" delete="false" limit="15">
|
||||
<tree string="对账" editable="bottom" js_class="custom_statements_list_button" edit="false" delete="false" limit="15"
|
||||
decoration-warning="bank_state == 'registered'"
|
||||
>
|
||||
<field name="your_own_unit"/>
|
||||
<field name="start_time" />
|
||||
<field name="name_account" />
|
||||
@@ -21,7 +23,9 @@
|
||||
<field name="name">property.bank.statements.tree</field>
|
||||
<field name="model">property.bank.statements</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="银行流水" editable="bottom" js_class="custom_bank_statements_button" delete="false" default_order="start_time desc" limit="15">
|
||||
<tree string="银行流水" editable="bottom" js_class="custom_bank_statements_button" delete="false" default_order="start_time desc" limit="15"
|
||||
decoration-warning="bank_state == 'registered'"
|
||||
>
|
||||
<field name="your_own_account" optional="hide"/>
|
||||
<field name="your_own_unit" optional="hide"/>
|
||||
<header>
|
||||
@@ -54,6 +58,8 @@
|
||||
<field name="receivable_modify_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="bank_id" column_invisible="1"/>
|
||||
<field name="reconciliation_date"/>
|
||||
<field name="is_penalty"/>
|
||||
<field name="receivable_id"/>
|
||||
<field name="amount"/>
|
||||
</tree>
|
||||
@@ -88,7 +94,7 @@
|
||||
<record id="view_property_bank_statements_action1" model="ir.actions.act_window">
|
||||
<field name="name">银行流水</field>
|
||||
<field name="res_model">property.bank.statements</field>
|
||||
<field name="view_mode">tree,search</field>
|
||||
<field name="view_mode">tree,form,search</field>
|
||||
<field name="domain">[('is_company', '=', True)]</field>
|
||||
<field name="context">{'group_by': 'bank_state'}</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
|
||||
@@ -598,12 +598,26 @@ class PropertyAccountsReceivable(models.Model):
|
||||
rec.is_rent = False
|
||||
|
||||
def penalty_wizard(self):
|
||||
self.ensure_one()
|
||||
penalty_lines = []
|
||||
penalty_lines.append((0, 0, {
|
||||
'date': self.due_date,
|
||||
'remake': f'开始计算违约金,应收金额: ¥{self.original_amount}',
|
||||
'days_overdue': 0,
|
||||
'remaining_principal': self.original_amount,
|
||||
'penalty': 0,
|
||||
}))
|
||||
return {
|
||||
'name': '合同违约金明细',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'penalty.line.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'name': '合同违约金明细',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'penalty.line.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_receivable_id': self.id,
|
||||
'default_penalty_line_ids': penalty_lines,
|
||||
'default_rent_collected': self.submitted,
|
||||
}
|
||||
}
|
||||
|
||||
@api.depends('contract_id')
|
||||
|
||||
@@ -627,14 +627,13 @@
|
||||
<button name="other" type="object" class="oe_highlight" string="其他"/>
|
||||
</header>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='property_ids']" position="before">
|
||||
<field name="contract_ids" string="合同编号" widget="many2many_tags" readonly="1" domain="[('lessor_id.name', '=', your_own_unit)]"/>
|
||||
<field name="property_name" string="物业信息"/>
|
||||
<field name="admin_id"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='bank_state']" position="after">
|
||||
<button name="button_reconciliation" type="object" string="对账" class="btn btn-primary" invisible="bank_state != 'not_reconciled'"/>
|
||||
<button name="button_reconciliation" type="object" string="对账" class="btn btn-primary" invisible="bank_state == 'reconciled'"/>
|
||||
<button name="binding_information" type="object" string="绑定信息" class="btn btn-primary"/>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<sheet>
|
||||
<group col="2">
|
||||
<group>
|
||||
<field name="receivable_id" column_invisible="1"/>
|
||||
<field name="rent_collected"/>
|
||||
<field name="days_overdue"/>
|
||||
<field name="total_penalty"/>
|
||||
@@ -33,7 +34,7 @@
|
||||
<field name="remake"/>
|
||||
<field name="days_overdue"/>
|
||||
<field name="remaining_principal"/>
|
||||
<field name="calculation_formula"/>
|
||||
<field name="penalty"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user