Date: Sat, 21 Mar 2026 19:47:55 +0800
Subject: [PATCH 16/16] =?UTF-8?q?=E6=8A=A5=E8=A1=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../models/receivable_flow_report.py | 40 +++++++++++--
.../yuthon_report/receivable_report.js | 58 +++++++++++++++----
.../yuthon_report/receivable_report.xml | 39 +++++++++++++
.../views/receivable_flow_report.xml | 8 +--
4 files changed, 125 insertions(+), 20 deletions(-)
diff --git a/yuthon_report/models/receivable_flow_report.py b/yuthon_report/models/receivable_flow_report.py
index e09235fb5..0d1fd9ce7 100644
--- a/yuthon_report/models/receivable_flow_report.py
+++ b/yuthon_report/models/receivable_flow_report.py
@@ -36,6 +36,9 @@ class ReceivableFlowReport(models.Model):
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
+ property_id = params.get('property_id')
+ lessee_id = params.get('lessee_id')
+ contract_id = params.get('contract_id')
if condition:
domain.append(('tenure_type', '=', condition))
@@ -43,6 +46,12 @@ class ReceivableFlowReport(models.Model):
domain.append(('precinct_id', '=', partner_id))
if company_id:
domain.append(('company_id', '=', company_id))
+ if property_id:
+ domain.append(('property_ids', 'in', [property_id]))
+ if lessee_id:
+ domain.append(('lessee_id', '=', lessee_id))
+ if contract_id:
+ domain.append(('contract_id', '=', contract_id))
receivables = self.sudo().search(domain, order="id asc")
rec_ids = receivables.ids
@@ -209,8 +218,18 @@ class ReceivableFlowReport(models.Model):
"序号": idx,
"租金id": rec.id,
"应收日期": rec.due_date.strftime("%Y-%m-%d") if rec.due_date else "",
- "应收金额": round(rec.original_amount, 2),
- "收款总金额": round(total_collected, 2),
+ "应收金额": {
+ 'model': 'property.accounts.receivable',
+ 'display_name': str(round(rec.original_amount, 2)),
+ 'view_mode': 'tree',
+ 'domain': [('id', '=', rec.id)],
+ },
+ "收款总金额": {
+ 'model': 'receivable.modify.line',
+ 'display_name': str(round(total_collected, 2)),
+ 'view_mode': 'tree',
+ 'domain': [('receivable_id', '=', rec.id), ('reconciliation_date', '!=', False)],
+ },
"未收": round(rec.submitted, 2),
"收款金额": round(payment_amount, 2),
"收款时间": payment_date.strftime("%Y-%m-%d") if payment_date else "",
@@ -218,7 +237,12 @@ class ReceivableFlowReport(models.Model):
"状态": charge_status,
"违约金计算时间": billing_period,
"违约天数": days_diff,
- "违约金金额": round(penalty_amount, 2),
+ "违约金金额": {
+ 'model': 'property.accounts.receivable',
+ 'display_name': str(round(penalty_amount, 2)),
+ 'view_mode': 'tree',
+ 'domain': [('id', '=', rec.id)],
+ },
"违约金收款时间": penalty_payment_date.strftime("%Y-%m-%d") if penalty_payment_date else "",
"违约金收款金额": round(total_penalty_collected, 2),
"合同编号": {
@@ -226,7 +250,11 @@ class ReceivableFlowReport(models.Model):
'id': rec.contract_id.id if rec.contract_id else None,
'display_name': rec.contract_id.contract_code if rec.contract_id else "",
} if rec.contract_id else "",
- "物业名称": rec.property_name or "",
+ "物业名称": {
+ 'model': 'yuthon.property',
+ 'id': rec.contract_id.lease_ids[0].address_id.id if rec.contract_id and rec.contract_id.lease_ids and rec.contract_id.lease_ids[0].address_id else None,
+ 'display_name': rec.property_name or "",
+ } if rec.contract_id and rec.contract_id.lease_ids and rec.contract_id.lease_ids[0].address_id else rec.property_name or "",
"承租方名称": {
'model': 'property.tenant.information',
'id': rec.lessee_id.id if rec.lessee_id else None,
@@ -236,8 +264,8 @@ class ReceivableFlowReport(models.Model):
stats = [
{'name': '记录总数', 'amount': len(rows)},
- {'name': '应收总额', 'amount': round(sum(row["应收金额"] for row in rows), 2)},
- {'name': '已收总额', 'amount': round(sum(row["收款总金额"] for row in rows), 2)},
+ {'name': '应收总额', 'amount': round(sum(float(row["应收金额"]['display_name']) for row in rows), 2)},
+ {'name': '已收总额', 'amount': round(sum(float(row["收款总金额"]['display_name']) for row in rows), 2)},
{'name': '未收总额', 'amount': round(sum(row["未收"] for row in rows), 2)},
]
diff --git a/yuthon_report/static/src/components/yuthon_report/receivable_report.js b/yuthon_report/static/src/components/yuthon_report/receivable_report.js
index 284006f51..4224bad25 100644
--- a/yuthon_report/static/src/components/yuthon_report/receivable_report.js
+++ b/yuthon_report/static/src/components/yuthon_report/receivable_report.js
@@ -33,6 +33,9 @@ class ReceivableReport extends Component {
selectPartner: {},
selectCompany: {},
selectManagementType: {},
+ selectProperty: {},
+ selectLessee: {},
+ selectContract: {},
condition: false,
conditions: [
{'name': '', 'value': ''},
@@ -142,13 +145,15 @@ class ReceivableReport extends Component {
* 穿透渲染器:将对象格式的字段渲染为可点击的链接
* 如果字段值是对象(包含 model、id、display_name),则渲染为可点击链接
* 否则直接显示原值
+ * 支持 view_mode: 'tree' + domain 打开 tree 弹窗
+ * 支持 view_mode: 'form' 或默认打开 form 弹窗
*/
linkRender(params) {
if (!params.value) return '';
- // 如果是对象格式(包含 model 和 id),渲染为可点击链接
- if (typeof params.value === 'object' && params.value.model && params.value.id) {
- const { model, id, display_name } = params.value;
+ // 如果是对象格式(包含 model),渲染为可点击链接
+ if (typeof params.value === 'object' && params.value.model) {
+ const { model, id, display_name, view_mode, domain, view_id } = params.value;
const el = document.createElement('div');
el.className = 'o_record_cell';
@@ -156,13 +161,25 @@ class ReceivableReport extends Component {
el.classList.add('btn');
el.classList.add('btn-link');
el.onclick = () => {
- params.context.actionService.doAction({
- type: 'ir.actions.act_window',
- res_model: model,
- res_id: id,
- views: [[false, 'form'], [false, 'search']],
- target: 'new'
- });
+ if (view_mode === 'tree') {
+ // tree 弹窗:用 domain 过滤多条记录
+ params.context.actionService.doAction({
+ type: 'ir.actions.act_window',
+ res_model: model,
+ views: [[view_id || false, 'tree']],
+ domain: domain || [],
+ target: 'new',
+ });
+ } else {
+ // form 弹窗:打开单条记录
+ params.context.actionService.doAction({
+ type: 'ir.actions.act_window',
+ res_model: model,
+ res_id: id,
+ views: [[false, 'form'], [false, 'search']],
+ target: 'new'
+ });
+ }
};
return el;
@@ -292,6 +309,9 @@ class ReceivableReport extends Component {
partner_id: this.state.selectPartner?.id,
company_id: this.state.selectCompany?.id,
management_type_id: this.state.selectManagementType?.id || false,
+ property_id: this.state.selectProperty?.id || false,
+ lessee_id: this.state.selectLessee?.id || false,
+ contract_id: this.state.selectContract?.id || false,
};
} else {
let startDate = formatDate(this.state.startDate, {format: "yyyy-MM-dd"})
@@ -304,6 +324,9 @@ class ReceivableReport extends Component {
partner_id: this.state.selectPartner?.id,
company_id: this.state.selectCompany?.id,
management_type_id: this.state.selectManagementType?.id || false,
+ property_id: this.state.selectProperty?.id || false,
+ lessee_id: this.state.selectLessee?.id || false,
+ contract_id: this.state.selectContract?.id || false,
};
}
const result = await this.serviceOrm.call(
@@ -376,6 +399,21 @@ class ReceivableReport extends Component {
this._applyManagementTypeFilter();
}
+ async onUpdateProperty(v) {
+ this.state.selectProperty = v[0] || {};
+ await this.onSearch();
+ }
+
+ async onUpdateLessee(v) {
+ this.state.selectLessee = v[0] || {};
+ await this.onSearch();
+ }
+
+ async onUpdateContract(v) {
+ this.state.selectContract = v[0] || {};
+ await this.onSearch();
+ }
+
_applyManagementTypeFilter() {
if (!this.grid) return;
const typeName = this.state.selectManagementType?.display_name || '';
diff --git a/yuthon_report/static/src/components/yuthon_report/receivable_report.xml b/yuthon_report/static/src/components/yuthon_report/receivable_report.xml
index cc576b665..9229e82a8 100644
--- a/yuthon_report/static/src/components/yuthon_report/receivable_report.xml
+++ b/yuthon_report/static/src/components/yuthon_report/receivable_report.xml
@@ -109,6 +109,45 @@
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/receivable_flow_report.xml b/yuthon_report/views/receivable_flow_report.xml
index 4cff46fa9..7f3f08c3a 100644
--- a/yuthon_report/views/receivable_flow_report.xml
+++ b/yuthon_report/views/receivable_flow_report.xml
@@ -14,11 +14,11 @@
{"headerName": "序号", "field": "序号", "width": 45, "flex": 0, "pinned": "left"},
{"headerName": "租金id", "field": "租金id", "flex": 1},
{"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
- {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2, "cellRenderer": "linkRender"},
{"headerName": "承租方名称", "field": "承租方名称", "flex": 1.4},
{"headerName": "应收日期", "field": "应收日期", "flex": 1},
- {"headerName": "应收金额", "field": "应收金额", "flex": 1, "type": "number"},
- {"headerName": "收款总金额", "field": "收款总金额", "flex": 1, "type": "number"},
+ {"headerName": "应收金额", "field": "应收金额", "flex": 1, "type": "number", "cellRenderer": "linkRender"},
+ {"headerName": "收款总金额", "field": "收款总金额", "flex": 1, "type": "number", "cellRenderer": "linkRender"},
{"headerName": "未收", "field": "未收", "flex": 1, "type": "number"},
{"headerName": "收款金额", "field": "收款金额", "flex": 1, "type": "number"},
{"headerName": "收款时间", "field": "收款时间", "flex": 1},
@@ -26,7 +26,7 @@
{"headerName": "状态", "field": "状态", "flex": 1},
{"headerName": "违约金计算时间", "field": "违约金计算时间", "flex": 2.5, "wrapText": true},
{"headerName": "违约天数", "field": "违约天数", "flex": 1, "type": "number"},
- {"headerName": "违约金金额", "field": "违约金金额", "flex": 1, "type": "number"},
+ {"headerName": "违约金金额", "field": "违约金金额", "flex": 1, "type": "number", "cellRenderer": "linkRender"},
{"headerName": "违约金收款时间", "field": "违约金收款时间", "flex": 1},
{"headerName": "违约金收款金额", "field": "违约金收款金额", "flex": 1, "type": "number"}
]