diff --git a/yuthon_report/__manifest__.py b/yuthon_report/__manifest__.py
index 0dd3c1ccd..a7e1bd7ec 100644
--- a/yuthon_report/__manifest__.py
+++ b/yuthon_report/__manifest__.py
@@ -16,6 +16,8 @@
'views/penalty_month_received_report.xml',
'views/penalty_year_cum_report.xml',
'views/deposit_reports.xml',
+ 'views/cum_income_report.xml',
+ 'views/cum_arrears_report.xml',
'views/menu.xml',
'views/ledger_report_views.xml',
'views/receivable_report_views.xml',
diff --git a/yuthon_report/models/__init__.py b/yuthon_report/models/__init__.py
index d2816f2c3..31cf08fe5 100644
--- a/yuthon_report/models/__init__.py
+++ b/yuthon_report/models/__init__.py
@@ -18,6 +18,8 @@ from . import pre_arrears_report
from . import pre_advance_report
from . import month_receivable_report
from . import total_received_report
+from . import cum_income_report
+from . import cum_arrears_report
from . import penalty_arrears_report
from . import penalty_month_received_report
from . import penalty_year_cum_report
diff --git a/yuthon_report/models/cum_arrears_report.py b/yuthon_report/models/cum_arrears_report.py
new file mode 100644
index 000000000..dc7fee57f
--- /dev/null
+++ b/yuthon_report/models/cum_arrears_report.py
@@ -0,0 +1,112 @@
+import datetime
+import calendar
+
+from odoo import api, models
+
+
+class CumArrearsReport(models.Model):
+ _inherit = 'receivable.modify.line'
+
+ @api.model
+ def get_cum_arrears_report(self, params):
+ """
+ 累计欠收报表(数据源:receivable.modify.line):
+ - 状态为挂账(bank_state = 'registered')
+ - 应收开始日期(receivable_id.due_date)小于等于查询月月末
+ - 排除押金和违约金类型
+ - 金额取 not_collected(未收)
+ """
+ today = datetime.date.today()
+
+ date_start = params.get("date_start")
+ if date_start:
+ query_date = datetime.datetime.fromisoformat(date_start).date()
+ else:
+ query_date = today
+
+ # 查询月月末
+ last_day = calendar.monthrange(query_date.year, query_date.month)[1]
+ month_end = query_date.replace(day=last_day)
+
+ domain = [
+ ('bank_state', '=', 'registered'),
+ ('receivable_id.due_date', '<=', month_end),
+ ('receivable_id.is_invalid', '=', False),
+ ]
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('receivable_id.contract_id.contract_code', 'ilike', keyword),
+ ('receivable_id.property_name', 'ilike', keyword),
+ ('receivable_id.lessee_id.name', 'ilike', keyword),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ if condition:
+ domain.append(('receivable_id.tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('receivable_id.precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('receivable_id.company_id', '=', company_id))
+ if management_type_id:
+ domain.append(('receivable_id.management_type_id', '=', management_type_id))
+ else:
+ domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金']))
+
+ lines = self.search(domain, order="receivable_id 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.not_collected 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 ''
+
+ rows.append({
+ "序号": idx,
+ "合同编号": {
+ 'model': 'property.lease.contract',
+ 'id': contract.id,
+ 'display_name': contract.contract_code,
+ } if contract else "",
+ "物业名称": property_val,
+ "承租方名称": {
+ 'model': 'property.tenant.information',
+ 'id': rec.lessee_id.id,
+ 'display_name': rec.lessee_id.name,
+ } if rec and rec.lessee_id else "",
+ "收款类型": rec.management_type_id.name if rec and rec.management_type_id else "",
+ "未收金额": amount,
+ "所属公司": contract.lessor_id.name if contract and contract.lessor_id else "",
+ "管理员": {
+ 'model': 'res.users',
+ 'id': rec.admin_id.id,
+ 'display_name': rec.admin_id.name,
+ } if rec and rec.admin_id else "",
+ })
+
+ stats = [
+ {'name': '记录总数', 'amount': len(rows)},
+ {'name': '累计欠收', 'amount': round(total_amount, 2)},
+ ]
+
+ return {"rows": rows, "stats": stats}
diff --git a/yuthon_report/models/cum_income_report.py b/yuthon_report/models/cum_income_report.py
new file mode 100644
index 000000000..5deb3078e
--- /dev/null
+++ b/yuthon_report/models/cum_income_report.py
@@ -0,0 +1,105 @@
+import datetime
+
+from odoo import api, models
+
+
+class CumIncomeReport(models.Model):
+ _inherit = 'receivable.modify.line'
+
+ @api.model
+ def get_cum_income_report(self, params):
+ """
+ 累计收入报表(数据源:receivable.modify.line):
+ - 按年查询
+ - 取交易金额(original_amount)
+ - 排除押金和违约金类型
+ """
+ today = datetime.date.today()
+ current_year = today.year
+
+ date_start = params.get("date_start")
+ # 按年查询:如果有传值则使用该年份,否则使用当前年份
+ if date_start:
+ year = datetime.datetime.fromisoformat(date_start).year
+ else:
+ year = current_year
+
+ # 构建年份的开始和结束日期
+ year_start = datetime.date(year, 1, 1)
+ year_end = datetime.date(year, 12, 31)
+
+ domain = [
+ ('receivable_id.due_date', '>=', year_start),
+ ('receivable_id.due_date', '<=', year_end),
+ ('receivable_id.is_invalid', '=', False),
+ ]
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('receivable_id.contract_id.contract_code', 'ilike', keyword),
+ ('receivable_id.property_name', 'ilike', keyword),
+ ('receivable_id.lessee_id.name', 'ilike', keyword),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ if condition:
+ domain.append(('receivable_id.tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('receivable_id.precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('receivable_id.company_id', '=', company_id))
+ if management_type_id:
+ domain.append(('receivable_id.management_type_id', '=', management_type_id))
+ else:
+ domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金']))
+
+ lines = self.search(domain, order="receivable_id 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 ''
+
+ rows.append({
+ "序号": idx,
+ "合同编号": {
+ 'model': 'property.lease.contract',
+ 'id': contract.id,
+ 'display_name': contract.contract_code,
+ } if contract else "",
+ "承租方名称": {
+ 'model': 'property.tenant.information',
+ 'id': rec.lessee_id.id,
+ 'display_name': rec.lessee_id.name,
+ } if rec and rec.lessee_id else "",
+ "物业名称": property_val,
+ "金额": amount,
+ })
+
+ stats = [
+ {'name': '记录总数', 'amount': len(rows)},
+ {'name': '累计收入', 'amount': round(total_amount, 2)},
+ ]
+
+ return {"rows": rows, "stats": stats}
diff --git a/yuthon_report/models/monthly_precollection_report.py b/yuthon_report/models/monthly_precollection_report.py
index 1f78e0334..7af723cd2 100644
--- a/yuthon_report/models/monthly_precollection_report.py
+++ b/yuthon_report/models/monthly_precollection_report.py
@@ -1,90 +1,121 @@
import datetime
+import calendar
from odoo import api, models
class MonthlyPrecollectionReport(models.Model):
- _inherit = 'property.accounts.receivable'
+ _inherit = 'receivable.modify.line'
@api.model
def get_monthly_precollection_report(self, params):
- """汇总当前筛选条件下的应收数据(本月预收视图)。"""
- domain = []
+ """
+ 本月预收报表(数据源:receivable.modify.line):
+ - 应收开始日期(receivable_id.due_date)在查询月当月(月初 ~ 月末)
+ - delivered1 > original_amount(已交超过当期金额,即有预收)
+ - 应收账单非作废:receivable_id.is_invalid = False
+ - 排除押金和违约金类型
+ - 金额取 delivered1 - original_amount(预收金额)
+ """
+ today = datetime.date.today()
+
+ date_start = params.get("date_start")
+ if date_start:
+ query_date = datetime.datetime.fromisoformat(date_start).date()
+ else:
+ query_date = today
+
+ # 查询月月初和月末
+ 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 = [
+ ('receivable_id.due_date', '>=', month_start),
+ ('receivable_id.due_date', '<=', month_end),
+ ('receivable_id.delivered1', '>', 0),
+ ('receivable_id.is_invalid', '=', False),
+ ]
keyword = params.get("keyword")
if keyword:
domain += [
'|', '|',
- ('lessee_id.name', 'ilike', keyword),
- ('property_name', 'ilike', keyword),
- ('title_deed', 'ilike', keyword),
+ ('receivable_id.contract_id.contract_code', 'ilike', keyword),
+ ('receivable_id.property_name', 'ilike', keyword),
+ ('receivable_id.lessee_id.name', 'ilike', keyword),
]
- date_start = params.get("date_start")
- date_end = params.get("date_end")
- if date_start:
- start_dt = datetime.datetime.fromisoformat(date_start).date()
- domain.append(("due_date", ">=", start_dt))
- if date_end:
- end_dt = datetime.datetime.fromisoformat(date_end).date()
- domain.append(("due_date", "<=", end_dt))
-
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
management_type_id = params.get('management_type_id')
if condition:
- domain.append(('tenure_type', '=', condition))
+ domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
- domain.append(('precinct_id', '=', partner_id))
+ domain.append(('receivable_id.precinct_id', '=', partner_id))
if company_id:
- domain.append(('company_id', '=', company_id))
+ domain.append(('receivable_id.company_id', '=', company_id))
if management_type_id:
- domain.append(('management_type_id', '=', management_type_id))
+ domain.append(('receivable_id.management_type_id', '=', management_type_id))
+ else:
+ domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金']))
- receivables = self.search(domain, limit=1000, order="due_date asc")
+ lines = self.search(domain, order="receivable_id asc")
- result = []
+ rows = []
total_amount = 0.0
- for index, rec in enumerate(receivables, start=1):
- amount = rec.original_amount or 0.0
+ idx = 1
+ for line in lines:
+ rec = line.receivable_id
+ contract = rec.contract_id if rec else None
+ # 预收金额 = 已交 - 当期金额,必须 > 0 才是真正预收
+ amount = round((rec.delivered1 or 0.0) - (rec.original_amount or 0.0), 2)
+ if amount <= 0:
+ continue
total_amount += amount
- result.append({
- "序号": index,
+
+ # 物业穿透
+ 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 ''
+
+ rows.append({
+ "序号": idx,
"合同编号": {
'model': 'property.lease.contract',
- 'id': rec.contract_id.id,
- 'display_name': rec.contract_id.contract_code,
- } if rec.contract_id else "",
- "物业名称": {
- 'model': 'yuthon.property',
- 'id': rec.contract_id.lease_ids[0].address_id.id,
- 'display_name': rec.property_name,
- } if rec.contract_id and rec.contract_id.lease_ids and rec.contract_id.lease_ids[0].address_id else rec.property_name or "",
+ 'id': contract.id,
+ 'display_name': contract.contract_code,
+ } if contract else "",
+ "物业名称": property_val,
"承租方名称": {
'model': 'property.tenant.information',
'id': rec.lessee_id.id,
'display_name': rec.lessee_id.name,
- } if rec.lessee_id else "",
- "金额": round(amount, 2),
- "所属公司": (
- rec.contract_id.lessor_id.referred
- or rec.contract_id.lessor_id.name
- if rec.contract_id and rec.contract_id.lessor_id
- else ""
- ),
+ } if rec and rec.lessee_id else "",
+ "收款类型": rec.management_type_id.name if rec and rec.management_type_id else "",
+ "金额": amount,
+ "所属公司": contract.lessor_id.name if contract and contract.lessor_id else "",
"管理员": {
'model': 'res.users',
- 'id': rec.contract_id.admin_id.id,
- 'display_name': rec.contract_id.admin_id.name,
- } if rec.contract_id and rec.contract_id.admin_id else "",
+ 'id': rec.admin_id.id,
+ 'display_name': rec.admin_id.name,
+ } if rec and rec.admin_id else "",
})
+ idx += 1
- return {
- "rows": result,
- "stats": [
- {"name": "应收总额", "amount": round(total_amount, 2)},
- ],
- }
+ stats = [
+ {'name': '合同总数', 'amount': len(rows)},
+ {'name': '预收总额', 'amount': round(total_amount, 2)},
+ ]
+
+ return {"rows": rows, "stats": stats}
diff --git a/yuthon_report/models/pre_advance_report.py b/yuthon_report/models/pre_advance_report.py
index 2e694625a..51c740a31 100644
--- a/yuthon_report/models/pre_advance_report.py
+++ b/yuthon_report/models/pre_advance_report.py
@@ -11,11 +11,10 @@ class PreAdvanceReport(models.Model):
"""
前期预收报表(数据源:receivable.modify.line):
- 应收开始日期(receivable_id.due_date)在查询月最后一天之后(未来应收)
- - 应收账单未收金额(receivable_id.submitted)= 0(已全额收款)
- - original_amount > 0(有交易金额)
+ - delivered1 > original_amount(已交超过当期金额,即有预收)
- 应收账单非作废:receivable_id.is_invalid = False
- 排除押金和违约金类型
- - 金额取 original_amount(交易金额)
+ - 金额取 delivered1 - original_amount(预收金额)
"""
today = datetime.date.today()
if today.month == 12:
@@ -30,8 +29,7 @@ class PreAdvanceReport(models.Model):
domain = [
('receivable_id.due_date', '>', cutoff_date),
- ('receivable_id.submitted', '=', 0),
- ('receivable_id.original_amount', '>', 0),
+ ('receivable_id.delivered1', '>', 0),
('receivable_id.is_invalid', '=', False),
]
@@ -64,10 +62,14 @@ class PreAdvanceReport(models.Model):
rows = []
total_amount = 0.0
- for idx, line in enumerate(lines, start=1):
+ idx = 1
+ for line in lines:
rec = line.receivable_id
contract = rec.contract_id if rec else None
- amount = round(rec.original_amount or 0.0, 2)
+ # 预收金额 = 已交 - 当期金额,必须 > 0 才是真正预收
+ amount = round((rec.delivered1 or 0.0) - (rec.original_amount or 0.0), 2)
+ if amount <= 0:
+ continue
total_amount += amount
# 物业穿透
@@ -103,6 +105,7 @@ class PreAdvanceReport(models.Model):
'display_name': rec.admin_id.name,
} if rec and rec.admin_id else "",
})
+ idx += 1
stats = [
{'name': '合同总数', 'amount': len(rows)},
diff --git a/yuthon_report/views/cum_arrears_report.xml b/yuthon_report/views/cum_arrears_report.xml
new file mode 100644
index 000000000..21bce27e6
--- /dev/null
+++ b/yuthon_report/views/cum_arrears_report.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ 累计欠收
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "receivable.modify.line",
+ "method": "get_cum_arrears_report",
+ "dateMode": "single",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "width": 45, "flex": 0, "pinned": "left"},
+ {"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2.5},
+ {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.4},
+ {"headerName": "收款类型", "field": "收款类型", "flex": 1},
+ {"headerName": "未收金额", "field": "未收金额", "flex": 1, "type": "number"},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
diff --git a/yuthon_report/views/cum_income_report.xml b/yuthon_report/views/cum_income_report.xml
new file mode 100644
index 000000000..a2916dbf3
--- /dev/null
+++ b/yuthon_report/views/cum_income_report.xml
@@ -0,0 +1,8 @@
+
+
+
+ 累计收入
+ receivable_report_client
+ {"report_config": {"model": "receivable.modify.line", "method": "get_cum_income_report", "columns": [{"headerName": "序号", "field": "序号", "width": 80, "flex": 0}, {"headerName": "合同编号", "field": "合同编号", "flex": 1}, {"headerName": "承租方名称", "field": "承租方名称", "flex": 1}, {"headerName": "物业名称", "field": "物业名称", "flex": 1}, {"headerName": "金额", "field": "金额", "flex": 1}]}}
+
+
diff --git a/yuthon_report/views/menu.xml b/yuthon_report/views/menu.xml
index b3f2a28c6..3e08126a8 100644
--- a/yuthon_report/views/menu.xml
+++ b/yuthon_report/views/menu.xml
@@ -62,12 +62,12 @@
sequence="8"
name="累计欠收"
parent="yuthon_property_income_menu"
- action="action_report_placeholder"/>
+ action="action_cum_arrears_report"/>
+ action="action_cum_income_report"/>