diff --git a/yuthon_report/models/receivable_flow_report.py b/yuthon_report/models/receivable_flow_report.py index c90c7ffd2..8357276f1 100644 --- a/yuthon_report/models/receivable_flow_report.py +++ b/yuthon_report/models/receivable_flow_report.py @@ -89,44 +89,33 @@ class ReceivableFlowReport(models.Model): # ===== 预加载结束 ===== rows = [] + seq = 0 - for idx, rec in enumerate(receivables, start=1): - # 获取收款信息(从预加载字典取) - modify_lines = modify_map.get(rec.id, []) - - # 计算收款总金额 - total_collected = sum(line.amount for line in modify_lines) - - # 获取最新的收款时间和备注 - latest_payment = sorted( - [l for l in modify_lines if l.reconciliation_date], - key=lambda l: l.reconciliation_date, reverse=True + for rec in receivables: + # 获取收款明细(按对账日期升序) + modify_lines = sorted( + modify_map.get(rec.id, []), + key=lambda l: l.reconciliation_date or datetime.date.min ) - payment_date = latest_payment[0].reconciliation_date if latest_payment else None - payment_remark = latest_payment[0].bank_id.remake if latest_payment and latest_payment[0].bank_id else "" - - # 获取收款金额(最近一次) - payment_amount = latest_payment[0].amount if latest_payment else 0.0 - + + # 收款汇总 + total_collected = sum(line.amount for line in modify_lines) + # 获取状态 charge_status = "未收" if rec.submitted == 0: charge_status = "已收" - - # 从 penalty.line.wizard 取违约金信息(唯一数据源) + + # 违约金信息 wz_list = wizard_map.get(rec.id, []) - - # 违约金字段:有 wizard 才显示,否则全为空/0 billing_period = "" days_diff = 0 penalty_amount = 0.0 if wz_list: wz = wz_list[0] - # 直接从 penalty_line_ids 手动汇总,绕开 compute 缓存问题 pl_all = list(wz.penalty_line_ids) days_diff = sum(pl.days_overdue for pl in pl_all) penalty_amount = round(sum(pl.penalty for pl in pl_all), 2) - # 计算时间:取 penalty > 0 的明细行,从每行 remake 解析起止日期 penalty_lines = [pl for pl in pl_all if pl.penalty > 0] if penalty_lines: all_dates = [] @@ -141,46 +130,19 @@ class ReceivableFlowReport(models.Model): penalty_pay_lines = [] for wz in wz_list: penalty_pay_lines.extend(pay_line_map.get(wz.id, [])) - - # 计算违约金收款金额 total_penalty_collected = sum(line.amount for line in penalty_pay_lines) - - # 获取最新的违约金收款时间 latest_penalty_payment = sorted( [l for l in penalty_pay_lines if l.date], key=lambda l: l.date, reverse=True ) penalty_payment_date = latest_penalty_payment[0].date if latest_penalty_payment else None - + + seq += 1 + # ==== 应收主行 ==== rows.append({ - "序号": idx, + "row_type": "主", + "序号": seq, "租金id": rec.id, - "应收日期": rec.due_date.strftime("%Y-%m-%d") if rec.due_date else "", - "应收金额": { - '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 "", - "收款备注": payment_remark, - "状态": charge_status, - "违约金明细": { - 'model': 'property.accounts.receivable', - 'display_name': '违约金明细', - 'method': 'penalty_wizard', - 'id': rec.id, - }, - "违约金收款时间": penalty_payment_date.strftime("%Y-%m-%d") if penalty_payment_date else "", - "违约金收款金额": round(total_penalty_collected, 2), "合同编号": { 'model': 'property.lease.contract', 'id': rec.contract_id.id if rec.contract_id else None, @@ -196,13 +158,63 @@ class ReceivableFlowReport(models.Model): 'id': rec.lessee_id.id if rec.lessee_id else None, 'display_name': rec.lessee_id.name if rec.lessee_id else "", } if rec.lessee_id else "", + "应收日期": rec.due_date.strftime("%Y-%m-%d") if rec.due_date else "", + "应收金额": { + '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), + "状态": charge_status, + "收款日期": "", + "收款金额": "", + "收款备注": "", + "违约金明细": { + 'model': 'property.accounts.receivable', + 'display_name': '违约金明细', + 'method': 'penalty_wizard', + 'id': rec.id, + }, + "违约金收款时间": penalty_payment_date.strftime("%Y-%m-%d") if penalty_payment_date else "", + "违约金收款金额": round(total_penalty_collected, 2), }) + # ==== 每笔收款展开为子行 ==== + for ml in modify_lines: + rows.append({ + "row_type": "收款", + "序号": "", + "租金id": rec.id, + "合同编号": "", + "物业名称": "", + "承租方名称": "", + "应收日期": "", + "应收金额": "", + "收款总金额": "", + "未收": "", + "状态": "", + "收款日期": ml.reconciliation_date.strftime("%Y-%m-%d") if ml.reconciliation_date else "", + "收款金额": round(ml.amount, 2), + "收款备注": ml.bank_id.remake if ml.bank_id else "", + "违约金明细": "", + "违约金收款时间": "", + "违约金收款金额": "", + }) + + + main_rows = [r for r in rows if r.get('row_type') == '主'] stats = [ - {'name': '记录总数', 'amount': len(rows)}, - {'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)}, + {'name': '记录总数', 'amount': len(main_rows)}, + {'name': '应收总额', 'amount': round(sum(float(row["应收金额"]['display_name']) for row in main_rows), 2)}, + {'name': '已收总额', 'amount': round(sum(float(row["收款总金额"]['display_name']) for row in main_rows), 2)}, + {'name': '未收总额', 'amount': round(sum(row["未收"] for row in main_rows), 2)}, ] return {"rows": rows, "stats": stats} 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 bd9105bc5..7f09d547d 100644 --- a/yuthon_report/static/src/components/yuthon_report/receivable_report.js +++ b/yuthon_report/static/src/components/yuthon_report/receivable_report.js @@ -73,6 +73,9 @@ class ReceivableReport extends Component { rowClassRules: { 'ag-row-new-rent': function(params) { return params.data && params.data.acc_type === 'new'; + }, + 'ag-row-payment-line': function(params) { + return params.data && params.data.row_type === '收款'; } }, sideBar: { @@ -126,11 +129,13 @@ class ReceivableReport extends Component { orm: this.serviceOrm, // 传递 orm 用于调用模型方法 }, onFilterChanged: () => { - // 过滤后重新编号序号列 + // 过滤后重新编号序号列(仅主行编号) let num = 1; this.grid.forEachNodeAfterFilter((node) => { - if (node.data) { + if (node.data && node.data['row_type'] === '主') { node.data['序号'] = num++; + } else if (node.data && node.data['row_type'] === '收款') { + node.data['序号'] = ''; } }); this.grid.refreshCells({ columns: ['序号'], force: true }); diff --git a/yuthon_report/static/src/components/yuthon_report/yuthon_report.scss b/yuthon_report/static/src/components/yuthon_report/yuthon_report.scss index 62c3f0931..721433cde 100644 --- a/yuthon_report/static/src/components/yuthon_report/yuthon_report.scss +++ b/yuthon_report/static/src/components/yuthon_report/yuthon_report.scss @@ -35,4 +35,11 @@ background-color: #d4edda !important; color: #155724 !important; font-weight: bold; + } + + /* 收款子行样式 */ + .ag-row-payment-line { + background-color: #f0f7ff !important; + color: #1a56db !important; + font-style: italic; } \ No newline at end of file diff --git a/yuthon_report/views/receivable_flow_report.xml b/yuthon_report/views/receivable_flow_report.xml index 36cb96256..4753f7037 100644 --- a/yuthon_report/views/receivable_flow_report.xml +++ b/yuthon_report/views/receivable_flow_report.xml @@ -13,17 +13,17 @@ "columns": [ {"headerName": "序号", "field": "序号", "width": 45, "flex": 0, "pinned": "left"}, {"headerName": "租金id", "field": "租金id", "flex": 1}, - {"headerName": "合同编号", "field": "合同编号", "flex": 1.2}, + {"headerName": "合同编号", "field": "合同编号", "flex": 1.2, "cellRenderer": "linkRender"}, {"headerName": "物业名称", "field": "物业名称", "flex": 2, "cellRenderer": "linkRender"}, - {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.4}, + {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.4, "cellRenderer": "linkRender"}, {"headerName": "应收日期", "field": "应收日期", "flex": 1}, {"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}, - {"headerName": "收款备注", "field": "收款备注", "flex": 2, "wrapText": true}, {"headerName": "状态", "field": "状态", "flex": 1}, + {"headerName": "收款日期", "field": "收款日期", "flex": 1}, + {"headerName": "收款金额", "field": "收款金额", "flex": 1, "type": "number"}, + {"headerName": "收款备注", "field": "收款备注", "flex": 2, "wrapText": true}, {"headerName": "违约金明细", "field": "违约金明细", "flex": 1, "cellRenderer": "linkRender"}, {"headerName": "违约金收款时间", "field": "违约金收款时间", "flex": 1}, {"headerName": "违约金收款金额", "field": "违约金收款金额", "flex": 1, "type": "number"}