!11479 报表文字说明

Merge pull request !11479 from 李鹏宇/dev_wang
This commit is contained in:
李鹏宇
2026-05-13 09:46:51 +00:00
committed by Gitee
4 changed files with 430 additions and 41 deletions
@@ -1,3 +1,4 @@
.list_ids_width{
width: 80px !important;
max-width: 80px !important;
@@ -6,45 +7,4 @@
.list_name_width{
width: 300px !important;
max-width: 300px !important;
}
.o_m2o_mutil_line {
white-space: normal !important;
word-break: break-all !important;
word-wrap: break-word !important;
}
/* 覆盖 many2one 下拉选项 */
.o-autocomplete .dropdown-item {
white-space: normal !important;
word-break: break-all !important;
word-wrap: break-word !important;
}
/* 覆盖 select2 下拉选项 */
.select2-container--odoo .select2-results__option {
white-space: normal !important;
word-break: break-all !important;
word-wrap: break-word !important;
}
/* 覆盖选择器弹窗中的表格单元格 */
.modal-dialog .o_list_view .o_list_table td {
white-space: normal !important;
word-break: break-all !important;
word-wrap: break-word !important;
}
/* 覆盖搜索更多弹窗中的表格 */
.o_field_many2one .modal-body .o_list_view td {
white-space: normal !important;
word-break: break-all !important;
word-wrap: break-word !important;
}
/* 覆盖通用的列表视图单元格 */
.o_list_view .o_list_table td {
white-space: normal !important;
word-break: break-all !important;
word-wrap: break-word !important;
}
+287
View File
@@ -0,0 +1,287 @@
# -*- coding: utf-8 -*-
import datetime
import calendar
from odoo import api, models
class TextSummaryReport(models.Model):
_name = 'text.summary.report'
_description = '文字汇总报表'
@api.model
def get_text_summary_report(self, params):
"""
生成文字汇总报表(类似图中的格式)
:param params: 筛选参数(date_start/company_id/partner_id等)
:return: 分组汇总数据
"""
today = datetime.date.today()
# 处理日期参数
date_str = params.get("date_start")
if date_str:
# 支持 YYYY-MM 和 YYYY-MM-DD 两种格式
if len(date_str) == 7: # YYYY-MM 格式
year, month = map(int, date_str.split('-'))
query_date = datetime.date(year, month, 1)
else: # YYYY-MM-DD 格式
query_date = datetime.datetime.fromisoformat(date_str).date()
else:
query_date = today
# 获取筛选条件
company_id = params.get('company_id')
precinct_id = params.get('partner_id')
# 构建报表数据结构
report_data = {
'summary_info': {
'统计时间': f"{query_date.year}-{query_date.month:02d}",
'统计片区': '全部' if not precinct_id else self._get_precinct_name(precinct_id),
'所属公司': '全部' if not company_id else self._get_company_name(company_id),
'创建时间': datetime.date.today().strftime("%Y-%m-%d"),
},
'groups': [
{
'title': '租金累计欠收',
'items': self._get_rent_arrears_data(query_date, company_id, precinct_id)
},
{
'title': '违约金累计欠收',
'items': self._get_penalty_arrears_data(query_date, company_id, precinct_id)
},
{
'title': '本月预收',
'items': self._get_month_precollection_data(query_date, company_id, precinct_id)
},
{
'title': '其他收入',
'items': self._get_other_income_data(query_date, company_id, precinct_id)
},
{
'title': '管理费支出',
'items': self._get_management_fee_data(query_date, company_id, precinct_id)
},
]
}
return report_data
def _get_precinct_name(self, precinct_id):
"""获取片区名称"""
try:
precinct = self.env['res.users'].browse(precinct_id)
return precinct.name if precinct else '未知'
except:
return '未知'
def _get_company_name(self, company_id):
"""获取公司名称"""
try:
company = self.env['yuthon.property.company'].browse(company_id)
return company.name if company else '未知'
except:
return '未知'
def _get_rent_arrears_data(self, query_date, company_id, precinct_id):
"""获取租金累计欠收数据 - 从累计欠收报表获取"""
params = {
'date_start': query_date.strftime("%Y-%m-%d"),
'company_id': company_id,
'partner_id': precinct_id,
}
# 调用累计欠收报表
result = self.env['receivable.modify.line'].get_cum_arrears_report(params)
rows = result.get('rows', [])
items = []
# 按片区分组
area_map = {}
for row in rows:
area = row.get('管理员', {}).get('display_name', '未分配') if isinstance(row.get('管理员'), dict) else row.get('管理员', '未分配')
if area not in area_map:
area_map[area] = []
property_name = row.get('物业名称', {})
if isinstance(property_name, dict):
property_name = property_name.get('display_name', '未命名物业')
amount = row.get('未收金额', 0.0)
area_map[area].append(f"{property_name} 欠收:{amount:.2f}元")
for area, texts in area_map.items():
items.append({
'area': area,
'content': '。'.join(texts[:10]) + '。' # 每个片区最多显示10条
})
return items if items else [{'area': '暂无数据', 'content': '暂无租金累计欠收数据。'}]
def _get_penalty_arrears_data(self, query_date, company_id, precinct_id):
"""获取违约金累计欠收数据 - 从违约金欠收报表获取"""
params = {
'date_start': query_date.strftime("%Y-%m-%d"),
'company_id': company_id,
'partner_id': precinct_id,
}
# 调用违约金欠收报表
result = self.env['property.accounts.receivable'].get_penalty_arrears_report(params)
rows = result.get('rows', [])
items = []
# 按片区分组
area_map = {}
for row in rows:
area = row.get('管理员', {}).get('display_name', '未分配') if isinstance(row.get('管理员'), dict) else row.get('管理员', '未分配')
if area not in area_map:
area_map[area] = []
property_name = row.get('物业名称', {})
if isinstance(property_name, dict):
property_name = property_name.get('display_name', '未命名物业')
amount = row.get('总未收', 0.0)
area_map[area].append(f"{property_name} 违约金欠收:{amount:.2f}元")
for area, texts in area_map.items():
items.append({
'area': area,
'content': '。'.join(texts[:10]) + '。'
})
return items if items else [{'area': '暂无数据', 'content': '暂无违约金累计欠收数据。'}]
def _get_month_precollection_data(self, query_date, company_id, precinct_id):
"""获取本月预收数据 - 从本月预收报表获取"""
params = {
'date_start': query_date.strftime("%Y-%m-%d"),
'company_id': company_id,
'partner_id': precinct_id,
}
# 调用本月预收报表
result = self.env['receivable.modify.line'].get_monthly_precollection_report(params)
rows = result.get('rows', [])
items = []
# 按片区分组
area_map = {}
for row in rows:
area = row.get('管理员', {}).get('display_name', '未分配') if isinstance(row.get('管理员'), dict) else row.get('管理员', '未分配')
if area not in area_map:
area_map[area] = []
property_name = row.get('物业名称', {})
if isinstance(property_name, dict):
property_name = property_name.get('display_name', '未命名物业')
amount = row.get('金额', 0.0)
area_map[area].append(f"{property_name}本月预收:{amount:.2f}元")
for area, texts in area_map.items():
items.append({
'area': area,
'content': '。'.join(texts[:10]) + '。'
})
return items if items else [{'area': '暂无数据', 'content': '暂无本月预收数据。'}]
def _get_other_income_data(self, query_date, company_id, precinct_id):
"""获取其他收入数据 - 从停车费报表获取"""
params = {
'date_start': query_date.strftime("%Y-%m-%d"),
'company_id': company_id,
'partner_id': precinct_id,
}
# 调用停车费本月实收报表
result = self.env['property.accounts.receivable'].get_parking_fee_current_month_report(params)
rows = result.get('rows', [])
items = []
# 按片区分组
area_map = {}
total_parking = 0.0
for row in rows:
area = row.get('管理员', {}).get('display_name', '未分配') if isinstance(row.get('管理员'), dict) else row.get('管理员', '未分配')
if area not in area_map:
area_map[area] = []
property_name = row.get('物业名称', {})
if isinstance(property_name, dict):
property_name = property_name.get('display_name', '未命名物业')
amount = row.get('金额', 0.0)
total_parking += amount
area_map[area].append(f"{property_name}停车费:{amount:.2f}元")
for area, texts in area_map.items():
items.append({
'area': area,
'content': '。'.join(texts[:10]) + '。'
})
# 如果停车费有数据,添加汇总信息
if total_parking > 0:
items.append({
'area': '合计',
'content': f"停车费总收入:{total_parking:.2f}元。"
})
return items if items else [{'area': '暂无数据', 'content': '暂无其他收入数据。'}]
def _get_management_fee_data(self, query_date, company_id, precinct_id):
"""获取管理费支出数据 - 从管理费相关报表获取"""
# 管理费支出需要根据实际业务逻辑查询
# 这里查询管理费类型的应收记录作为支出参考
# 查询管理费类型
management_types = self.env['yuthon.management.type'].search([('name', 'ilike', '管理')])
params = {
'date_start': query_date.strftime("%Y-%m-%d"),
'company_id': company_id,
'partner_id': precinct_id,
'management_type_id': management_types[0].id if management_types else None,
}
# 调用归属本月报表获取管理费数据
result = self.env['receivable.modify.line'].get_current_month_received_report(params)
rows = result.get('rows', [])
items = []
# 按片区分组
area_map = {}
total_fee = 0.0
for row in rows:
area = row.get('管理员', {}).get('display_name', '未分配') if isinstance(row.get('管理员'), dict) else row.get('管理员', '未分配')
if area not in area_map:
area_map[area] = []
property_name = row.get('物业名称', {})
if isinstance(property_name, dict):
property_name = property_name.get('display_name', '未命名物业')
amount = row.get('金额', 0.0)
total_fee += amount
area_map[area].append(f"{property_name}管理费 支出:{amount:.2f}元")
for area, texts in area_map.items():
items.append({
'area': area,
'content': '。'.join(texts[:10]) + '。'
})
# 如果管理费有数据,添加汇总信息
if total_fee > 0:
items.append({
'area': '合计',
'content': f"管理费总支出:{total_fee:.2f}元。"
})
return items if items else [{'area': '暂无数据', 'content': '暂无管理费支出数据。'}]
@@ -0,0 +1,52 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { Component, onMounted, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { standardActionServiceProps } from "@web/webclient/actions/action_service";
class TextSummaryReport extends Component {
setup() {
this.serviceOrm = useService("orm");
this.state = useState({
reportData: null,
dateStart: "",
loading: false,
});
this.config = this.props.action.context.report_config || {};
onMounted(() => {
// 页面加载时自动执行搜索
this.onSearch();
});
}
async onSearch() {
this.state.loading = true;
const params = {
date_start: this.state.dateStart,
};
try {
const result = await this.serviceOrm.call(
this.config.model || 'text.summary.report',
this.config.method || 'get_text_summary_report',
[params]
);
this.state.reportData = result;
} catch (error) {
console.error('报表查询失败:', error);
this.state.reportData = null;
} finally {
this.state.loading = false;
}
}
}
TextSummaryReport.props = { ...standardActionServiceProps };
TextSummaryReport.template = "text_summary_report.TextSummaryReport";
registry.category("actions").add("text_summary_report_client", TextSummaryReport);
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="text_summary_report.TextSummaryReport">
<div class="container-fluid" style="height: calc(100vh - 80px); display: flex; flex-direction: column;">
<!-- 搜索区域 -->
<div class="card mb-3 flex-shrink-0">
<div class="card-body">
<div class="row">
<div class="col-md-2">
<label class="form-label">年月:</label>
<input
type="month"
class="form-control"
t-model="state.dateStart"
/>
</div>
<div class="col-md-10 d-flex align-items-end justify-end">
<button class="btn btn-primary" t-on-click="onSearch">
<span t-if="state.loading" class="fa fa-spinner fa-spin"></span>
查询
</button>
</div>
</div>
</div>
</div>
<!-- 报表信息栏 -->
<div t-if="state.reportData" class="card mb-3 flex-shrink-0">
<div class="card-body">
<div class="row">
<div class="col-md-3">
<span class="text-muted">统计时间:</span>
<span t-esc="state.reportData.summary_info['统计时间']"></span>
</div>
<div class="col-md-3">
<span class="text-muted">统计片区:</span>
<span t-esc="state.reportData.summary_info['统计片区']"></span>
</div>
<div class="col-md-3">
<span class="text-muted">所属公司:</span>
<span t-esc="state.reportData.summary_info['所属公司']"></span>
</div>
<div class="col-md-3 text-right">
<span class="text-muted">创建时间:</span>
<span t-esc="state.reportData.summary_info['创建时间']"></span>
</div>
</div>
</div>
</div>
<!-- 报表内容区域 - 可滚动 -->
<div t-if="state.reportData" class="card flex-grow-1 overflow-auto" style="min-height: 0;">
<div class="card-body">
<t t-foreach="state.reportData.groups" t-as="group" t-key="group_index">
<div class="mb-4">
<div class="bg-danger text-white p-2 mb-2">
<strong t-esc="group.title"></strong>
</div>
<div class="border-left-4 border-danger pl-3">
<t t-foreach="group.items" t-as="item" t-key="item_index">
<div class="mb-2">
<div class="font-weight-bold text-primary" t-esc="item.area"></div>
<div class="text-sm text-gray-700" t-esc="item.content"></div>
</div>
</t>
</div>
</div>
</t>
</div>
</div>
<!-- 加载提示 -->
<div t-if="state.loading" class="flex-grow-1 flex items-center justify-center">
<div class="text-center">
<i class="fa fa-spinner fa-spin fa-3x"></i>
<p>加载中...</p>
</div>
</div>
<!-- 空状态提示 -->
<div t-if="!state.loading &amp;&amp; !state.reportData" class="flex-grow-1 flex items-center justify-center">
<div class="text-center text-muted">
<i class="fa fa-file-text-o fa-3x mb-2"></i>
<p>请点击查询按钮获取报表数据</p>
</div>
</div>
</div>
</t>
</templates>