diff --git a/yuthon_report/__manifest__.py b/yuthon_report/__manifest__.py
index d6393ae6f..55a300e29 100644
--- a/yuthon_report/__manifest__.py
+++ b/yuthon_report/__manifest__.py
@@ -41,6 +41,8 @@
'views/receivable_flow_report.xml',
'views/text_summary_report.xml',
'views/yuthon_dashboard_views.xml',
+ 'views/management_fee_report.xml',
+ 'views/management_fee_yearly_report.xml',
],
'installable': True,
'application': True,
diff --git a/yuthon_report/models/__init__.py b/yuthon_report/models/__init__.py
index 972eebf14..adcf52b53 100644
--- a/yuthon_report/models/__init__.py
+++ b/yuthon_report/models/__init__.py
@@ -35,4 +35,6 @@ from . import hydropower_report
from . import receivable_filter_domains
from . import text_summary_report
from . import bank_statements_report
-from . import yuthon_dashboard
\ No newline at end of file
+from . import yuthon_dashboard
+from . import management_fee_monthly_report
+from . import management_fee_yearly_report
\ No newline at end of file
diff --git a/yuthon_report/models/management_fee_monthly_report.py b/yuthon_report/models/management_fee_monthly_report.py
new file mode 100644
index 000000000..8c3da4ef1
--- /dev/null
+++ b/yuthon_report/models/management_fee_monthly_report.py
@@ -0,0 +1,107 @@
+import datetime
+import calendar
+import logging
+
+from odoo import api, models
+
+_logger = logging.getLogger(__name__)
+
+
+class PropertyBillsList(models.Model):
+ _inherit = 'property.bills.list'
+
+ @api.model
+ def get_management_fee_monthly_report(self, params):
+ """管理费本月支出报表。
+
+ 数据源:property.bills.list
+ 过滤条件:
+ - management_type_id.name = '管理费'
+ - actual_date(实付日期)在查询月本月范围内(月初~月末)
+ 字段:应付日期、实付日期、应付、实付、标题
+ """
+ # ========== 一、参数解析 ==========
+ date_start = params.get("date_start")
+ keyword = params.get("keyword")
+
+ # ========== 二、查询日期解析 ==========
+ if date_start:
+ try:
+ query_date = datetime.datetime.fromisoformat(date_start.split(' ')[0]).date()
+ except Exception:
+ query_date = datetime.date.today()
+ else:
+ query_date = datetime.date.today()
+
+ # 计算查询月月初和月末
+ month_first = query_date.replace(day=1)
+ _, last_day = calendar.monthrange(query_date.year, query_date.month)
+ month_last = query_date.replace(day=last_day)
+
+ # ========== 三、构建domain ==========
+ domain = [
+ ('management_type_id.name', '=', '管理费'),
+ ('actual_date', '>=', month_first),
+ ('actual_date', '<=', month_last),
+ ]
+
+ # 关键字过滤
+ if keyword:
+ domain += ['|', '|', '|',
+ ('name', 'ilike', keyword),
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ]
+
+ # 公司/片区过滤
+ company_id = params.get('company_id')
+ partner_id = params.get('partner_id')
+ if company_id:
+ domain.append(('lessor_id', '=', int(company_id)))
+ if partner_id:
+ domain += ['|',
+ ('contract_id.precinct_id', '=', int(partner_id)),
+ ('receivables_id.precinct_id', '=', int(partner_id)),
+ ]
+
+ # 查询记录
+ records = self.sudo().search(domain)
+
+ # ========== 四、构建报表行 ==========
+ rows = []
+ total_amount = 0.0 # 应付合计
+ total_amount_actual = 0.0 # 实付合计
+
+ for idx, rec in enumerate(records, start=1):
+ due_date_str = rec.due_date.strftime("%Y-%m-%d") if rec.due_date else ""
+ actual_date_str = rec.actual_date.strftime("%Y-%m-%d") if rec.actual_date else ""
+ amount_val = round(rec.amount or 0.0, 2)
+ amount_actual_val = round(rec.amount_actual or 0.0, 2)
+
+ total_amount += amount_val
+ total_amount_actual += amount_actual_val
+
+ rows.append({
+ "序号": idx,
+ "应付日期": due_date_str,
+ "实付日期": actual_date_str,
+ "应付": amount_val,
+ "实付": amount_actual_val,
+ "标题": rec.name or "",
+ })
+
+ # ========== 五、构建统计汇总数据 ==========
+ stats = [
+ {'name': '记录总数', 'amount': len(rows)},
+ {'name': '应付合计', 'amount': round(total_amount, 2)},
+ {'name': '实付合计', 'amount': round(total_amount_actual, 2)},
+ ]
+
+ _logger.info(
+ "[MANAGEMENT_FEE_MONTHLY] query_date=%s, month_range=[%s, %s], rows=%d, "
+ "total_amount=%.2f, total_amount_actual=%.2f",
+ query_date, month_first, month_last, len(rows), total_amount, total_amount_actual,
+ )
+
+ return {"rows": rows, "stats": stats}
diff --git a/yuthon_report/models/management_fee_yearly_report.py b/yuthon_report/models/management_fee_yearly_report.py
new file mode 100644
index 000000000..de6a17fc0
--- /dev/null
+++ b/yuthon_report/models/management_fee_yearly_report.py
@@ -0,0 +1,107 @@
+import datetime
+import calendar
+import logging
+
+from odoo import api, models
+
+_logger = logging.getLogger(__name__)
+
+
+class PropertyBillsList(models.Model):
+ _inherit = 'property.bills.list'
+
+ @api.model
+ def get_management_fee_yearly_report(self, params):
+ """管理费本年累计报表。
+
+ 数据源:property.bills.list
+ 过滤条件:
+ - management_type_id.name = '管理费'
+ - actual_date(实付日期)在查询月所在年的1月1日~查询月末
+ 字段:应付日期、实付日期、应付、实付、标题
+ """
+ # ========== 一、参数解析 ==========
+ date_start = params.get("date_start")
+ keyword = params.get("keyword")
+
+ # ========== 二、查询日期解析 ==========
+ if date_start:
+ try:
+ query_date = datetime.datetime.fromisoformat(date_start.split(' ')[0]).date()
+ except Exception:
+ query_date = datetime.date.today()
+ else:
+ query_date = datetime.date.today()
+
+ # 计算查询范围:年初(1月1日)到查询月末
+ year_start = query_date.replace(month=1, day=1)
+ _, last_day = calendar.monthrange(query_date.year, query_date.month)
+ month_last = query_date.replace(day=last_day)
+
+ # ========== 三、构建domain ==========
+ domain = [
+ ('management_type_id.name', '=', '管理费'),
+ ('actual_date', '>=', year_start),
+ ('actual_date', '<=', month_last),
+ ]
+
+ # 关键字过滤
+ if keyword:
+ domain += ['|', '|', '|',
+ ('name', 'ilike', keyword),
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ]
+
+ # 公司/片区过滤
+ company_id = params.get('company_id')
+ partner_id = params.get('partner_id')
+ if company_id:
+ domain.append(('lessor_id', '=', int(company_id)))
+ if partner_id:
+ domain += ['|',
+ ('contract_id.precinct_id', '=', int(partner_id)),
+ ('receivables_id.precinct_id', '=', int(partner_id)),
+ ]
+
+ # 查询记录
+ records = self.sudo().search(domain)
+
+ # ========== 四、构建报表行 ==========
+ rows = []
+ total_amount = 0.0 # 应付合计
+ total_amount_actual = 0.0 # 实付合计
+
+ for idx, rec in enumerate(records, start=1):
+ due_date_str = rec.due_date.strftime("%Y-%m-%d") if rec.due_date else ""
+ actual_date_str = rec.actual_date.strftime("%Y-%m-%d") if rec.actual_date else ""
+ amount_val = round(rec.amount or 0.0, 2)
+ amount_actual_val = round(rec.amount_actual or 0.0, 2)
+
+ total_amount += amount_val
+ total_amount_actual += amount_actual_val
+
+ rows.append({
+ "序号": idx,
+ "应付日期": due_date_str,
+ "实付日期": actual_date_str,
+ "应付": amount_val,
+ "实付": amount_actual_val,
+ "标题": rec.name or "",
+ })
+
+ # ========== 五、构建统计汇总数据 ==========
+ stats = [
+ {'name': '记录总数', 'amount': len(rows)},
+ {'name': '应付合计', 'amount': round(total_amount, 2)},
+ {'name': '实付合计', 'amount': round(total_amount_actual, 2)},
+ ]
+
+ _logger.info(
+ "[MANAGEMENT_FEE_YEARLY] query_date=%s, year_range=[%s, %s], rows=%d, "
+ "total_amount=%.2f, total_amount_actual=%.2f",
+ query_date, year_start, month_last, len(rows), total_amount, total_amount_actual,
+ )
+
+ return {"rows": rows, "stats": stats}
diff --git a/yuthon_report/models/yuthon_dashboard.py b/yuthon_report/models/yuthon_dashboard.py
index cb2a2d242..c21de6f1d 100644
--- a/yuthon_report/models/yuthon_dashboard.py
+++ b/yuthon_report/models/yuthon_dashboard.py
@@ -352,14 +352,32 @@ class YuthonDashboard(models.Model):
year_receivables = self.env['property.accounts.receivable'].sudo().search(year_domain)
year_total = sum((r.original_amount or 0.0) for r in year_receivables)
- cards.append({
- 'label': f'{label}·本月支出', 'value': round(month_total, 2),
- 'is_negative': False, 'action': '', 'action_context': {},
- })
- cards.append({
- 'label': f'{label}·本年累计', 'value': round(year_total, 2),
- 'is_negative': False, 'action': '', 'action_context': {},
- })
+ # 本月支出卡片:管理费绑定报表,其他费用暂不绑定
+ 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': {},
+ })
+ 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': {},
+ })
+ 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}元"
diff --git a/yuthon_report/views/management_fee_report.xml b/yuthon_report/views/management_fee_report.xml
new file mode 100644
index 000000000..3a5765058
--- /dev/null
+++ b/yuthon_report/views/management_fee_report.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ 管理费本月支出
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.bills.list",
+ "method": "get_management_fee_monthly_report",
+ "dateMode": "single",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "width": 45, "flex": 0, "pinned": "left"},
+ {"headerName": "应付日期", "field": "应付日期", "flex": 1.2},
+ {"headerName": "实付日期", "field": "实付日期", "flex": 1.2},
+ {"headerName": "应付", "field": "应付", "flex": 1, "type": "number"},
+ {"headerName": "实付", "field": "实付", "flex": 1, "type": "number"},
+ {"headerName": "标题", "field": "标题", "flex": 2.5}
+ ]
+ }
+ }
+
+
+
+
diff --git a/yuthon_report/views/management_fee_yearly_report.xml b/yuthon_report/views/management_fee_yearly_report.xml
new file mode 100644
index 000000000..885490122
--- /dev/null
+++ b/yuthon_report/views/management_fee_yearly_report.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ 管理费本年累计
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.bills.list",
+ "method": "get_management_fee_yearly_report",
+ "dateMode": "single",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "width": 45, "flex": 0, "pinned": "left"},
+ {"headerName": "应付日期", "field": "应付日期", "flex": 1.2},
+ {"headerName": "实付日期", "field": "实付日期", "flex": 1.2},
+ {"headerName": "应付", "field": "应付", "flex": 1, "type": "number"},
+ {"headerName": "实付", "field": "实付", "flex": 1, "type": "number"},
+ {"headerName": "标题", "field": "标题", "flex": 2.5}
+ ]
+ }
+ }
+
+
+
+