From a67e28ee8e2b1293eb9a7813ed85de87a9b62203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Tue, 16 Jun 2026 18:43:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=A5=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_report/__manifest__.py | 2 + yuthon_report/models/__init__.py | 4 +- .../models/management_fee_monthly_report.py | 107 ++++++++++++++++++ .../models/management_fee_yearly_report.py | 107 ++++++++++++++++++ yuthon_report/models/yuthon_dashboard.py | 34 ++++-- yuthon_report/views/management_fee_report.xml | 27 +++++ .../views/management_fee_yearly_report.xml | 27 +++++ 7 files changed, 299 insertions(+), 9 deletions(-) create mode 100644 yuthon_report/models/management_fee_monthly_report.py create mode 100644 yuthon_report/models/management_fee_yearly_report.py create mode 100644 yuthon_report/views/management_fee_report.xml create mode 100644 yuthon_report/views/management_fee_yearly_report.xml 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} + ] + } + } + + + +