diff --git a/yuthon_report/__manifest__.py b/yuthon_report/__manifest__.py
index 0add3096f..0dd3c1ccd 100644
--- a/yuthon_report/__manifest__.py
+++ b/yuthon_report/__manifest__.py
@@ -11,6 +11,7 @@
'views/pre_arrears_report.xml',
'views/pre_advance_report.xml',
'views/month_receivable_report.xml',
+ 'views/total_received_report.xml',
'views/penalty_arrears_report.xml',
'views/penalty_month_received_report.xml',
'views/penalty_year_cum_report.xml',
diff --git a/yuthon_report/models/__init__.py b/yuthon_report/models/__init__.py
index a5840c504..d2816f2c3 100644
--- a/yuthon_report/models/__init__.py
+++ b/yuthon_report/models/__init__.py
@@ -17,6 +17,7 @@ from . import staff_position_report
from . import pre_arrears_report
from . import pre_advance_report
from . import month_receivable_report
+from . import total_received_report
from . import penalty_arrears_report
from . import penalty_month_received_report
from . import penalty_year_cum_report
diff --git a/yuthon_report/models/total_received_report.py b/yuthon_report/models/total_received_report.py
new file mode 100644
index 000000000..85fc4a5b2
--- /dev/null
+++ b/yuthon_report/models/total_received_report.py
@@ -0,0 +1,90 @@
+import datetime
+import calendar
+
+from odoo import api, models
+
+
+class TotalReceivedReport(models.Model):
+ _inherit = 'receivable.modify.line'
+
+ @api.model
+ def get_total_received_report(self, params):
+ """实收总额:查询月当月,应收类型不为押金和违约金的已对金额"""
+ domain = [('reconciliation_date', '!=', False)]
+
+ # 单月日期过滤
+ if params.get('date_start'):
+ query_date = datetime.datetime.fromisoformat(params['date_start']).date()
+ month_start = query_date.replace(day=1)
+ last_day = calendar.monthrange(query_date.year, query_date.month)[1]
+ month_end = query_date.replace(day=last_day)
+ domain += [
+ ('reconciliation_date', '>=', month_start),
+ ('reconciliation_date', '<=', month_end),
+ ]
+
+ # 排除押金和违约金
+ domain += [
+ ('receivable_id.management_type_id.name', 'not in', ['押金', '违约金']),
+ ]
+
+ if params.get('keyword'):
+ keyword = params['keyword']
+ domain += ['|', '|',
+ ('receivable_id.lessee_id.name', 'ilike', keyword),
+ ('receivable_id.property_name', 'ilike', keyword),
+ ('receivable_id.contract_id.contract_code', 'ilike', keyword),
+ ]
+
+ lines = self.search(domain, order='reconciliation_date asc')
+
+ rows = []
+ total_amount = 0.0
+ for idx, line in enumerate(lines, start=1):
+ rec = line.receivable_id
+ contract = rec.contract_id if rec else None
+ amount = round(line.original_amount or 0.0, 2)
+ total_amount += amount
+
+ # 物业穿透
+ property_val = ''
+ if contract and contract.lease_ids and contract.lease_ids[0].address_id:
+ prop = contract.lease_ids[0].address_id
+ property_val = {
+ 'model': 'yuthon.property',
+ 'id': prop.id,
+ 'display_name': rec.property_name or prop.name,
+ }
+ elif rec:
+ property_val = rec.property_name or ''
+
+ # 合同穿透
+ contract_val = ''
+ if contract:
+ contract_val = {
+ 'model': 'property.lease.contract',
+ 'id': contract.id,
+ 'display_name': contract.contract_code or '',
+ }
+
+ # 承租方穿透
+ lessee_val = ''
+ if rec and rec.lessee_id:
+ lessee_val = {
+ 'model': 'property.tenant.information',
+ 'id': rec.lessee_id.id,
+ 'display_name': rec.lessee_id.name,
+ }
+
+ rows.append({
+ '序号': idx,
+ '物业名称': property_val,
+ '合同编号': contract_val,
+ '承租方名称': lessee_val,
+ '金额': amount,
+ '所属公司': contract.lessor_id.name if contract and contract.lessor_id else '',
+ '管理员': contract.admin_id.name if contract and contract.admin_id else '',
+ })
+
+ stats = [{'name': '合计', 'amount': round(total_amount, 2)}]
+ return {'rows': rows, 'stats': stats}
diff --git a/yuthon_report/views/menu.xml b/yuthon_report/views/menu.xml
index b4d3c14af..0a2159aa3 100644
--- a/yuthon_report/views/menu.xml
+++ b/yuthon_report/views/menu.xml
@@ -45,9 +45,9 @@
action="action_month_receivable_report"/>
+ action="action_total_received_report"/>