573 lines
31 KiB
Python
573 lines
31 KiB
Python
# -*- coding: utf-8 -*-
|
||
import datetime
|
||
import calendar
|
||
import logging
|
||
|
||
from odoo import api, models
|
||
from .report_utils import _id_list
|
||
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
|
||
class YuthonDashboard(models.Model):
|
||
_name = 'yuthon.dashboard'
|
||
_description = '经营汇总看板'
|
||
|
||
@api.model
|
||
def get_dashboard_data(self, params):
|
||
"""
|
||
聚合所有子报表的汇总数据,返回帆软风格的卡片布局数据。
|
||
:param params: { date_start, date_end, company_id, partner_id, keyword, condition }
|
||
:return: { filters, sections }
|
||
"""
|
||
today = datetime.date.today()
|
||
|
||
# 标准化日期参数 — 只有用户明确选了月份才传日期
|
||
date_start_raw = params.get("date_start")
|
||
has_date = bool(date_start_raw)
|
||
if has_date:
|
||
if isinstance(date_start_raw, str) and len(date_start_raw) == 7: # YYYY-MM
|
||
y, m = map(int, date_start_raw.split('-'))
|
||
query_date = datetime.date(y, m, 1)
|
||
else:
|
||
query_date = datetime.datetime.fromisoformat(date_start_raw).date()
|
||
else:
|
||
# 不选日期时用当月作为页面显示,但不强塞给子报表
|
||
query_date = today.replace(day=1)
|
||
|
||
# 构建各报表所需的标准化 params
|
||
# 注意:大部分子报表使用 dateMode="single",前端 ReceivableReport 传的是:
|
||
# date_start = "YYYY-MM-DD 00:00:00", date_end = "YYYY-MM-DD 23:59:59"
|
||
# 只有 receivable_flow_report 使用 dateMode="range"(但在本看板中不调用)
|
||
base_params = {}
|
||
if has_date:
|
||
qd_str = query_date.strftime("%Y-%m-%d")
|
||
base_params['date_start'] = f"{qd_str} 00:00:00"
|
||
base_params['date_end'] = f"{qd_str} 23:59:59"
|
||
|
||
for key in ('company_id', 'partner_id', 'keyword', 'condition',
|
||
'property_id', 'lessee_id', 'contract_id', 'management_type_id'):
|
||
if key in params and params[key]:
|
||
base_params[key] = params[key]
|
||
|
||
# 提取所有汇总金额(包装异常,单个报表失败不影响整体)
|
||
def safe_get(model_name, method_name, stats_index=1, overrides=None):
|
||
"""安全调用子报表方法,返回汇总金额
|
||
:param overrides: 可选 dict,覆盖 base_params 中的参数
|
||
看板只取 stats 不构建 rows,传 summary_only=True 减少遍历开销
|
||
"""
|
||
try:
|
||
params = dict(base_params)
|
||
if overrides:
|
||
params.update(overrides)
|
||
params['summary_only'] = True # 看板只取汇总,跳过 rows 构建
|
||
result = getattr(self.env[model_name], method_name)(params)
|
||
stats = result.get('stats', [])
|
||
if stats and len(stats) > stats_index:
|
||
return stats[stats_index].get('amount', 0.0)
|
||
elif stats:
|
||
return stats[0].get('amount', 0.0)
|
||
return 0.0
|
||
except Exception as e:
|
||
_logger.error("[DASHBOARD_SAFE_GET_ERROR] model=%s method=%s error=%s", model_name, method_name, e, exc_info=True)
|
||
return 0.0
|
||
|
||
# 全月范围参数(用于 dateMode="range" 的报表)
|
||
if has_date:
|
||
last_day = calendar.monthrange(query_date.year, query_date.month)[1]
|
||
range_params = {
|
||
'date_start': f"{query_date.strftime('%Y-%m-%d')} 00:00:00",
|
||
'date_end': f"{query_date.replace(day=last_day).strftime('%Y-%m-%d')} 23:59:59",
|
||
}
|
||
else:
|
||
range_params = {}
|
||
|
||
# ==================== 一、经营收入板块 ====================
|
||
# 当期应收(合同应收):receivable_report.py 的 get_complete_ledger_report
|
||
# stats: [租金, 管理费, 水费, 电费, 其他费用, 小计合计] → 取 index 5(违约金已移除)
|
||
# 注意:该报表使用 ReceivableReport 组件,默认 dateMode="range"(全月范围)
|
||
current_receivable = safe_get('property.accounts.receivable', 'get_complete_ledger_report', 5, overrides=range_params)
|
||
# 前期欠收
|
||
pre_arrears = safe_get('receivable.modify.line', 'get_pre_arrears_report', 1)
|
||
# 前期预收
|
||
pre_advance = safe_get('receivable.modify.line', 'get_pre_advance_report', 1)
|
||
# 本月应收 = 当期应收 + 前期欠收 - 前期预收(仅展示,不跳转)
|
||
month_receivable = current_receivable + pre_arrears - pre_advance
|
||
# 实收总额
|
||
total_received = safe_get('receivable.modify.line', 'get_total_received_report', 0)
|
||
# 归属本月
|
||
current_month_received = safe_get('receivable.modify.line', 'get_current_month_received_report', 0)
|
||
# 本月预收
|
||
monthly_precollection = safe_get('receivable.modify.line', 'get_monthly_precollection_report', 1)
|
||
# 累计欠收
|
||
cum_arrears = safe_get('receivable.modify.line', 'get_cum_arrears_report', 1)
|
||
# 累计收入
|
||
cum_income = safe_get('receivable.modify.line', 'get_cum_income_report', 1)
|
||
|
||
# ==================== 二、其他收入板块 ====================
|
||
# 违约金:调用三个独立报表方法
|
||
# 累计欠收:stats = [记录总数, 违约金总额, 违约金已收, 违约金未收] → 取 index 3(违约金未收)
|
||
# 时间过滤:用 range_params(月末),与报表页面保持一致
|
||
penalty_cum_arrears = safe_get(
|
||
'property.accounts.receivable', 'get_penalty_arrears_report', 3,
|
||
overrides=range_params
|
||
)
|
||
_logger.info("[DASHBOARD_PENALTY_ARREARS] range_params=%s, value=%s", range_params, penalty_cum_arrears)
|
||
# 本月实收:stats = [记录总数, 实收违约金] → 取 index 1
|
||
penalty_month_received = safe_get(
|
||
'property.accounts.receivable', 'get_penalty_month_received_report', 1,
|
||
overrides=range_params
|
||
)
|
||
# 本年累计:stats = [记录总数, 实收违约金] → 取 index 1,范围 1月1日 到 查询月末
|
||
penalty_year_cum_params = dict(base_params)
|
||
if has_date:
|
||
y = query_date.year
|
||
penalty_year_cum_params['date_start'] = f"{y}-01-01 00:00:00"
|
||
penalty_year_cum = safe_get(
|
||
'property.accounts.receivable', 'get_penalty_year_cum_report', 1,
|
||
overrides=penalty_year_cum_params
|
||
)
|
||
|
||
# 停车费
|
||
parking_month_receivable = safe_get('property.accounts.receivable', 'get_parking_fee_current_month_receivable', 0)
|
||
parking_month_received = safe_get('property.accounts.receivable', 'get_parking_fee_current_month_report', 0)
|
||
parking_year_cum = safe_get('property.accounts.receivable', 'get_parking_fee_yearly_report', 0)
|
||
|
||
# ==================== 三、押金板块 ====================
|
||
deposit_month_receivable = safe_get('property.accounts.receivable', 'get_deposit_month_receivable_report', 1)
|
||
deposit_pre_received = safe_get('property.accounts.receivable', 'get_deposit_pre_received_report', 1)
|
||
deposit_month_received = safe_get('property.accounts.receivable', 'get_deposit_month_received_report', 1)
|
||
deposit_cum_arrears = safe_get('property.accounts.receivable', 'get_deposit_cum_arrears_report', 1)
|
||
deposit_month_refund = safe_get('property.accounts.receivable', 'get_deposit_month_refund_report', 1)
|
||
deposit_total = safe_get('property.accounts.receivable', 'get_deposit_total_report', 1)
|
||
|
||
# ==================== 四、费用支出板块 ====================
|
||
# 直接查询本月费用支出(管理费)、维修费、评估费
|
||
fee_data = self._get_fee_expense_data(base_params, query_date)
|
||
|
||
# ==================== 五、面积/比率板块 ====================
|
||
area_data = self._get_area_data(base_params, query_date)
|
||
|
||
# ==================== 汇总信息 ====================
|
||
filters = {
|
||
'统计时间': f"{query_date.year}-{query_date.month:02d}",
|
||
# 多选:所属公司传ID列表
|
||
'所属公司': self._get_company_display(params.get('company_id')),
|
||
'管理员': self._get_admin_display(params.get('partner_id')),
|
||
}
|
||
|
||
# ==================== 构建筛选条件上下文(透传给子报表) ====================
|
||
# 子报表从 action.context.dashboard_filter 中读取并初始化筛选状态
|
||
# date_str: "YYYY-MM" 格式,子报表据此还原 DateTime 对象
|
||
# 多选:company_id/partner_id 透传ID列表,名称拼接
|
||
cid_list = _id_list(params.get('company_id'))
|
||
dashboard_filter = {
|
||
'date_str': f"{query_date.year}-{query_date.month:02d}" if has_date else '',
|
||
'company_id': cid_list or False,
|
||
'company_name': self._get_company_display(params.get('company_id')) if cid_list else '',
|
||
'partner_id': params.get('partner_id') or False,
|
||
'partner_name': self._get_admin_display(params.get('partner_id')) if params.get('partner_id') else '',
|
||
'condition': params.get('condition') or '',
|
||
}
|
||
|
||
def make_ctx(extra=None):
|
||
ctx = {'dashboard_filter': dashboard_filter}
|
||
if extra:
|
||
ctx.update(extra)
|
||
return ctx
|
||
|
||
sections = [
|
||
# 一、经营收入
|
||
{
|
||
'id': 'income',
|
||
'title': '一、经营收入',
|
||
'cards': [
|
||
{'label': '当期应收', 'value': current_receivable, 'is_negative': False,
|
||
'action': 'yuthon_report.action_property_accounts_receivable_report', 'action_context': make_ctx()},
|
||
{'label': '前期欠收', 'value': pre_arrears, 'is_negative': True,
|
||
'action': 'yuthon_report.action_pre_arrears_report', 'action_context': make_ctx()},
|
||
{'label': '前期预收', 'value': pre_advance, 'is_negative': False,
|
||
'action': 'yuthon_report.action_pre_advance_report', 'action_context': make_ctx()},
|
||
{'label': '本月应收', 'value': month_receivable, 'is_negative': False},
|
||
{'label': '实收总额', 'value': total_received, 'is_negative': False,
|
||
'action': 'yuthon_report.action_total_received_report', 'action_context': make_ctx()},
|
||
{'label': '归属本月', 'value': current_month_received, 'is_negative': False,
|
||
'action': 'yuthon_report.action_current_month_received_report', 'action_context': make_ctx()},
|
||
{'label': '本月预收', 'value': monthly_precollection, 'is_negative': False,
|
||
'action': 'yuthon_report.action_monthly_precollection_report', 'action_context': make_ctx()},
|
||
{'label': '累计欠收', 'value': cum_arrears, 'is_negative': True,
|
||
'action': 'yuthon_report.action_cum_arrears_report', 'action_context': make_ctx()},
|
||
{'label': '累计收入', 'value': cum_income, 'is_negative': False,
|
||
'action': 'yuthon_report.action_cum_income_report', 'action_context': make_ctx()},
|
||
],
|
||
'row_groups': [
|
||
['当期应收', '前期欠收', '前期预收', '本月应收', '实收总额'],
|
||
['归属本月', '本月预收', '累计欠收', '累计收入'],
|
||
],
|
||
},
|
||
# 二、其他收入
|
||
{
|
||
'id': 'other_income',
|
||
'title': '二、其他收入',
|
||
'cards': [
|
||
{'label': '违约金·累计欠收', 'value': penalty_cum_arrears, 'is_negative': True,
|
||
'action': 'yuthon_report.action_penalty_arrears_report',
|
||
'action_context': make_ctx({'date_end': range_params.get('date_end')})},
|
||
{'label': '违约金·本月实收', 'value': penalty_month_received, 'is_negative': False,
|
||
'action': 'yuthon_report.action_penalty_month_received_report',
|
||
'action_context': make_ctx({'date_start': range_params.get('date_start'), 'date_end': range_params.get('date_end')})},
|
||
{'label': '违约金·本年累计', 'value': penalty_year_cum, 'is_negative': False,
|
||
'action': 'yuthon_report.action_penalty_year_cum_report',
|
||
'action_context': make_ctx({'date_start': penalty_year_cum_params.get('date_start'), 'date_end': penalty_year_cum_params.get('date_end')})},
|
||
{'label': '停车费·本月应收', 'value': parking_month_receivable, 'is_negative': False,
|
||
'action': 'yuthon_report.action_parking_fee_current_month_receivable', 'action_context': make_ctx()},
|
||
{'label': '停车费·本月实收', 'value': parking_month_received, 'is_negative': False,
|
||
'action': 'yuthon_report.action_parking_fee_current_month_report', 'action_context': make_ctx()},
|
||
{'label': '停车费·本年累计', 'value': parking_year_cum, 'is_negative': False,
|
||
'action': 'yuthon_report.action_parking_fee_yearly_report', 'action_context': make_ctx()},
|
||
],
|
||
'row_groups': [
|
||
['违约金·累计欠收', '违约金·本月实收', '违约金·本年累计'],
|
||
['停车费·本月应收', '停车费·本月实收', '停车费·本年累计'],
|
||
],
|
||
},
|
||
# 三、押金
|
||
{
|
||
'id': 'deposit',
|
||
'title': '三、押金',
|
||
'cards': [
|
||
{'label': '本月应收', 'value': deposit_month_receivable, 'is_negative': False,
|
||
'action': 'yuthon_report.action_deposit_month_receivable_report', 'action_context': make_ctx()},
|
||
{'label': '前期实收', 'value': deposit_pre_received, 'is_negative': False,
|
||
'action': 'yuthon_report.action_deposit_pre_received_report', 'action_context': make_ctx()},
|
||
{'label': '本月实收', 'value': deposit_month_received, 'is_negative': False,
|
||
'action': 'yuthon_report.action_deposit_month_received_report', 'action_context': make_ctx()},
|
||
{'label': '累计欠收', 'value': deposit_cum_arrears, 'is_negative': True,
|
||
'action': 'yuthon_report.action_deposit_cum_arrears_report', 'action_context': make_ctx()},
|
||
{'label': '本月已退', 'value': deposit_month_refund, 'is_negative': False,
|
||
'action': 'yuthon_report.action_deposit_month_refund_report', 'action_context': make_ctx()},
|
||
{'label': '累计押金', 'value': deposit_total, 'is_negative': False,
|
||
'action': 'yuthon_report.action_deposit_total_report', 'action_context': make_ctx()},
|
||
],
|
||
'row_groups': [
|
||
['本月应收', '前期实收', '本月实收', '累计欠收', '本月已退', '累计押金'],
|
||
],
|
||
},
|
||
# 四、费用支出(cards由_get_fee_expense_data生成,需补充action_context)
|
||
{
|
||
'id': 'fee',
|
||
'title': '四、费用支出',
|
||
'cards': [dict(c, action_context=make_ctx()) for c in fee_data['cards']],
|
||
'row_groups': fee_data['row_groups'],
|
||
},
|
||
# 五、面积/比率
|
||
{
|
||
'id': 'area',
|
||
'title': '五、面积/比率',
|
||
'cards': [dict(c, action_context=make_ctx()) for c in area_data['cards']],
|
||
'row_groups': area_data['row_groups'],
|
||
},
|
||
]
|
||
|
||
return {
|
||
'filters': filters,
|
||
'sections': sections,
|
||
'notes': self._get_notes_data(base_params, query_date),
|
||
}
|
||
|
||
# -------------------------------------------------------
|
||
# 说明板块数据:仅当选择片区管理员时返回数据
|
||
# 直接调用已有报表方法拿 rows,从 rows 提取物业名和金额
|
||
# -------------------------------------------------------
|
||
|
||
def _get_notes_data(self, base_params, query_date):
|
||
"""
|
||
获取说明板块内容(仅当选择片区管理员时有数据)
|
||
直接复用已有报表方法的 rows 数据,不重复写查询逻辑
|
||
"""
|
||
partner_id = _id_list(base_params.get('partner_id'))
|
||
# 没有选片区管理员时不返回任何说明
|
||
if not partner_id:
|
||
return {'rent_arrears': [], 'monthly_precollection': [], 'expense': []}
|
||
|
||
try:
|
||
# 前期欠收:调用 get_pre_arrears_report,从 rows 取物业名和金额
|
||
rent_arrears = []
|
||
arrears_result = self.env['receivable.modify.line'].get_pre_arrears_report(base_params)
|
||
for idx, row in enumerate(arrears_result.get('rows', []), start=1):
|
||
# 物业名称可能是对象 {display_name} 或字符串
|
||
prop = row.get('物业名称', '')
|
||
prop_name = prop.get('display_name', '') if isinstance(prop, dict) else str(prop)
|
||
amount = row.get('金额', 0)
|
||
payout = row.get('收款日期', '')
|
||
# 有收款日期的标注
|
||
suffix = f"({payout}已交)" if payout else ''
|
||
rent_arrears.append({'text': f"{idx}. {prop_name}欠{self._format_amount(amount)}元{suffix};"})
|
||
|
||
# 本月预收:调用 get_monthly_precollection_report,从 rows 取物业名和金额
|
||
monthly_precollection = []
|
||
precollection_result = self.env['receivable.modify.line'].get_monthly_precollection_report(base_params)
|
||
for idx, row in enumerate(precollection_result.get('rows', []), start=1):
|
||
prop = row.get('物业名称', '')
|
||
prop_name = prop.get('display_name', '') if isinstance(prop, dict) else str(prop)
|
||
amount = row.get('金额', 0)
|
||
monthly_precollection.append({'text': f"{idx}. {prop_name}本月预收:{self._format_amount(amount)}元。"})
|
||
|
||
# 支出:调用三个费用报表方法,从 rows 取标题和实付金额
|
||
expense = []
|
||
fee_methods = [
|
||
('管理费', 'property.bills.list', 'get_management_fee_monthly_report'),
|
||
('维修费', 'property.bills.list', 'get_maintenance_fee_monthly_report'),
|
||
('评估费', 'property.bills.list', 'get_assessment_fee_monthly_report'),
|
||
]
|
||
for fee_label, model_name, method_name in fee_methods:
|
||
fee_result = getattr(self.env[model_name], method_name)(base_params)
|
||
for row in fee_result.get('rows', []):
|
||
title = row.get('标题', '')
|
||
amount_actual = row.get('实付', 0)
|
||
if amount_actual > 0:
|
||
expense.append(f"{title}{fee_label}支出{self._format_amount(amount_actual)}元。")
|
||
|
||
_logger.info("[NOTES] 结果: rent_arrears=%d条, monthly_precollection=%d条, expense=%d条",
|
||
len(rent_arrears), len(monthly_precollection), len(expense))
|
||
|
||
return {
|
||
'rent_arrears': rent_arrears,
|
||
'monthly_precollection': monthly_precollection,
|
||
'expense': expense,
|
||
}
|
||
except Exception as e:
|
||
_logger.error("[DASHBOARD_NOTES_ERROR] %s", e, exc_info=True)
|
||
return {'rent_arrears': [], 'monthly_precollection': [], 'expense': []}
|
||
|
||
def _format_amount(self, value):
|
||
"""格式化金额,去掉尾随的 .00 但保留非零小数"""
|
||
if value is None:
|
||
return "0"
|
||
rounded = round(float(value), 2)
|
||
if rounded == int(rounded):
|
||
return str(int(rounded))
|
||
return f"{rounded:.2f}"
|
||
|
||
# -------------------------------------------------------
|
||
# 私有辅助方法
|
||
# -------------------------------------------------------
|
||
|
||
def _get_company_display(self, company_id):
|
||
"""根据公司ID列表获取公司显示名(多选支持)
|
||
company_id 是 yuthon.property.company 的 ID 列表(多选)或单值
|
||
"""
|
||
if not company_id:
|
||
return '全部'
|
||
try:
|
||
# 多选:company_id 是列表,获取所有名称用逗号拼接
|
||
cid_list = _id_list(company_id)
|
||
if not cid_list:
|
||
return '全部'
|
||
companies = self.env['yuthon.property.company'].browse(cid_list)
|
||
names = [c.name for c in companies if c.name]
|
||
return ', '.join(names) if names else '未知'
|
||
except Exception:
|
||
return '未知'
|
||
|
||
def _get_admin_display(self, partner_id):
|
||
"""根据片区ID列表获取管理员显示名(多选支持)
|
||
partner_id 是 yuthon.precinct.message 的 ID 列表(多选)或单值
|
||
display_name 已自动拼接为 "{片区名称}{管理员姓名}",如 "二B梁嘉辉"
|
||
"""
|
||
if not partner_id:
|
||
return '全部'
|
||
try:
|
||
# 多选:partner_id 是列表,获取所有名称用逗号拼接
|
||
pid_list = _id_list(partner_id)
|
||
if not pid_list:
|
||
return '全部'
|
||
names = []
|
||
for pid in pid_list:
|
||
precinct = self.env['yuthon.precinct.message'].browse(pid)
|
||
if precinct:
|
||
names.append(precinct.display_name)
|
||
return '、'.join(names) if names else '未知'
|
||
except Exception:
|
||
return '未知'
|
||
|
||
def _get_fee_expense_data(self, base_params, query_date):
|
||
"""费用支出:管理费、维修费、评估费及其它"""
|
||
try:
|
||
month_first = query_date.replace(day=1)
|
||
last_day = calendar.monthrange(query_date.year, query_date.month)[1]
|
||
month_last = query_date.replace(day=last_day)
|
||
|
||
company_id = _id_list(base_params.get('company_id'))
|
||
# 多选标准化:partner_id 统一为列表
|
||
partner_id = _id_list(base_params.get('partner_id'))
|
||
keyword = base_params.get('keyword')
|
||
|
||
# 按费用类型分别统计(数据源:property.bills.list)
|
||
fee_types = ['管理费', '维修费', '评估费']
|
||
|
||
cards = []
|
||
row_labels = []
|
||
row_labels_explanations = []
|
||
|
||
for label in fee_types:
|
||
# 费用支出数据来源:property.bills.list
|
||
domain = [
|
||
('management_type_id.name', '=', label),
|
||
('actual_date', '>=', month_first),
|
||
('actual_date', '<=', month_last),
|
||
]
|
||
if company_id:
|
||
domain.append(('lessor_id', 'in', company_id))
|
||
if partner_id:
|
||
# 多选:片区管理员用 'in' 操作符
|
||
domain += ['|',
|
||
('contract_id.precinct_id', 'in', partner_id),
|
||
('receivables_id.precinct_id', 'in', partner_id),
|
||
]
|
||
if keyword:
|
||
domain += ['|', '|', '|',
|
||
('name', 'ilike', keyword),
|
||
('contract_id.contract_code', 'ilike', keyword),
|
||
('lessee_id.name', 'ilike', keyword),
|
||
('property_name', 'ilike', keyword)]
|
||
|
||
records = self.env['property.bills.list'].sudo().search(domain)
|
||
month_total = sum((r.amount_actual or 0.0) for r in records)
|
||
|
||
# 本年累计
|
||
year_start = datetime.date(query_date.year, 1, 1)
|
||
year_domain = [
|
||
('management_type_id.name', '=', label),
|
||
('actual_date', '>=', year_start),
|
||
('actual_date', '<=', month_last),
|
||
]
|
||
if company_id:
|
||
year_domain.append(('lessor_id', 'in', company_id))
|
||
if partner_id:
|
||
# 多选:片区管理员用 'in' 操作符
|
||
year_domain += ['|',
|
||
('contract_id.precinct_id', 'in', partner_id),
|
||
('receivables_id.precinct_id', 'in', partner_id),
|
||
]
|
||
year_records = self.env['property.bills.list'].sudo().search(year_domain)
|
||
year_total = sum((r.amount_actual or 0.0) for r in year_records)
|
||
|
||
# 本月支出卡片:管理费、维修费绑定报表,其他费用暂不绑定
|
||
if label == '管理费':
|
||
cards.append({
|
||
'label': f'{label}·本月支出', 'value': round(month_total, 2),
|
||
'is_negative': False,
|
||
'action': 'yuthon_report.action_management_fee_monthly_report',
|
||
'action_context': {},
|
||
})
|
||
elif label == '维修费':
|
||
cards.append({
|
||
'label': f'{label}·本月支出', 'value': round(month_total, 2),
|
||
'is_negative': False,
|
||
'action': 'yuthon_report.action_maintenance_fee_monthly_report',
|
||
'action_context': {},
|
||
})
|
||
elif label == '评估费':
|
||
cards.append({
|
||
'label': f'{label}·本月支出', 'value': round(month_total, 2),
|
||
'is_negative': False,
|
||
'action': 'yuthon_report.action_assessment_fee_monthly_report',
|
||
'action_context': {},
|
||
})
|
||
else:
|
||
cards.append({
|
||
'label': f'{label}·本月支出', 'value': round(month_total, 2),
|
||
'is_negative': False, 'action': '', 'action_context': {},
|
||
})
|
||
# 本年累计卡片:管理费、维修费绑定报表,其他费用暂不绑定
|
||
if label == '管理费':
|
||
cards.append({
|
||
'label': f'{label}·本年累计', 'value': round(year_total, 2),
|
||
'is_negative': False,
|
||
'action': 'yuthon_report.action_management_fee_yearly_report',
|
||
'action_context': {},
|
||
})
|
||
elif label == '维修费':
|
||
cards.append({
|
||
'label': f'{label}·本年累计', 'value': round(year_total, 2),
|
||
'is_negative': False,
|
||
'action': 'yuthon_report.action_maintenance_fee_yearly_report',
|
||
'action_context': {},
|
||
})
|
||
elif label == '评估费':
|
||
cards.append({
|
||
'label': f'{label}·本年累计', 'value': round(year_total, 2),
|
||
'is_negative': False,
|
||
'action': 'yuthon_report.action_assessment_fee_yearly_report',
|
||
'action_context': {},
|
||
})
|
||
else:
|
||
cards.append({
|
||
'label': f'{label}·本年累计', 'value': round(year_total, 2),
|
||
'is_negative': False, 'action': '', 'action_context': {},
|
||
})
|
||
row_labels.append(label)
|
||
# 说明信息
|
||
desc = f"{label}本月{month_total:.2f}元"
|
||
row_labels_explanations.append({'label': label, 'description': desc, 'amount': round(month_total, 2)})
|
||
|
||
row_groups = [
|
||
[f'{l}·本月支出', f'{l}·本年累计'] for l in row_labels
|
||
]
|
||
|
||
return {'cards': cards, 'row_groups': row_groups, 'explanations': row_labels_explanations}
|
||
except Exception:
|
||
return {'cards': [], 'row_groups': [], 'explanations': []}
|
||
|
||
def _get_area_data(self, base_params, query_date):
|
||
"""面积/比率板块
|
||
使用子报表方法获取汇总数据,确保看板卡片值与报表一致
|
||
已租面积拆为两个卡片:物业已租面积、合同已租面积
|
||
"""
|
||
try:
|
||
# 管理面积:调用管理面积报表(summary_only=True 跳过 rows 构建)
|
||
area_params = dict(base_params)
|
||
area_params['summary_only'] = True
|
||
management_result = self.env['management.area.report'].get_management_area_report(area_params)
|
||
management_stats = management_result.get('stats', [])
|
||
total_management = management_stats[1].get('amount', 0.0) if len(management_stats) > 1 else 0.0
|
||
|
||
# 可租面积:调用可租面积报表
|
||
rent_result = self.env['rent.area.report'].get_rent_area_report(area_params)
|
||
rent_stats = rent_result.get('stats', [])
|
||
total_rent = rent_stats[1].get('amount', 0.0) if len(rent_stats) > 1 else 0.0
|
||
|
||
# 已租面积:调用已租面积报表(返回物业已租面积和合同已租面积两个汇总)
|
||
leased_result = self.env['leased.area.report'].get_leased_area_report(area_params)
|
||
leased_stats = leased_result.get('stats', [])
|
||
# stats[1] = 物业已租面积合计, stats[2] = 合同已租面积合计
|
||
total_property_leased = leased_stats[1].get('amount', 0.0) if len(leased_stats) > 1 else 0.0
|
||
total_contract_leased = leased_stats[2].get('amount', 0.0) if len(leased_stats) > 2 else 0.0
|
||
|
||
# 出租率:物业已租面积 / 可租面积
|
||
rental_rate = round((total_property_leased / total_rent * 100) if total_rent > 0 else 0, 2)
|
||
|
||
cards = [
|
||
{'label': '管理面积(㎡)', 'value': round(total_management, 4), 'is_negative': False,
|
||
'action': 'yuthon_report.action_management_area_report', 'action_context': {}, 'is_area': True},
|
||
{'label': '可租面积(㎡)', 'value': round(total_rent, 4), 'is_negative': False,
|
||
'action': 'yuthon_report.action_rent_area_report', 'action_context': {}, 'is_area': True},
|
||
{'label': '物业已租面积(㎡)', 'value': round(total_property_leased, 4), 'is_negative': False,
|
||
'action': 'yuthon_report.action_leased_area_report', 'action_context': {}, 'is_area': True},
|
||
{'label': '合同已租面积(㎡)', 'value': round(total_contract_leased, 4), 'is_negative': False,
|
||
'action': 'yuthon_report.action_leased_area_report', 'action_context': {}, 'is_area': True},
|
||
{'label': '出租率', 'value': rental_rate, 'is_negative': False,
|
||
'action': '', 'action_context': {}, 'is_percent': True},
|
||
{'label': '收费率', 'value': 0.00, 'is_negative': False,
|
||
'action': '', 'action_context': {}, 'is_percent': True},
|
||
]
|
||
|
||
return {
|
||
'cards': cards,
|
||
'row_groups': [['管理面积(㎡)', '可租面积(㎡)', '物业已租面积(㎡)', '合同已租面积(㎡)', '出租率', '收费率']],
|
||
}
|
||
except Exception as e:
|
||
_logger.error("[DASHBOARD_AREA_ERROR] %s", e, exc_info=True)
|
||
return {'cards': [], 'row_groups': []}
|