From 09a0a911ec3f5ddeefc706675478ab50b2d0f855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Wed, 13 May 2026 17:41:09 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=AD=97=E6=AE=B5=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../static/src/scss/property_scss.scss | 42 +------------------ 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/yuthon_property/static/src/scss/property_scss.scss b/yuthon_property/static/src/scss/property_scss.scss index c65e9be32..864cb4082 100644 --- a/yuthon_property/static/src/scss/property_scss.scss +++ b/yuthon_property/static/src/scss/property_scss.scss @@ -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; } \ No newline at end of file From c14fa77e9131980f6ff179c6d5d1f4e9e21c0703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Wed, 13 May 2026 17:45:52 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E6=96=87=E5=AD=97?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_report/models/text_summary_report.py | 287 ++++++++++++++++++ .../yuthon_report/text_summary_report.js | 52 ++++ .../yuthon_report/text_summary_report.xml | 90 ++++++ 3 files changed, 429 insertions(+) create mode 100644 yuthon_report/models/text_summary_report.py create mode 100644 yuthon_report/static/src/components/yuthon_report/text_summary_report.js create mode 100644 yuthon_report/static/src/components/yuthon_report/text_summary_report.xml diff --git a/yuthon_report/models/text_summary_report.py b/yuthon_report/models/text_summary_report.py new file mode 100644 index 000000000..fe00f0664 --- /dev/null +++ b/yuthon_report/models/text_summary_report.py @@ -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': '暂无管理费支出数据。'}] \ No newline at end of file diff --git a/yuthon_report/static/src/components/yuthon_report/text_summary_report.js b/yuthon_report/static/src/components/yuthon_report/text_summary_report.js new file mode 100644 index 000000000..2d842376d --- /dev/null +++ b/yuthon_report/static/src/components/yuthon_report/text_summary_report.js @@ -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); \ No newline at end of file diff --git a/yuthon_report/static/src/components/yuthon_report/text_summary_report.xml b/yuthon_report/static/src/components/yuthon_report/text_summary_report.xml new file mode 100644 index 000000000..af753c0a0 --- /dev/null +++ b/yuthon_report/static/src/components/yuthon_report/text_summary_report.xml @@ -0,0 +1,90 @@ + + + + +
+ +
+
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+
+
+ 统计时间: + +
+
+ 统计片区: + +
+
+ 所属公司: + +
+
+ 创建时间: + +
+
+
+
+ + +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+ + +
+
+ +

加载中...

+
+
+ + +
+
+ +

请点击查询按钮获取报表数据

+
+
+
+
+
\ No newline at end of file