报表
This commit is contained in:
@@ -5,6 +5,7 @@ from odoo import models, fields, api, exceptions, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import calendar
|
||||
import re
|
||||
|
||||
|
||||
class PropertyMeterWater(models.Model):
|
||||
@@ -621,6 +622,7 @@ class PropertyAccountsReceivable(models.Model):
|
||||
company_id2 = fields.Many2one("res.company", string="公司", compute='_compute_company_id2', store=True)
|
||||
contract_id = fields.Many2one('property.lease.contract', string="关联租赁合同审批")
|
||||
rent_id = fields.Char(string='租金id')
|
||||
source_receivable_id = fields.Many2one('property.accounts.receivable', string='原始应收', index=True, copy=False)
|
||||
contract_sort = fields.Selection([('property', '物业'), ('guanggao', '广告位'),('parking', '停车场'), ('guanzi', '管子间'), ('room', '机房'), ('base_station','基站'), ('other', '其他')],
|
||||
related='contract_id.contract_sort', string="资产类型", index=True)
|
||||
admin_id = fields.Many2one(related='contract_id.admin_id', string="片区管理员", store=True)
|
||||
@@ -882,6 +884,45 @@ class PropertyAccountsReceivable(models.Model):
|
||||
}
|
||||
}
|
||||
|
||||
def _resolve_source_receivable(self):
|
||||
self.ensure_one()
|
||||
empty_receivable = self.env['property.accounts.receivable']
|
||||
if self.source_receivable_id:
|
||||
return self.source_receivable_id
|
||||
if self.management_type_id.name != '违约金' or not self.contract_id:
|
||||
return empty_receivable
|
||||
|
||||
date_match = re.search(r'(\d{4}-\d{2}-\d{2})[至-](\d{4}-\d{2}-\d{2})', self.remark or '')
|
||||
if not date_match:
|
||||
return empty_receivable
|
||||
|
||||
start_date = datetime.strptime(date_match.group(1), "%Y-%m-%d").date()
|
||||
candidates = self.search([
|
||||
('contract_id', '=', self.contract_id.id),
|
||||
('id', '!=', self.id),
|
||||
('is_invalid', '=', False),
|
||||
('management_type_id.name', 'not in', ['违约金', '押金']),
|
||||
('is_liquidated_damages', '=', True),
|
||||
], order='due_date asc, id asc')
|
||||
|
||||
payout_matches = candidates.filtered(
|
||||
lambda r: r.payout_date and r.payout_date + timedelta(days=1) == start_date
|
||||
)
|
||||
if len(payout_matches) == 1:
|
||||
return payout_matches[:1]
|
||||
|
||||
cycle_matches = candidates.filtered(
|
||||
lambda r: r.due_date and (r.due_date + relativedelta(months=1)).replace(day=1) == start_date
|
||||
)
|
||||
if len(cycle_matches) == 1:
|
||||
return cycle_matches[:1]
|
||||
|
||||
if payout_matches:
|
||||
return payout_matches[:1]
|
||||
if cycle_matches:
|
||||
return cycle_matches[:1]
|
||||
return empty_receivable
|
||||
|
||||
@api.depends('contract_id')
|
||||
def _compute_property_name(self):
|
||||
for record in self:
|
||||
|
||||
@@ -150,6 +150,7 @@ class PropertyReconciliationWizard(models.Model):
|
||||
('management_type_id.name', '=', '违约金')
|
||||
])
|
||||
for penalty in receivable_penalties:
|
||||
source_receivable = penalty.source_receivable_id or penalty._resolve_source_receivable()
|
||||
contract_penalties.append({
|
||||
'contract_id': penalty.contract_id.id,
|
||||
'rent_month': 0,
|
||||
@@ -158,6 +159,7 @@ class PropertyReconciliationWizard(models.Model):
|
||||
'amount': 0,
|
||||
'collection_type': 'penalty',
|
||||
'remake': penalty.remark,
|
||||
'source_receivable_id': source_receivable.id if source_receivable else False,
|
||||
})
|
||||
contract_receivables_ids = self.env['property.accounts.receivable'].search([
|
||||
('contract_id', '=', contract_id.id.origin),
|
||||
@@ -194,6 +196,7 @@ class PropertyReconciliationWizard(models.Model):
|
||||
'amount': 0,
|
||||
'collection_type': 'penalty',
|
||||
'remake': f'违约金({start_date_str}至{end_date_str},共{late_days}天)',
|
||||
'source_receivable_id': contract_receivables.id,
|
||||
})
|
||||
|
||||
# 查找应收是否有未交全的租金
|
||||
@@ -225,6 +228,7 @@ class PropertyReconciliationWizard(models.Model):
|
||||
'amount': 0,
|
||||
'collection_type': 'penalty',
|
||||
'remake': f'违约金({fields.Date.to_string(first_day)}至{fields.Date.to_string(self.actual_date)},共{penalty_days.days + 1}天)',
|
||||
'source_receivable_id': rent.id,
|
||||
}))
|
||||
|
||||
# 3. 添加到总列表
|
||||
@@ -404,8 +408,8 @@ class PropertyReconciliationWizard(models.Model):
|
||||
'amount': line.amount,
|
||||
})
|
||||
|
||||
management_type_id = self.env['yuthon.management.type'].search([('name', '=', '违约金')])
|
||||
if line.collection_type == 'penalty':
|
||||
management_type_id = self.env['yuthon.management.type'].search([('name', '=', '违约金')], limit=1)
|
||||
if line.collection_type == 'penalty' and management_type_id:
|
||||
penalty_date = line.due_date
|
||||
first_day_of_last_month = penalty_date.replace(day=1) - relativedelta(months=2)
|
||||
# 上个月最后一天:当前月第一天减1天
|
||||
@@ -423,6 +427,11 @@ class PropertyReconciliationWizard(models.Model):
|
||||
date_match = re.search(date_pattern, line.remake or '')
|
||||
extract_start_date = date_match.group(1) if date_match else line.due_date
|
||||
extract_end_date = date_match.group(2) if date_match else None
|
||||
if extract_start_date and isinstance(extract_start_date, str):
|
||||
extract_start_date = datetime.strptime(extract_start_date, "%Y-%m-%d").date()
|
||||
if extract_end_date and isinstance(extract_end_date, str):
|
||||
extract_end_date = datetime.strptime(extract_end_date, "%Y-%m-%d").date()
|
||||
|
||||
check_domain = [
|
||||
('due_date', '=', line.due_date),
|
||||
('contract_id', '=', line.contract_id.id),
|
||||
@@ -430,24 +439,19 @@ class PropertyReconciliationWizard(models.Model):
|
||||
('is_invalid', '=', False),
|
||||
('management_type_id', '=', management_type_id.id),
|
||||
]
|
||||
# 检查是否已有相同记录
|
||||
existing_record = self.env['property.accounts.receivable'].search(check_domain)
|
||||
is_reconciliation = round(line.amount,2) == round(line.rent, 2)
|
||||
source_receivable = line.source_receivable_id
|
||||
if not source_receivable and existing_record:
|
||||
source_receivable = existing_record[:1].source_receivable_id or existing_record[:1]._resolve_source_receivable()
|
||||
is_reconciliation = round(line.amount, 2) == round(line.rent, 2)
|
||||
receivable_id = self.env['property.accounts.receivable']
|
||||
|
||||
if not existing_record and is_create:
|
||||
domain2 = [
|
||||
('due_date', '=', line.due_date),
|
||||
('contract_id', '=', line.contract_id.id),
|
||||
('collection_type', '=', 'rent'),
|
||||
]
|
||||
extract_start_date = datetime.strptime(extract_start_date, "%Y-%m-%d").date()
|
||||
extract_end_date = datetime.strptime(extract_end_date, "%Y-%m-%d").date()
|
||||
reconciliation_id = self.env['property.reconciliation.line'].search(domain2, limit=1)
|
||||
receivable_id = self.env['property.accounts.receivable'].create({
|
||||
'is_reconciliation': is_reconciliation,
|
||||
'due_date': extract_end_date,
|
||||
'due_date': extract_end_date or line.due_date,
|
||||
'due_date_end': None,
|
||||
'penalty': reconciliation_id.rent,
|
||||
'penalty': source_receivable.submitted if source_receivable else 0,
|
||||
'management_type_id': management_type_id.id,
|
||||
'contract_id': line.contract_id.id,
|
||||
'delivered1': line.amount,
|
||||
@@ -455,13 +459,17 @@ class PropertyReconciliationWizard(models.Model):
|
||||
'original_amount': line.rent,
|
||||
'remark': line.remake,
|
||||
'reconciliation_time': fields.Datetime.now(),
|
||||
'charging_range': f"{extract_start_date} 到 {extract_end_date}共{(extract_end_date - extract_start_date).days + 1}"
|
||||
'charging_range': f"{extract_start_date} 到 {extract_end_date}共{(extract_end_date - extract_start_date).days + 1}" if extract_start_date and extract_end_date else False,
|
||||
'source_receivable_id': source_receivable.id if source_receivable else False,
|
||||
})
|
||||
else:
|
||||
if line.amount:
|
||||
existing_record.write({
|
||||
'delivered1': existing_record.delivered1 + line.amount,
|
||||
'due_date': extract_end_date,
|
||||
receivable_id = existing_record[:1]
|
||||
if receivable_id and source_receivable and not receivable_id.source_receivable_id:
|
||||
receivable_id.write({'source_receivable_id': source_receivable.id})
|
||||
if receivable_id and line.amount:
|
||||
receivable_id.write({
|
||||
'delivered1': receivable_id.delivered1 + line.amount,
|
||||
'due_date': extract_end_date or line.due_date,
|
||||
'due_date_end': None,
|
||||
'is_reconciliation': is_reconciliation,
|
||||
'payout_date': rec.actual_date if (line.rent == line.amount and line.rent != 0) else None,
|
||||
@@ -470,9 +478,6 @@ class PropertyReconciliationWizard(models.Model):
|
||||
'reconciliation_time': fields.Datetime.now(),
|
||||
})
|
||||
|
||||
if existing_record:
|
||||
receivable_id = existing_record
|
||||
|
||||
if receivable_id:
|
||||
self.env['receivable.modify.line'].create({
|
||||
'bank_id': rec.bank_id.id,
|
||||
@@ -480,13 +485,12 @@ class PropertyReconciliationWizard(models.Model):
|
||||
'is_write': True if line.amount != 0 else False,
|
||||
'is_penalty': True,
|
||||
'reconciliation_date': rec.actual_date if line.amount != 0 else None,
|
||||
'receivable_id': receivable_id[0].id,
|
||||
'receivable_id': receivable_id.id,
|
||||
'amount': line.amount,
|
||||
})
|
||||
# 删除多余的违约金数据
|
||||
if len(receivable_id) > 1:
|
||||
receivable_id = existing_record[1:]
|
||||
receivable_id.unlink()
|
||||
if len(existing_record) > 1:
|
||||
existing_record[1:].unlink()
|
||||
|
||||
rec.amount_been_reconciled += line.amount
|
||||
yes_amount += line.amount
|
||||
@@ -518,6 +522,7 @@ class PropertyReconciliationLine(models.Model):
|
||||
rent_month = fields.Float(string='当期金额')
|
||||
due_date = fields.Date(string='应收日期')
|
||||
rent = fields.Float(string='未收')
|
||||
source_receivable_id = fields.Many2one('property.accounts.receivable', string='原始应收')
|
||||
amount = fields.Float(string='金额', store=True)
|
||||
collection_type = fields.Selection([('deposit', '押金'), ('rent', '租金'), ('water_fee', '水费'), ('lightbox', '灯箱'), ('guanzijian', '管子间'),
|
||||
('electricity_fee', '电费'), ('penalty', '违约金'), ('management_fee', '管理费'), ('repair_fee', '维修费'), ('fee', '人工费/日光灯款/代垫用料费'), ('rename_fee', '更名费'), ('service', '有偿服务收入')], string='收款类型', store=True)
|
||||
|
||||
@@ -58,51 +58,43 @@ class ReceivableFlowReport(models.Model):
|
||||
rec_ids = receivables.ids
|
||||
|
||||
# ===== 批量预加载,避免N+1查询 =====
|
||||
# 0. 先查询违约金类型记录
|
||||
penalty_type = self.env['yuthon.management.type'].search([('name', '=', '违约金')], limit=1)
|
||||
|
||||
# 1. 批量查收款明细行(sudo跳过多公司权限)
|
||||
all_modify_lines = self.env['receivable.modify.line'].sudo().search([
|
||||
('receivable_id', 'in', rec_ids),
|
||||
('reconciliation_date', '!=', False),
|
||||
])
|
||||
# 按 receivable_id 分组
|
||||
modify_map = {} # {receivable_id: [lines]}
|
||||
modify_map = {}
|
||||
for ml in all_modify_lines:
|
||||
modify_map.setdefault(ml.receivable_id.id, []).append(ml)
|
||||
|
||||
# 2. 直接查询所有 penalty.pay.line 记录
|
||||
# 先查询所有相关的 penalty.line.wizard
|
||||
all_wizards = self.env['penalty.line.wizard'].sudo().search([
|
||||
('receivable_id', 'in', rec_ids),
|
||||
penalty_receivables = self.env['property.accounts.receivable'].sudo().search([
|
||||
('contract_id', 'in', receivables.mapped('contract_id').ids),
|
||||
('is_invalid', '=', False),
|
||||
('management_type_id.name', '=', '违约金'),
|
||||
])
|
||||
all_wizard_ids = all_wizards.ids
|
||||
|
||||
# 然后查询所有相关的 penalty.pay.line
|
||||
if all_wizard_ids:
|
||||
all_penalty_pay_lines = self.env['penalty.pay.line'].sudo().search([
|
||||
('penalty_id', 'in', all_wizard_ids),
|
||||
penalty_source_map = {}
|
||||
penalty_receivable_ids = []
|
||||
for penalty_rec in penalty_receivables:
|
||||
source_receivable = penalty_rec.source_receivable_id or penalty_rec._resolve_source_receivable()
|
||||
if source_receivable and source_receivable.id in rec_ids:
|
||||
penalty_source_map[penalty_rec.id] = source_receivable.id
|
||||
penalty_receivable_ids.append(penalty_rec.id)
|
||||
|
||||
penalty_modify_map = {}
|
||||
if penalty_receivable_ids:
|
||||
penalty_modify_lines = self.env['receivable.modify.line'].sudo().search([
|
||||
('receivable_id', 'in', penalty_receivable_ids),
|
||||
('reconciliation_date', '!=', False),
|
||||
])
|
||||
else:
|
||||
all_penalty_pay_lines = self.env['penalty.pay.line']
|
||||
|
||||
# 调试信息
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
_logger.info(f"查询的 rec_ids 数量: {len(rec_ids)}, 示例: {rec_ids[:5] if len(rec_ids) > 5 else rec_ids}")
|
||||
_logger.info(f"查询到的 all_wizards 数量: {len(all_wizards)}")
|
||||
_logger.info(f"查询到的 all_penalty_pay_lines 数量: {len(all_penalty_pay_lines)}")
|
||||
|
||||
# 按 receivable_id 分组 {receivable_id: [penalty_pay_lines]}
|
||||
penalty_pay_map = {}
|
||||
for ppl in all_penalty_pay_lines:
|
||||
rid = ppl.penalty_id.receivable_id.id if ppl.penalty_id and ppl.penalty_id.receivable_id else None
|
||||
if rid:
|
||||
penalty_pay_map.setdefault(rid, []).append(ppl)
|
||||
|
||||
_logger.info(f"penalty_pay_map 中的 receivable_id 数量: {len(penalty_pay_map)}, 示例: {list(penalty_pay_map.keys())[:5] if len(penalty_pay_map) > 5 else list(penalty_pay_map.keys())}")
|
||||
for ml in penalty_modify_lines:
|
||||
source_id = penalty_source_map.get(ml.receivable_id.id)
|
||||
if source_id and (
|
||||
ml.is_penalty
|
||||
or (ml.receivable_id.management_type_id and ml.receivable_id.management_type_id.name == '违约金')
|
||||
):
|
||||
penalty_modify_map.setdefault(source_id, []).append(ml)
|
||||
# ===== 预加载结束 =====
|
||||
|
||||
|
||||
rows = []
|
||||
seq = 0
|
||||
|
||||
@@ -180,51 +172,19 @@ class ReceivableFlowReport(models.Model):
|
||||
if rec.submitted == 0:
|
||||
charge_status = "已收"
|
||||
|
||||
# 违约金收款:直接从 penalty.pay.line 计算
|
||||
pay_lines = penalty_pay_map.get(rec.id, [])
|
||||
|
||||
# 调试信息
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
_logger.info(f"应收记录ID: {rec.id}, penalty_type: {penalty_type}, penalty_type.id: {penalty_type.id if penalty_type else None}")
|
||||
_logger.info(f"pay_lines 数量: {len(pay_lines)}")
|
||||
for ppl in pay_lines:
|
||||
_logger.info(f" - penalty.pay.line ID: {ppl.id}, management_type_id: {ppl.management_type_id}, management_type_id.id: {ppl.management_type_id.id if ppl.management_type_id else None}, amount: {ppl.amount}, date: {ppl.date}")
|
||||
|
||||
# 违约金收款:兼容不同公司/多条“违约金”管理类型,按名称兜底过滤
|
||||
penalty_pay_lines = [
|
||||
l for l in pay_lines
|
||||
if (
|
||||
(penalty_type and l.management_type_id == penalty_type)
|
||||
or (l.management_type_id and l.management_type_id.name == '违约金')
|
||||
penalty_payment_lines = []
|
||||
if rec.management_type_id.name != '违约金':
|
||||
penalty_payment_lines = sorted(
|
||||
penalty_modify_map.get(rec.id, []),
|
||||
key=lambda l: l.reconciliation_date or datetime.date.min
|
||||
)
|
||||
]
|
||||
|
||||
# 如果 penalty.pay.line 没取到,兜底用 receivable.modify.line 中 is_penalty=True 的收款
|
||||
if not penalty_pay_lines and modify_lines:
|
||||
penalty_pay_lines = [
|
||||
type('PenaltyPayProxy', (), {
|
||||
'amount': ml.amount,
|
||||
'date': ml.reconciliation_date,
|
||||
})
|
||||
for ml in modify_lines
|
||||
if getattr(ml, 'is_penalty', False)
|
||||
]
|
||||
total_penalty_collected = round(
|
||||
sum((line.amount or 0.0) for line in penalty_payment_lines),
|
||||
2,
|
||||
)
|
||||
penalty_payment_date = penalty_payment_lines[-1].reconciliation_date if penalty_payment_lines else None
|
||||
|
||||
collected = sum((getattr(l, 'amount', 0.0) or 0.0) for l in penalty_pay_lines)
|
||||
total_penalty_collected = round(collected, 2)
|
||||
|
||||
# 违约金收款时间:取最新收款日期
|
||||
penalty_payment_date = None
|
||||
if penalty_pay_lines:
|
||||
latest_penalty_pay = sorted(
|
||||
[l for l in penalty_pay_lines if getattr(l, 'date', False)],
|
||||
key=lambda l: l.date,
|
||||
reverse=True,
|
||||
)
|
||||
penalty_payment_date = latest_penalty_pay[0].date if latest_penalty_pay else None
|
||||
|
||||
_logger.info(f"total_penalty_collected: {total_penalty_collected}, penalty_payment_date: {penalty_payment_date}")
|
||||
|
||||
# 复用 _onchange_calculation_date 逻辑计算违约金时间区间、逆期天数、违约金
|
||||
penalty_range_str = ""
|
||||
|
||||
Reference in New Issue
Block a user