@@ -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;
|
||||
}
|
||||
@@ -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 && !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>
|
||||
Reference in New Issue
Block a user