diff --git a/yuthon_official/views/yuthon_file_processing_views.xml b/yuthon_official/views/yuthon_file_processing_views.xml
index bf86c2658..21208adef 100644
--- a/yuthon_official/views/yuthon_file_processing_views.xml
+++ b/yuthon_official/views/yuthon_file_processing_views.xml
@@ -88,8 +88,7 @@
-
-
+
diff --git a/yuthon_official/views/yuthon_official_views.xml b/yuthon_official/views/yuthon_official_views.xml
index 035b5d3eb..b325ddc74 100644
--- a/yuthon_official/views/yuthon_official_views.xml
+++ b/yuthon_official/views/yuthon_official_views.xml
@@ -96,8 +96,7 @@
-
-
+
diff --git a/yuthon_property_approval/views/property_lease_contract.xml b/yuthon_property_approval/views/property_lease_contract.xml
index 287263b80..b5c410f01 100644
--- a/yuthon_property_approval/views/property_lease_contract.xml
+++ b/yuthon_property_approval/views/property_lease_contract.xml
@@ -96,8 +96,7 @@
-
-
+
diff --git a/yuthon_report/models/cumulative_arrears_report.py b/yuthon_report/models/cumulative_arrears_report.py
new file mode 100644
index 000000000..94b7e7715
--- /dev/null
+++ b/yuthon_report/models/cumulative_arrears_report.py
@@ -0,0 +1,88 @@
+import datetime
+
+from odoo import api, models
+
+
+class CumulativeArrearsReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_cumulative_arrears_report(self, params):
+ """
+ 累计欠收报表:
+ - 依据应收日期过滤(欠租所属月份)
+ - 金额:月租金取合同租金(contract_id.rent),欠租金额取 submitted,面积取合同 total_area
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ if date_start:
+ start_dt = datetime.datetime.fromisoformat(date_start).date()
+ domain.append(("due_date", ">=", start_dt))
+ if date_end:
+ end_dt = datetime.datetime.fromisoformat(date_end).date()
+ domain.append(("due_date", "<=", end_dt))
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+ if management_type_id:
+ domain.append(('management_type_id', '=', management_type_id))
+
+ receivables = self.search(domain, limit=3000, order="due_date asc")
+
+ rows = []
+ total_arrears = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ contract = rec.contract_id
+ rent = contract.rent or rec.original_amount or 0.0
+ arrears = rec.submitted or 0.0
+ total_arrears += arrears
+
+ rows.append({
+ "序号": idx,
+ "物业名称": rec.property_name or "",
+ "面积": contract.total_area if contract and contract.total_area else "",
+ "承租方名称": rec.lessee_id.name if rec.lessee_id else "",
+ "合同编号": contract.contract_code if contract else "",
+ "月租金": round(rent, 2),
+ "欠租金额": round(arrears, 2),
+ "欠租所属月份": rec.due_date.strftime("%Y-%m-%d") if rec.due_date else "",
+ "所属公司": contract.lessor_id.referred if contract and contract.lessor_id and hasattr(contract.lessor_id, 'referred') else contract.lessor_id.name if contract and contract.lessor_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "物业名称": False,
+ "面积": False,
+ "承租方名称": False,
+ "合同编号": False,
+ "月租金": False,
+ "欠租金额": round(total_arrears, 2),
+ "欠租所属月份": False,
+ "所属公司": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/current_month_received_report.py b/yuthon_report/models/current_month_received_report.py
new file mode 100644
index 000000000..8ebc14710
--- /dev/null
+++ b/yuthon_report/models/current_month_received_report.py
@@ -0,0 +1,92 @@
+import datetime
+
+from odoo import api, models
+
+
+class CurrentMonthReceivedReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_current_month_received_report(self, params):
+ """归属本月实收报表:按收款日期筛选已收金额。"""
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else None
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else None
+ if start_dt and end_dt:
+ # 优先按收款日期过滤;若收款日期为空,则按应收日期兜底
+ domain += [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), '&', ('due_date', '>=', start_dt), ('due_date', '<=', end_dt)
+ ]
+ elif start_dt:
+ domain += [
+ '|',
+ ('payout_date', '>=', start_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt)
+ ]
+ elif end_dt:
+ domain += [
+ '|',
+ ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '<=', end_dt)
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+ if management_type_id:
+ domain.append(('management_type_id', '=', management_type_id))
+
+ receivables = self.search(domain, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.delivered1 or 0.0 # 已收金额
+ total_amount += amount
+
+ rows.append({
+ "序号": idx,
+ "合同编号": rec.contract_id.contract_code if rec.contract_id else "",
+ "物业名称": rec.property_name or "",
+ "承租方名称": rec.lessee_id.name if rec.lessee_id else "",
+ "金额": round(amount, 2),
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "合同编号": "合计",
+ "物业名称": False,
+ "承租方名称": False,
+ "金额": round(total_amount, 2),
+ "所属公司": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/current_receivable_property_fee_report.py b/yuthon_report/models/current_receivable_property_fee_report.py
new file mode 100644
index 000000000..374fcd0b8
--- /dev/null
+++ b/yuthon_report/models/current_receivable_property_fee_report.py
@@ -0,0 +1,112 @@
+import datetime
+
+from odoo import api, models
+
+
+class CurrentReceivablePropertyFeeReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_current_receivable_property_fee_report(self, params):
+ """
+ 当前应收-物管水电费:
+ - 按应收日期过滤
+ - 默认管理类型名称包含“管”或“水”或“电”,可通过 management_type_id 精确指定
+ - 含税金额:original_amount
+ - 不含税金额:original_amount / (1 + tax/100)
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ if date_start:
+ start_dt = datetime.datetime.fromisoformat(date_start).date()
+ domain.append(("due_date", ">=", start_dt))
+ if date_end:
+ end_dt = datetime.datetime.fromisoformat(date_end).date()
+ domain.append(("due_date", "<=", end_dt))
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+
+ if management_type_id:
+ domain.append(('management_type_id', '=', management_type_id))
+ type_domain = domain[:]
+ else:
+ # 更宽泛:物业/物管/管/水/电/能源等
+ types = self.env['yuthon.management.type'].search([
+ '|', '|', '|', '|',
+ ('name', 'ilike', '物'),
+ ('name', 'ilike', '管'),
+ ('name', 'ilike', '物业'),
+ ('name', 'ilike', '水'),
+ ('name', 'ilike', '电'),
+ ])
+ if types:
+ type_domain = domain + [('management_type_id', 'in', types.ids)]
+ else:
+ type_domain = domain[:]
+
+ # 先按类型+日期,再放宽日期,再放宽类型
+ receivables = self.search(type_domain, limit=3000, order="due_date asc")
+ if not receivables and type_domain != domain:
+ receivables = self.search(domain, limit=3000, order="due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ total_amount_nt = 0.0
+
+ for idx, rec in enumerate(receivables, start=1):
+ amt = rec.original_amount or 0.0
+ tax_rate = rec.tax or 0.0
+ amt_nt = amt / (1 + tax_rate / 100) if tax_rate else amt
+ total_amount += amt
+ total_amount_nt += amt_nt
+
+ rows.append({
+ "序号": idx,
+ "应收租期": rec.due_date.strftime("%Y-%m-%d") if rec.due_date else "",
+ "承租方名称": rec.lessee_id.name if rec.lessee_id else "",
+ "金额(含税)": round(amt, 2),
+ "税率": tax_rate,
+ "金额(不含税)": round(amt_nt, 2),
+ "物业名称": rec.property_name or "",
+ "产权证": rec.title_deed or "",
+ "合同编号": rec.contract_id.contract_code if rec.contract_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "应收租期": False,
+ "承租方名称": "合计:",
+ "金额(含税)": round(total_amount, 2),
+ "税率": False,
+ "金额(不含税)": round(total_amount_nt, 2),
+ "物业名称": False,
+ "产权证": False,
+ "合同编号": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/deposit_total_report.py b/yuthon_report/models/deposit_total_report.py
new file mode 100644
index 000000000..dd16335bc
--- /dev/null
+++ b/yuthon_report/models/deposit_total_report.py
@@ -0,0 +1,80 @@
+from odoo import api, models
+
+
+class DepositTotalReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_deposit_total_report(self, params):
+ """
+ 押金累计总额:
+ - 默认查询管理类型含“押金”的应收账单
+ - 支持关键词搜索合同/物业/承租方
+ - 金额取 original_amount
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+
+ # 管理类型
+ if management_type_id:
+ type_domain = domain + [('management_type_id', '=', management_type_id)]
+ else:
+ deposit_types = self.env['yuthon.management.type'].search([('name', 'ilike', '押金')])
+ if deposit_types:
+ type_domain = domain + [('management_type_id', 'in', deposit_types.ids)]
+ else:
+ type_domain = domain[:]
+
+ receivables = self.search(type_domain, limit=3000, order="due_date asc")
+ if not receivables and type_domain != domain:
+ receivables = self.search(domain, limit=3000, order="due_date asc")
+
+ rows = []
+ total_amount = 0.0
+
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ rows.append({
+ "序号": idx,
+ "合同编号": rec.contract_id.contract_code if rec.contract_id else "",
+ "物业名称": rec.property_name or "",
+ "承租方名称": rec.lessee_id.name if rec.lessee_id else "",
+ "金额": round(amount, 2),
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "合同编号": False,
+ "物业名称": False,
+ "承租方名称": "合计:",
+ "金额": round(total_amount, 2),
+ "所属公司": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/management_area_report.py b/yuthon_report/models/management_area_report.py
new file mode 100644
index 000000000..eb43bf9b0
--- /dev/null
+++ b/yuthon_report/models/management_area_report.py
@@ -0,0 +1,88 @@
+import datetime
+
+from odoo import api, models
+
+
+class ManagementAreaReport(models.Model):
+ _inherit = 'yuthon.property'
+
+ @api.model
+ def get_management_area_report(self, params):
+ """管理面积报表数据源。"""
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('name', 'ilike', keyword),
+ ('title_deed', 'ilike', keyword),
+ ('code', 'ilike', keyword),
+ ]
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else None
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+
+ properties = self.search(domain, limit=2000)
+
+ rows = []
+ total_rent_area = 0.0
+ total_equity_area = 0.0
+
+ for idx, prop in enumerate(properties, start=1):
+ # 按结束日期或当前日期选取有效管理信息
+ target_date = end_dt or datetime.date.today()
+ valid_info = prop.manage_information_ids.filtered(
+ lambda info: info.start_time <= target_date and (not info.end_date or info.end_date >= target_date)
+ )[:1]
+
+ # 若指定了日期范围但无有效管理信息,则跳过
+ if (date_start or date_end) and not valid_info:
+ continue
+
+ management_area = valid_info.management_area if valid_info else prop.management_area
+ rent_area = valid_info.rent_area if valid_info else prop.rent_area
+ company = valid_info.company_id if valid_info else prop.company_id
+ purpose = valid_info.purpose_id if valid_info else prop.purpose_id
+ message_standard = prop.message_standard_id # 信息行无管理费标准字段,直接取物业字段
+
+ total_rent_area += rent_area or 0.0
+ total_equity_area += prop.equity_area or 0.0
+
+ rows.append({
+ "序号": idx,
+ "物业id": prop.id,
+ "产权证号": prop.title_deed or "",
+ "物业名称": prop.name or "",
+ "可租面积": round(rent_area or 0.0, 4),
+ "产权面积": round(prop.equity_area or 0.0, 4),
+ "用途": purpose.name if purpose else "",
+ "管理费标准": message_standard.name if message_standard else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "物业id": "合计",
+ "产权证号": False,
+ "物业名称": False,
+ "可租面积": round(total_rent_area, 4),
+ "产权面积": round(total_equity_area, 4),
+ "用途": False,
+ "管理费标准": False,
+ }
+ }
+
diff --git a/yuthon_report/models/monthly_assessment_fee_report.py b/yuthon_report/models/monthly_assessment_fee_report.py
new file mode 100644
index 000000000..ec4214bf1
--- /dev/null
+++ b/yuthon_report/models/monthly_assessment_fee_report.py
@@ -0,0 +1,111 @@
+import datetime
+
+from odoo import api, models
+
+
+class MonthlyAssessmentFeeReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_monthly_assessment_fee_report(self, params):
+ """
+ 本月支出-评估费:
+ - 默认当月,支持 date_start/date_end 覆盖
+ - 管理类型默认匹配“评估/估”,可传 management_type_id 精确指定
+ - 金额取 original_amount
+ - 日期优先收款日期 payout_date,若无则应收日期 due_date
+ - 备注取 remark
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 当月时间范围
+ today = datetime.date.today()
+ month_start = today.replace(day=1)
+ if month_start.month == 12:
+ month_end = month_start.replace(year=month_start.year + 1, month=1) - datetime.timedelta(days=1)
+ else:
+ month_end = month_start.replace(month=month_start.month + 1) - datetime.timedelta(days=1)
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else month_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else month_end
+
+ date_domain = [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ base_domain = []
+ if condition:
+ base_domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ base_domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ base_domain.append(('company_id', '=', company_id))
+
+ if management_type_id:
+ type_domain = base_domain + [('management_type_id', '=', management_type_id)]
+ else:
+ assess_types = self.env['yuthon.management.type'].search([
+ '|',
+ ('name', 'ilike', '评估'),
+ ('name', 'ilike', '估'),
+ ])
+ if assess_types:
+ type_domain = base_domain + [('management_type_id', 'in', assess_types.ids)]
+ else:
+ type_domain = base_domain[:]
+
+ receivables = self.search(type_domain + date_domain, limit=2000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain + date_domain, limit=2000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain, limit=2000, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ pay_date = rec.payout_date or rec.due_date
+ rows.append({
+ "序号": idx,
+ "编码": rec.contract_id.contract_code if rec.contract_id else rec.code if hasattr(rec, 'code') else "",
+ "日期": pay_date.strftime("%Y-%m-%d") if pay_date else "",
+ "物业名称": rec.property_name or "",
+ "金额": round(amount, 2),
+ "备注": rec.remark or "",
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "编码": False,
+ "日期": False,
+ "物业名称": False,
+ "金额": round(total_amount, 2),
+ "备注": False,
+ "所属公司": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/monthly_expense_total_report.py b/yuthon_report/models/monthly_expense_total_report.py
new file mode 100644
index 000000000..759d60078
--- /dev/null
+++ b/yuthon_report/models/monthly_expense_total_report.py
@@ -0,0 +1,98 @@
+import datetime
+
+from odoo import api, models
+
+
+class MonthlyExpenseTotalReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_monthly_expense_total_report(self, params):
+ """
+ 本月支出-合计:
+ - 默认统计当前月份,可用 date_start/date_end 覆盖
+ - 不限定管理类型(可在前端传 management_type_id 精确过滤)
+ - 金额取 original_amount
+ - 日期优先收款日期 payout_date,若为空则应收日期 due_date
+ - 备注取 remark
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 当月范围
+ today = datetime.date.today()
+ month_start = today.replace(day=1)
+ if month_start.month == 12:
+ month_end = month_start.replace(year=month_start.year + 1, month=1) - datetime.timedelta(days=1)
+ else:
+ month_end = month_start.replace(month=month_start.month + 1) - datetime.timedelta(days=1)
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else month_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else month_end
+
+ date_domain = [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ base_domain = []
+ if condition:
+ base_domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ base_domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ base_domain.append(('company_id', '=', company_id))
+ if management_type_id:
+ base_domain.append(('management_type_id', '=', management_type_id))
+
+ receivables = self.search(base_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ if not receivables:
+ receivables = self.search(base_domain, limit=3000, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ pay_date = rec.payout_date or rec.due_date
+ rows.append({
+ "序号": idx,
+ "编码": rec.contract_id.contract_code if rec.contract_id else rec.code if hasattr(rec, 'code') else "",
+ "日期": pay_date.strftime("%Y-%m-%d") if pay_date else "",
+ "物业名称": rec.property_name or "",
+ "金额": round(amount, 2),
+ "备注": rec.remark or "",
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "编码": False,
+ "日期": False,
+ "物业名称": False,
+ "金额": round(total_amount, 2),
+ "备注": False,
+ "所属公司": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/monthly_maintenance_fee_report.py b/yuthon_report/models/monthly_maintenance_fee_report.py
new file mode 100644
index 000000000..f31fdf03f
--- /dev/null
+++ b/yuthon_report/models/monthly_maintenance_fee_report.py
@@ -0,0 +1,111 @@
+import datetime
+
+from odoo import api, models
+
+
+class MonthlyMaintenanceFeeReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_monthly_maintenance_fee_report(self, params):
+ """
+ 本月支出-维修费:
+ - 默认当月,可用 date_start/date_end 覆盖
+ - 管理类型默认匹配“维/维修”,可传 management_type_id 精确指定
+ - 金额取 original_amount
+ - 日期优先收款日期 payout_date,若无则应收日期 due_date
+ - 备注取 remark
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 当月时间范围
+ today = datetime.date.today()
+ month_start = today.replace(day=1)
+ if month_start.month == 12:
+ month_end = month_start.replace(year=month_start.year + 1, month=1) - datetime.timedelta(days=1)
+ else:
+ month_end = month_start.replace(month=month_start.month + 1) - datetime.timedelta(days=1)
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else month_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else month_end
+
+ date_domain = [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ base_domain = []
+ if condition:
+ base_domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ base_domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ base_domain.append(('company_id', '=', company_id))
+
+ if management_type_id:
+ type_domain = base_domain + [('management_type_id', '=', management_type_id)]
+ else:
+ maintenance_types = self.env['yuthon.management.type'].search([
+ '|',
+ ('name', 'ilike', '维'),
+ ('name', 'ilike', '维修'),
+ ])
+ if maintenance_types:
+ type_domain = base_domain + [('management_type_id', 'in', maintenance_types.ids)]
+ else:
+ type_domain = base_domain[:]
+
+ receivables = self.search(type_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain, limit=3000, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ pay_date = rec.payout_date or rec.due_date
+ rows.append({
+ "序号": idx,
+ "编码": rec.contract_id.contract_code if rec.contract_id else rec.code if hasattr(rec, 'code') else "",
+ "日期": pay_date.strftime("%Y-%m-%d") if pay_date else "",
+ "物业名称": rec.property_name or "",
+ "金额": round(amount, 2),
+ "备注": rec.remark or "",
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "编码": False,
+ "日期": False,
+ "物业名称": False,
+ "金额": round(total_amount, 2),
+ "备注": False,
+ "所属公司": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/monthly_management_fee_report.py b/yuthon_report/models/monthly_management_fee_report.py
new file mode 100644
index 000000000..5a1848d99
--- /dev/null
+++ b/yuthon_report/models/monthly_management_fee_report.py
@@ -0,0 +1,112 @@
+import datetime
+
+from odoo import api, models
+
+
+class MonthlyManagementFeeReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_monthly_management_fee_report(self, params):
+ """
+ 本月支出-管理费:
+ - 默认统计当月,支持 date_start/date_end 覆盖
+ - 管理类型默认匹配名称含“管/管理/物管”,可传 management_type_id 精确指定
+ - 金额取 original_amount
+ - 日期优先收款日期 payout_date,若无则应收日期 due_date
+ - 备注取 remark
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 当月时间范围
+ today = datetime.date.today()
+ month_start = today.replace(day=1)
+ if month_start.month == 12:
+ month_end = month_start.replace(year=month_start.year + 1, month=1) - datetime.timedelta(days=1)
+ else:
+ month_end = month_start.replace(month=month_start.month + 1) - datetime.timedelta(days=1)
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else month_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else month_end
+
+ date_domain = [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ base_domain = []
+ if condition:
+ base_domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ base_domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ base_domain.append(('company_id', '=', company_id))
+
+ if management_type_id:
+ type_domain = base_domain + [('management_type_id', '=', management_type_id)]
+ else:
+ mgmt_types = self.env['yuthon.management.type'].search([
+ '|', '|',
+ ('name', 'ilike', '管'),
+ ('name', 'ilike', '管理'),
+ ('name', 'ilike', '物管'),
+ ])
+ if mgmt_types:
+ type_domain = base_domain + [('management_type_id', 'in', mgmt_types.ids)]
+ else:
+ type_domain = base_domain[:]
+
+ receivables = self.search(type_domain + date_domain, limit=2000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain + date_domain, limit=2000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain, limit=2000, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ pay_date = rec.payout_date or rec.due_date
+ rows.append({
+ "序号": idx,
+ "编码": rec.contract_id.contract_code if rec.contract_id else rec.code if hasattr(rec, 'code') else "",
+ "日期": pay_date.strftime("%Y-%m-%d") if pay_date else "",
+ "物业名称": rec.property_name or "",
+ "金额": round(amount, 2),
+ "备注": rec.remark or "",
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "编码": False,
+ "日期": False,
+ "物业名称": False,
+ "金额": round(total_amount, 2),
+ "备注": False,
+ "所属公司": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/monthly_other_expense_report.py b/yuthon_report/models/monthly_other_expense_report.py
new file mode 100644
index 000000000..8ff7cdd58
--- /dev/null
+++ b/yuthon_report/models/monthly_other_expense_report.py
@@ -0,0 +1,111 @@
+import datetime
+
+from odoo import api, models
+
+
+class MonthlyOtherExpenseReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_monthly_other_expense_report(self, params):
+ """
+ 本月支出-其他:
+ - 默认当月,支持 date_start/date_end 覆盖
+ - 管理类型默认匹配“其他/其它”,可传 management_type_id 精确指定
+ - 金额取 original_amount
+ - 日期优先收款日期 payout_date,若无则应收日期 due_date
+ - 备注取 remark
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 当月时间范围
+ today = datetime.date.today()
+ month_start = today.replace(day=1)
+ if month_start.month == 12:
+ month_end = month_start.replace(year=month_start.year + 1, month=1) - datetime.timedelta(days=1)
+ else:
+ month_end = month_start.replace(month=month_start.month + 1) - datetime.timedelta(days=1)
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else month_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else month_end
+
+ date_domain = [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ base_domain = []
+ if condition:
+ base_domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ base_domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ base_domain.append(('company_id', '=', company_id))
+
+ if management_type_id:
+ type_domain = base_domain + [('management_type_id', '=', management_type_id)]
+ else:
+ other_types = self.env['yuthon.management.type'].search([
+ '|',
+ ('name', 'ilike', '其他'),
+ ('name', 'ilike', '其它'),
+ ])
+ if other_types:
+ type_domain = base_domain + [('management_type_id', 'in', other_types.ids)]
+ else:
+ type_domain = base_domain[:]
+
+ receivables = self.search(type_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain, limit=3000, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ pay_date = rec.payout_date or rec.due_date
+ rows.append({
+ "序号": idx,
+ "编码": rec.contract_id.contract_code if rec.contract_id else rec.code if hasattr(rec, 'code') else "",
+ "日期": pay_date.strftime("%Y-%m-%d") if pay_date else "",
+ "物业名称": rec.property_name or "",
+ "金额": round(amount, 2),
+ "备注": rec.remark or "",
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "编码": False,
+ "日期": False,
+ "物业名称": False,
+ "金额": round(total_amount, 2),
+ "备注": False,
+ "所属公司": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/parking_fee_current_month_receivable.py b/yuthon_report/models/parking_fee_current_month_receivable.py
new file mode 100644
index 000000000..10b931500
--- /dev/null
+++ b/yuthon_report/models/parking_fee_current_month_receivable.py
@@ -0,0 +1,99 @@
+import datetime
+
+from odoo import api, models
+
+
+class ParkingFeeCurrentMonthReceivable(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_parking_fee_current_month_receivable(self, params):
+ """
+ 停车费本月应收:
+ - 默认过滤当月应收日期;可用 date_start/date_end 覆盖
+ - 默认管理类型名称含“车”,也可传 management_type_id 精确指定
+ - 金额取应收 original_amount
+ """
+ domain = []
+
+ # 关键词
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 时间范围(默认当月)
+ today = datetime.date.today()
+ month_start = today.replace(day=1)
+ if month_start.month == 12:
+ month_end = month_start.replace(year=month_start.year + 1, month=1) - datetime.timedelta(days=1)
+ else:
+ month_end = month_start.replace(month=month_start.month + 1) - datetime.timedelta(days=1)
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else month_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else month_end
+
+ domain.append(("due_date", ">=", start_dt))
+ domain.append(("due_date", "<=", end_dt))
+
+ # 其他过滤
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+
+ # 管理类型
+ if management_type_id:
+ type_domain = domain + [('management_type_id', '=', management_type_id)]
+ else:
+ parking_types = self.env['yuthon.management.type'].search([('name', 'ilike', '车')])
+ if parking_types:
+ type_domain = domain + [('management_type_id', 'in', parking_types.ids)]
+ else:
+ type_domain = domain[:]
+
+ receivables = self.search(type_domain, limit=2000, order="due_date asc")
+ if not receivables and type_domain != domain:
+ receivables = self.search(domain, limit=2000, order="due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ rows.append({
+ "序号": idx,
+ "合同编号": rec.contract_id.contract_code if rec.contract_id else "",
+ "物业名称": rec.property_name or "",
+ "承租方名称": rec.lessee_id.name if rec.lessee_id else "",
+ "金额": round(amount, 2),
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "合同编号": False,
+ "物业名称": False,
+ "承租方名称": "合计:",
+ "金额": round(total_amount, 2),
+ "所属公司": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/parking_fee_current_month_report.py b/yuthon_report/models/parking_fee_current_month_report.py
new file mode 100644
index 000000000..ae485a17b
--- /dev/null
+++ b/yuthon_report/models/parking_fee_current_month_report.py
@@ -0,0 +1,105 @@
+import datetime
+
+from odoo import api, models
+
+
+class ParkingFeeCurrentMonthReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_parking_fee_current_month_report(self, params):
+ """
+ 停车费本月实收:
+ - 默认过滤当月收款日期,收款日期为空则用应收日期兜底
+ - 默认管理类型名称含“车”,可通过 management_type_id 精确指定
+ - 金额取已收 delivered1
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 当月时间范围
+ today = datetime.date.today()
+ month_start = today.replace(day=1)
+ if month_start.month == 12:
+ month_end = month_start.replace(year=month_start.year + 1, month=1) - datetime.timedelta(days=1)
+ else:
+ month_end = month_start.replace(month=month_start.month + 1) - datetime.timedelta(days=1)
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else month_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else month_end
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+
+ # 管理类型
+ if management_type_id:
+ type_domain = domain + [('management_type_id', '=', management_type_id)]
+ else:
+ parking_types = self.env['yuthon.management.type'].search([('name', 'ilike', '车')])
+ if parking_types:
+ type_domain = domain + [('management_type_id', 'in', parking_types.ids)]
+ else:
+ type_domain = domain[:]
+
+ # 日期过滤:收款日期优先,收款为空则应收日期
+ date_domain = [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ ]
+
+ receivables = self.search(type_domain + date_domain, limit=2000, order="payout_date asc, due_date asc")
+ if not receivables:
+ receivables = self.search(type_domain, limit=2000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != domain:
+ receivables = self.search(domain + date_domain, limit=2000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != domain:
+ receivables = self.search(domain, limit=2000, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.delivered1 or 0.0
+ total_amount += amount
+ rows.append({
+ "序号": idx,
+ "合同编号": rec.contract_id.contract_code if rec.contract_id else "",
+ "物业名称": rec.property_name or "",
+ "承租方名称": rec.lessee_id.name if rec.lessee_id else "",
+ "金额": round(amount, 2),
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "合同编号": False,
+ "物业名称": False,
+ "承租方名称": "合计:",
+ "金额": round(total_amount, 2),
+ "所属公司": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/parking_fee_yearly_report.py b/yuthon_report/models/parking_fee_yearly_report.py
new file mode 100644
index 000000000..538bb8940
--- /dev/null
+++ b/yuthon_report/models/parking_fee_yearly_report.py
@@ -0,0 +1,110 @@
+import datetime
+
+from odoo import api, models
+
+
+class ParkingFeeYearlyReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_parking_fee_yearly_report(self, params):
+ """
+ 停车费本年累计:
+ - 默认取当年1月1日至12月31日的停车费账单
+ - 允许传入 date_start/date_end 覆盖
+ - 金额取 original_amount
+ """
+ domain = []
+
+ # 关键词
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 时间范围(默认当年)
+ today = datetime.date.today()
+ year_start = datetime.date(today.year, 1, 1)
+ year_end = datetime.date(today.year, 12, 31)
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else year_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else year_end
+
+ domain.append(("due_date", ">=", start_dt))
+ domain.append(("due_date", "<=", end_dt))
+
+ # 过滤条件
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+
+ if management_type_id:
+ domain.append(('management_type_id', '=', management_type_id))
+ parking_domain = domain[:]
+ else:
+ # 默认匹配管理类型名称含“车”(停车/车位等),若无匹配则不过滤
+ parking_types = self.env['yuthon.management.type'].search([('name', 'ilike', '车')])
+ if parking_types:
+ parking_domain = domain + [('management_type_id', 'in', parking_types.ids)]
+ else:
+ parking_domain = domain[:]
+
+ date_domain = [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ ]
+
+ # 1) 按停车管理类型 + 日期
+ receivables = self.search(parking_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ # 2) 无结果时,移除日期过滤
+ if not receivables:
+ receivables = self.search(parking_domain, limit=3000, order="payout_date asc, due_date asc")
+ # 3) 仍无结果,移除停车管理类型过滤(仅保留日期)
+ if not receivables and parking_domain != domain:
+ receivables = self.search(domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ # 4) 仍无结果,再移除日期,彻底兜底
+ if not receivables and parking_domain != domain:
+ receivables = self.search(domain, limit=3000, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ rows.append({
+ "序号": idx,
+ "合同编号": rec.contract_id.contract_code if rec.contract_id else "",
+ "物业名称": rec.property_name or "",
+ "承租方名称": rec.lessee_id.name if rec.lessee_id else "",
+ "金额": round(amount, 2),
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "合同编号": False,
+ "物业名称": False,
+ "承租方名称": "合计:",
+ "金额": round(total_amount, 2),
+ "所属公司": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/penalty_detail_report.py b/yuthon_report/models/penalty_detail_report.py
new file mode 100644
index 000000000..442228643
--- /dev/null
+++ b/yuthon_report/models/penalty_detail_report.py
@@ -0,0 +1,129 @@
+import datetime
+
+from odoo import api, models
+
+
+class PenaltyDetailReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_penalty_detail_report(self, params):
+ """违约金明细报表(仅取管理类型包含“违约”)。"""
+ # 优先用筛选传入的管理类型;否则自动匹配名称含“违约”
+ domain = []
+ penalty_types = self.env['yuthon.management.type'].search([('name', 'ilike', '违约')])
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else None
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else None
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+ if management_type_id:
+ domain.append(('management_type_id', '=', management_type_id))
+ elif penalty_types:
+ # 兼容未标记管理类型但标记了违约金的记录
+ domain += ['|', ('management_type_id', 'in', penalty_types.ids), ('is_penalty', '=', True)]
+ else:
+ domain.append(('is_penalty', '=', True))
+
+ date_domain = []
+ if start_dt and end_dt:
+ # 区间匹配 due_date 或 payout_date 任一落在范围内
+ date_domain = [
+ '|',
+ '&', ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ ]
+ elif start_dt:
+ date_domain = ['|', ('due_date', '>=', start_dt), ('payout_date', '>=', start_dt)]
+ elif end_dt:
+ date_domain = ['|', ('due_date', '<=', end_dt), ('payout_date', '<=', end_dt)]
+
+ # 先按日期过滤查,如果无结果且传了日期,则去掉日期过滤再查一遍,避免漏数据
+ receivables = self.search(domain + date_domain, limit=2000, order="due_date asc")
+ if not receivables and date_domain:
+ receivables = self.search(domain, limit=2000, order="due_date asc")
+
+ rows = []
+ total_amount = total_penalty = total_owed = total_collected = total_uncollected = 0.0
+ total_per_day = 0.0
+
+ for idx, rec in enumerate(receivables, start=1):
+ per_day = rec.per_day or 0.0
+ penalty_amount = rec.penalty or 0.0
+ # 违约金计费天数,若每日金额不为0,则用总额/每日
+ penalty_days = round(penalty_amount / per_day, 2) if per_day else 0.0
+
+ amount = rec.original_amount or 0.0
+ rent_owed = rec.rent_owed or 0.0
+ collected = rec.delivered1 or 0.0
+ uncollected = rec.submitted or 0.0
+
+ total_amount += amount
+ total_penalty += penalty_amount
+ total_owed += rent_owed
+ total_collected += collected
+ total_uncollected += uncollected
+ total_per_day += per_day
+
+ rows.append({
+ "序号": idx,
+ "合同编号": rec.contract_id.contract_code if rec.contract_id else "",
+ "物业名称": rec.property_name or "",
+ "承租方名称": rec.lessee_id.name if rec.lessee_id else "",
+ "金额": round(amount, 2),
+ "违约金计费时间(天)": penalty_days,
+ "欠租金额": round(rent_owed, 2),
+ "每天违约金(元/天)": round(per_day, 2),
+ "违约金金额": round(penalty_amount, 2),
+ "总已收金额": round(collected, 2),
+ "总未收金额": round(uncollected, 2),
+ "当月收款时间": rec.payout_date.strftime("%Y-%m-%d") if rec.payout_date else "",
+ "当月收款金额": round(collected, 2),
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "产权证号": rec.title_deed or "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "合同编号": "合计",
+ "物业名称": False,
+ "承租方名称": False,
+ "金额": round(total_amount, 2),
+ "违约金计费时间(天)": False,
+ "欠租金额": round(total_owed, 2),
+ "每天违约金(元/天)": round(total_per_day, 2),
+ "违约金金额": round(total_penalty, 2),
+ "总已收金额": round(total_collected, 2),
+ "总未收金额": round(total_uncollected, 2),
+ "当月收款时间": False,
+ "当月收款金额": round(total_collected, 2),
+ "所属公司": False,
+ "产权证号": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/models/receivable_detail_report.py b/yuthon_report/models/receivable_detail_report.py
new file mode 100644
index 000000000..0548c217a
--- /dev/null
+++ b/yuthon_report/models/receivable_detail_report.py
@@ -0,0 +1,87 @@
+import datetime
+
+from odoo import api, models
+
+
+class ReceivableDetailReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_receivable_detail_report(self, params):
+ """
+ 应收明细报表:
+ - 以应收期间过滤:due_date / due_date_end
+ - 支持关键词、权属类型、片区、公司、收款类型
+ - 金额取 original_amount
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ if date_start:
+ start_dt = datetime.datetime.fromisoformat(date_start).date()
+ domain.append(("due_date", ">=", start_dt))
+ if date_end:
+ end_dt = datetime.datetime.fromisoformat(date_end).date()
+ domain.append(("due_date", "<=", end_dt))
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+ if management_type_id:
+ domain.append(('management_type_id', '=', management_type_id))
+
+ receivables = self.search(domain, limit=3000, order="due_date asc")
+
+ rows = []
+ total_amount = 0.0
+
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+
+ if rec.due_date_end:
+ period = f"{rec.due_date}~{rec.due_date_end}"
+ else:
+ period = rec.due_date.strftime("%Y-%m-%d") if rec.due_date else ""
+
+ rows.append({
+ "序号": idx,
+ "应收租期": period,
+ "承租方名称": rec.lessee_id.name if rec.lessee_id else "",
+ "金额": round(amount, 2),
+ "物业名称": rec.property_name or "",
+ "产权证": rec.title_deed or "",
+ "合同编号": rec.contract_id.contract_code if rec.contract_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "应收租期": "合计",
+ "承租方名称": False,
+ "金额": round(total_amount, 2),
+ "物业名称": False,
+ "产权证": False,
+ "合同编号": False,
+ }
+ }
+
diff --git a/yuthon_report/models/rentable_area_report.py b/yuthon_report/models/rentable_area_report.py
new file mode 100644
index 000000000..df6f0b54c
--- /dev/null
+++ b/yuthon_report/models/rentable_area_report.py
@@ -0,0 +1,73 @@
+from odoo import api, models
+
+
+class RentableAreaReport(models.Model):
+ _inherit = 'yuthon.property'
+
+ @api.model
+ def get_rentable_area_report(self, params):
+ """
+ 可租面积报表:
+ - 金额字段用 rent_area
+ - 物业状态取 property_state 映射
+ - 空置时长取 vacant_duration
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('name', 'ilike', keyword),
+ ('title_deed', 'ilike', keyword),
+ ('code', 'ilike', keyword),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+
+ properties = self.search(domain, limit=3000)
+
+ state_map = {
+ 'on_rent': '已租',
+ 'vacant': '空置',
+ }
+
+ rows = []
+ total_area = 0.0
+ for idx, prop in enumerate(properties, start=1):
+ area = prop.rent_area or 0.0
+ total_area += area
+ rows.append({
+ "序号": idx,
+ "物业ID": prop.id,
+ "物业名称": prop.name or "",
+ "面积": round(area, 4),
+ "物业状态": state_map.get(prop.property_state, ""),
+ "空置时长": prop.vacant_duration or 0,
+ "管理员": prop.admin_id.name if prop.admin_id else "",
+ "所属公司": prop.company_id.referred if prop.company_id and hasattr(prop.company_id, 'referred') else prop.company_id.name if prop.company_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "物业ID": False,
+ "物业名称": "合计",
+ "面积": round(total_area, 4),
+ "物业状态": False,
+ "空置时长": False,
+ "管理员": False,
+ "所属公司": False,
+ }
+ }
+
diff --git a/yuthon_report/models/rented_area_report.py b/yuthon_report/models/rented_area_report.py
new file mode 100644
index 000000000..df845157a
--- /dev/null
+++ b/yuthon_report/models/rented_area_report.py
@@ -0,0 +1,86 @@
+from odoo import api, models
+
+
+class RentedAreaReport(models.Model):
+ _inherit = 'yuthon.property'
+
+ @api.model
+ def get_rented_area_report(self, params):
+ """
+ 已租面积报表:
+ - 数据来源:物业表(yuthon.property)
+ - 仅筛选 property_state = 'on_rent'
+ - 面积取 rent_area
+ - 单价取 unit_price(若无则0)
+ - 租金取当前合同 rent(若无则 current_rent 或0)
+ """
+ domain = [('property_state', '=', 'on_rent')]
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('name', 'ilike', keyword),
+ ('title_deed', 'ilike', keyword),
+ ('contract_id.contract_code', 'ilike', keyword),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+
+ properties = self.search(domain, limit=3000)
+
+ rows = []
+ total_area = 0.0
+ total_rent = 0.0
+
+ for idx, prop in enumerate(properties, start=1):
+ area = prop.rent_area or 0.0
+ unit_price = prop.unit_price or 0.0
+
+ # 物业上未必有 contract_id 字段,安全获取
+ contract = getattr(prop, 'contract_id', False)
+ rent_val = 0.0
+ if contract and getattr(contract, 'rent', False):
+ rent_val = contract.rent
+ elif getattr(prop, 'current_rent', False):
+ rent_val = prop.current_rent
+
+ total_area += area
+ total_rent += rent_val
+
+ rows.append({
+ "序号": idx,
+ "合同编号": contract.contract_code if contract and getattr(contract, 'contract_code', False) else "",
+ "物业名称": prop.name or "",
+ "承租方名称": prop.lessee_id.name if prop.lessee_id else "",
+ "面积": round(area, 4),
+ "单价": round(unit_price, 2),
+ "租金": round(rent_val, 2),
+ "管理员": prop.admin_id.name if prop.admin_id else "",
+ "所属公司": prop.company_id.referred if prop.company_id and hasattr(prop.company_id, 'referred') else prop.company_id.name if prop.company_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "合同编号": False,
+ "物业名称": "合计:",
+ "承租方名称": False,
+ "面积": round(total_area, 4),
+ "单价": False,
+ "租金": round(total_rent, 2),
+ "管理员": False,
+ "所属公司": False,
+ }
+ }
+
diff --git a/yuthon_report/models/yearly_assessment_fee_report.py b/yuthon_report/models/yearly_assessment_fee_report.py
new file mode 100644
index 000000000..24864f09f
--- /dev/null
+++ b/yuthon_report/models/yearly_assessment_fee_report.py
@@ -0,0 +1,107 @@
+import datetime
+
+from odoo import api, models
+
+
+class YearlyAssessmentFeeReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_yearly_assessment_fee_report(self, params):
+ """
+ 本年累计-评估费用:
+ - 默认统计当年,支持 date_start/date_end 覆盖
+ - 管理类型默认匹配名称含“评估/估”,可传 management_type_id 精确指定
+ - 金额取 original_amount
+ - 日期优先收款日期 payout_date,若无则应收日期 due_date
+ - 备注取 remark
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 时间范围:默认当年
+ today = datetime.date.today()
+ default_start = datetime.date(today.year, 1, 1)
+ default_end = datetime.date(today.year, 12, 31)
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else default_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else default_end
+
+ date_domain = [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ base_domain = []
+ if condition:
+ base_domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ base_domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ base_domain.append(('company_id', '=', company_id))
+
+ if management_type_id:
+ type_domain = base_domain + [('management_type_id', '=', management_type_id)]
+ else:
+ assess_types = self.env['yuthon.management.type'].search([
+ '|',
+ ('name', 'ilike', '评估'),
+ ('name', 'ilike', '估'),
+ ])
+ if assess_types:
+ type_domain = base_domain + [('management_type_id', 'in', assess_types.ids)]
+ else:
+ type_domain = base_domain[:]
+
+ receivables = self.search(type_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain, limit=3000, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ pay_date = rec.payout_date or rec.due_date
+ rows.append({
+ "序号": idx,
+ "编码": rec.contract_id.contract_code if rec.contract_id else rec.code if hasattr(rec, 'code') else "",
+ "日期": pay_date.strftime("%Y-%m-%d") if pay_date else "",
+ "物业名称": rec.property_name or "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ "金额": round(amount, 2),
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "备注": rec.remark or "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "编码": False,
+ "日期": False,
+ "物业名称": False,
+ "管理员": "合计:",
+ "金额": round(total_amount, 2),
+ "所属公司": False,
+ "备注": False,
+ }
+ }
+
diff --git a/yuthon_report/models/yearly_expense_report.py b/yuthon_report/models/yearly_expense_report.py
new file mode 100644
index 000000000..b3200f03f
--- /dev/null
+++ b/yuthon_report/models/yearly_expense_report.py
@@ -0,0 +1,88 @@
+import datetime
+
+from odoo import api, models
+
+
+class YearlyExpenseReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_yearly_expense_report(self, params):
+ """
+ 支出本年-合计:
+ - 默认统计当前自然年的支出,日期优先用收款日期 payout_date,为空则用应收日期 due_date
+ - 金额取 original_amount
+ - 备注取 remark
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 年度时间范围
+ today = datetime.date.today()
+ default_start = datetime.date(today.year, 1, 1)
+ default_end = datetime.date(today.year, 12, 31)
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else default_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else default_end
+
+ # 日期过滤:收款日期优先,为空则用应收日期
+ date_domain = [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+
+ base_domain = []
+ if condition:
+ base_domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ base_domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ base_domain.append(('company_id', '=', company_id))
+
+ receivables = self.search(base_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ if not receivables:
+ receivables = self.search(base_domain, limit=3000, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ pay_date = rec.payout_date or rec.due_date
+ rows.append({
+ "序号": idx,
+ "支出日期": pay_date.strftime("%Y-%m-%d") if pay_date else "",
+ "金额": round(amount, 2),
+ "物业名称": rec.property_name or "",
+ "备注": rec.remark or "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "支出日期": False,
+ "金额": round(total_amount, 2),
+ "物业名称": False,
+ "备注": False,
+ "管理员": False,
+ "所属公司": False,
+ }
+ }
+
diff --git a/yuthon_report/models/yearly_maintenance_fee_report.py b/yuthon_report/models/yearly_maintenance_fee_report.py
new file mode 100644
index 000000000..55d89adeb
--- /dev/null
+++ b/yuthon_report/models/yearly_maintenance_fee_report.py
@@ -0,0 +1,107 @@
+import datetime
+
+from odoo import api, models
+
+
+class YearlyMaintenanceFeeReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_yearly_maintenance_fee_report(self, params):
+ """
+ 本年累计-维修费:
+ - 默认统计当前自然年,可传 date_start/date_end 覆盖
+ - 管理类型默认匹配“维/维修”,可传 management_type_id 精确指定
+ - 金额取 original_amount
+ - 日期优先收款日期 payout_date,为空则应收日期 due_date
+ - 备注取 remark
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 时间范围:默认当年
+ today = datetime.date.today()
+ default_start = datetime.date(today.year, 1, 1)
+ default_end = datetime.date(today.year, 12, 31)
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else default_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else default_end
+
+ date_domain = [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ base_domain = []
+ if condition:
+ base_domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ base_domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ base_domain.append(('company_id', '=', company_id))
+
+ if management_type_id:
+ type_domain = base_domain + [('management_type_id', '=', management_type_id)]
+ else:
+ maintenance_types = self.env['yuthon.management.type'].search([
+ '|',
+ ('name', 'ilike', '维'),
+ ('name', 'ilike', '维修'),
+ ])
+ if maintenance_types:
+ type_domain = base_domain + [('management_type_id', 'in', maintenance_types.ids)]
+ else:
+ type_domain = base_domain[:]
+
+ receivables = self.search(type_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain, limit=3000, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ pay_date = rec.payout_date or rec.due_date
+ rows.append({
+ "序号": idx,
+ "编码": rec.contract_id.contract_code if rec.contract_id else rec.code if hasattr(rec, 'code') else "",
+ "支出日期": pay_date.strftime("%Y-%m-%d") if pay_date else "",
+ "物业名称": rec.property_name or "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ "金额": round(amount, 2),
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "备注": rec.remark or "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "编码": False,
+ "支出日期": False,
+ "物业名称": False,
+ "管理员": "合计:",
+ "金额": round(total_amount, 2),
+ "所属公司": False,
+ "备注": False,
+ }
+ }
+
diff --git a/yuthon_report/models/yearly_management_fee_report.py b/yuthon_report/models/yearly_management_fee_report.py
new file mode 100644
index 000000000..26a4ce2e9
--- /dev/null
+++ b/yuthon_report/models/yearly_management_fee_report.py
@@ -0,0 +1,109 @@
+import datetime
+
+from odoo import api, models
+
+
+class YearlyManagementFeeReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_yearly_management_fee_report(self, params):
+ """
+ 本年累计-管理费:
+ - 默认统计当前自然年,支持传入 date_start/date_end 覆盖
+ - 管理类型默认匹配名称含“管/管理/物管”,可传 management_type_id 精确指定
+ - 金额取 original_amount
+ - 日期显示收款日期 payout_date,若无则应收日期 due_date
+ - 备注取 remark
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 年度时间范围
+ today = datetime.date.today()
+ default_start = datetime.date(today.year, 1, 1)
+ default_end = datetime.date(today.year, 12, 31)
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else default_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else default_end
+
+ # 日期过滤:收款日期优先,为空则用应收日期
+ date_domain = [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ base_domain = []
+ if condition:
+ base_domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ base_domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ base_domain.append(('company_id', '=', company_id))
+
+ if management_type_id:
+ type_domain = base_domain + [('management_type_id', '=', management_type_id)]
+ else:
+ mgmt_types = self.env['yuthon.management.type'].search([
+ '|', '|',
+ ('name', 'ilike', '管'),
+ ('name', 'ilike', '管理'),
+ ('name', 'ilike', '物管'),
+ ])
+ if mgmt_types:
+ type_domain = base_domain + [('management_type_id', 'in', mgmt_types.ids)]
+ else:
+ type_domain = base_domain[:]
+
+ receivables = self.search(type_domain + date_domain, limit=2000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain + date_domain, limit=2000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain, limit=2000, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ pay_date = rec.payout_date or rec.due_date
+ rows.append({
+ "序号": idx,
+ "编码": rec.contract_id.contract_code if rec.contract_id else rec.code if hasattr(rec, 'code') else "",
+ "支出日期": pay_date.strftime("%Y-%m-%d") if pay_date else "",
+ "物业名称": rec.property_name or "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ "金额": round(amount, 2),
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "备注": rec.remark or "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "编码": False,
+ "支出日期": False,
+ "物业名称": False,
+ "管理员": "合计:",
+ "金额": round(total_amount, 2),
+ "所属公司": False,
+ "备注": False,
+ }
+ }
+
diff --git a/yuthon_report/models/yearly_other_fee_report.py b/yuthon_report/models/yearly_other_fee_report.py
new file mode 100644
index 000000000..33fa1b490
--- /dev/null
+++ b/yuthon_report/models/yearly_other_fee_report.py
@@ -0,0 +1,107 @@
+import datetime
+
+from odoo import api, models
+
+
+class YearlyOtherFeeReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_yearly_other_fee_report(self, params):
+ """
+ 本年累计-其他费用:
+ - 默认统计当年,支持 date_start/date_end 覆盖
+ - 管理类型默认匹配名称含“其他/其它”,可传 management_type_id 精确指定
+ - 金额取 original_amount
+ - 日期优先 payout_date,为空则 due_date
+ - 备注取 remark
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 年度范围
+ today = datetime.date.today()
+ default_start = datetime.date(today.year, 1, 1)
+ default_end = datetime.date(today.year, 12, 31)
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else default_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else default_end
+
+ date_domain = [
+ '|',
+ '&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
+ '&', ('payout_date', '=', False), ('due_date', '>=', start_dt), ('due_date', '<=', end_dt),
+ ]
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ base_domain = []
+ if condition:
+ base_domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ base_domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ base_domain.append(('company_id', '=', company_id))
+
+ if management_type_id:
+ type_domain = base_domain + [('management_type_id', '=', management_type_id)]
+ else:
+ other_types = self.env['yuthon.management.type'].search([
+ '|',
+ ('name', 'ilike', '其他'),
+ ('name', 'ilike', '其它'),
+ ])
+ if other_types:
+ type_domain = base_domain + [('management_type_id', 'in', other_types.ids)]
+ else:
+ type_domain = base_domain[:]
+
+ receivables = self.search(type_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain + date_domain, limit=3000, order="payout_date asc, due_date asc")
+ if not receivables and type_domain != base_domain:
+ receivables = self.search(base_domain, limit=3000, order="payout_date asc, due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.original_amount or 0.0
+ total_amount += amount
+ pay_date = rec.payout_date or rec.due_date
+ rows.append({
+ "序号": idx,
+ "编码": rec.contract_id.contract_code if rec.contract_id else rec.code if hasattr(rec, 'code') else "",
+ "支出日期": pay_date.strftime("%Y-%m-%d") if pay_date else "",
+ "物业名称": rec.property_name or "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ "金额": round(amount, 2),
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "备注": rec.remark or "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "编码": False,
+ "支出日期": False,
+ "物业名称": False,
+ "管理员": "合计:",
+ "金额": round(total_amount, 2),
+ "所属公司": False,
+ "备注": False,
+ }
+ }
+
diff --git a/yuthon_report/models/yearly_penalty_report.py b/yuthon_report/models/yearly_penalty_report.py
new file mode 100644
index 000000000..2de089c83
--- /dev/null
+++ b/yuthon_report/models/yearly_penalty_report.py
@@ -0,0 +1,91 @@
+import datetime
+
+from odoo import api, models
+
+
+class YearlyPenaltyReport(models.Model):
+ _inherit = 'property.accounts.receivable'
+
+ @api.model
+ def get_yearly_penalty_report(self, params):
+ """
+ 违约金本年累计:
+ - 默认过滤当年应收日期,可传 date_start/date_end 覆盖
+ - 管理类型默认匹配“违约”或 is_penalty=True,也可传 management_type_id 精确指定
+ - 金额优先取 penalty,其次 submitted,再次 original_amount
+ """
+ domain = []
+
+ keyword = params.get("keyword")
+ if keyword:
+ domain += [
+ '|', '|',
+ ('contract_id.contract_code', 'ilike', keyword),
+ ('property_name', 'ilike', keyword),
+ ('lessee_id.name', 'ilike', keyword),
+ ]
+
+ # 时间范围:默认当年
+ today = datetime.date.today()
+ year_start = datetime.date(today.year, 1, 1)
+ year_end = datetime.date(today.year, 12, 31)
+ date_start = params.get("date_start")
+ date_end = params.get("date_end")
+ start_dt = datetime.datetime.fromisoformat(date_start).date() if date_start else year_start
+ end_dt = datetime.datetime.fromisoformat(date_end).date() if date_end else year_end
+ domain.append(("due_date", ">=", start_dt))
+ domain.append(("due_date", "<=", end_dt))
+
+ condition = params.get('condition')
+ partner_id = params.get('partner_id')
+ company_id = params.get('company_id')
+ management_type_id = params.get('management_type_id')
+
+ if condition:
+ domain.append(('tenure_type', '=', condition))
+ if partner_id:
+ domain.append(('precinct_id', '=', partner_id))
+ if company_id:
+ domain.append(('company_id', '=', company_id))
+
+ if management_type_id:
+ type_domain = domain + [('management_type_id', '=', management_type_id)]
+ else:
+ penalty_types = self.env['yuthon.management.type'].search([('name', 'ilike', '违约')])
+ if penalty_types:
+ type_domain = domain + ['|', ('management_type_id', 'in', penalty_types.ids), ('is_penalty', '=', True)]
+ else:
+ type_domain = domain + [('is_penalty', '=', True)]
+
+ receivables = self.search(type_domain, order="due_date asc")
+ if not receivables and type_domain != domain:
+ receivables = self.search(domain, order="due_date asc")
+
+ rows = []
+ total_amount = 0.0
+ for idx, rec in enumerate(receivables, start=1):
+ amount = rec.penalty or rec.submitted or rec.original_amount or 0.0
+ total_amount += amount
+ rows.append({
+ "序号": idx,
+ "合同编号": rec.contract_id.contract_code if rec.contract_id else "",
+ "物业名称": rec.property_name or "",
+ "承租方名称": rec.lessee_id.name if rec.lessee_id else "",
+ "金额": round(amount, 2),
+ "所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
+ "管理员": rec.admin_id.name if rec.admin_id else "",
+ })
+
+ return {
+ "rows": rows,
+ "summary": {
+ "序号": False,
+ "合同编号": False,
+ "物业名称": False,
+ "承租方名称": "合计:",
+ "金额": round(total_amount, 2),
+ "所属公司": False,
+ "管理员": False,
+ }
+ }
+
diff --git a/yuthon_report/views/cumulative_arrears_report.xml b/yuthon_report/views/cumulative_arrears_report.xml
new file mode 100644
index 000000000..304958079
--- /dev/null
+++ b/yuthon_report/views/cumulative_arrears_report.xml
@@ -0,0 +1,39 @@
+
+
+
+
+ 累计欠收
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_cumulative_arrears_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "面积(m²)", "field": "面积", "flex": 1},
+ {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.4},
+ {"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
+ {"headerName": "月租金", "field": "月租金", "flex": 1, "type": "number"},
+ {"headerName": "欠租金额", "field": "欠租金额", "flex": 1, "type": "number"},
+ {"headerName": "欠租所属月份", "field": "欠租所属月份", "flex": 1.2},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/current_month_received_report.xml b/yuthon_report/views/current_month_received_report.xml
new file mode 100644
index 000000000..935fa12af
--- /dev/null
+++ b/yuthon_report/views/current_month_received_report.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ 归属本月实收
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_current_month_received_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "合同编号", "field": "合同编号", "flex": 1},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 1.6},
+ {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.2},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/current_receivable_property_fee_report.xml b/yuthon_report/views/current_receivable_property_fee_report.xml
new file mode 100644
index 000000000..d021cc685
--- /dev/null
+++ b/yuthon_report/views/current_receivable_property_fee_report.xml
@@ -0,0 +1,39 @@
+
+
+
+
+ 当期应收-物管水电费
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_current_receivable_property_fee_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "应收租期", "field": "应收租期", "flex": 1.4},
+ {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.6},
+ {"headerName": "金额(含税)", "field": "金额(含税)", "flex": 1, "type": "number"},
+ {"headerName": "税率", "field": "税率", "flex": 0.8},
+ {"headerName": "金额(不含税)", "field": "金额(不含税)", "flex": 1, "type": "number"},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "产权证", "field": "产权证", "flex": 1.2},
+ {"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/deposit_total_report.xml b/yuthon_report/views/deposit_total_report.xml
new file mode 100644
index 000000000..00b8bf3e5
--- /dev/null
+++ b/yuthon_report/views/deposit_total_report.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ 押金累计总额
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_deposit_total_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.5},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/management_area_report.xml b/yuthon_report/views/management_area_report.xml
new file mode 100644
index 000000000..72d2a05e7
--- /dev/null
+++ b/yuthon_report/views/management_area_report.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ 管理面积报表
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "yuthon.property",
+ "method": "get_management_area_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "物业id", "field": "物业id", "flex": 0.8},
+ {"headerName": "产权证号", "field": "产权证号", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 1.8},
+ {"headerName": "可租面积", "field": "可租面积", "flex": 1, "type": "number"},
+ {"headerName": "产权面积", "field": "产权面积", "flex": 1, "type": "number"},
+ {"headerName": "用途", "field": "用途", "flex": 1},
+ {"headerName": "管理费标准", "field": "管理费标准", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/monthly_assessment_fee_report.xml b/yuthon_report/views/monthly_assessment_fee_report.xml
new file mode 100644
index 000000000..7171644a5
--- /dev/null
+++ b/yuthon_report/views/monthly_assessment_fee_report.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ 本月支出-评估费
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_monthly_assessment_fee_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "编码", "field": "编码", "flex": 1},
+ {"headerName": "日期", "field": "日期", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "备注", "field": "备注", "flex": 2},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/monthly_expense_total_report.xml b/yuthon_report/views/monthly_expense_total_report.xml
new file mode 100644
index 000000000..c7b5fb689
--- /dev/null
+++ b/yuthon_report/views/monthly_expense_total_report.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ 本月支出-合计
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_monthly_expense_total_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "编码", "field": "编码", "flex": 1},
+ {"headerName": "日期", "field": "日期", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "备注", "field": "备注", "flex": 2},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/monthly_maintenance_fee_report.xml b/yuthon_report/views/monthly_maintenance_fee_report.xml
new file mode 100644
index 000000000..d2a269e63
--- /dev/null
+++ b/yuthon_report/views/monthly_maintenance_fee_report.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ 本月支出-维修费
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_monthly_maintenance_fee_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "编码", "field": "编码", "flex": 1},
+ {"headerName": "日期", "field": "日期", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "备注", "field": "备注", "flex": 2},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/monthly_management_fee_report.xml b/yuthon_report/views/monthly_management_fee_report.xml
new file mode 100644
index 000000000..b83d07f7a
--- /dev/null
+++ b/yuthon_report/views/monthly_management_fee_report.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ 本月支出-管理费
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_monthly_management_fee_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "编码", "field": "编码", "flex": 1},
+ {"headerName": "日期", "field": "日期", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "备注", "field": "备注", "flex": 2},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/monthly_other_expense_report.xml b/yuthon_report/views/monthly_other_expense_report.xml
new file mode 100644
index 000000000..eadf7ba7a
--- /dev/null
+++ b/yuthon_report/views/monthly_other_expense_report.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ 本月支出-其他
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_monthly_other_expense_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "编码", "field": "编码", "flex": 1},
+ {"headerName": "日期", "field": "日期", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "备注", "field": "备注", "flex": 2},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/parking_fee_current_month_receivable.xml b/yuthon_report/views/parking_fee_current_month_receivable.xml
new file mode 100644
index 000000000..8333afc7c
--- /dev/null
+++ b/yuthon_report/views/parking_fee_current_month_receivable.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ 停车费本月应收
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_parking_fee_current_month_receivable",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.5},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/parking_fee_current_month_report.xml b/yuthon_report/views/parking_fee_current_month_report.xml
new file mode 100644
index 000000000..4aef2d783
--- /dev/null
+++ b/yuthon_report/views/parking_fee_current_month_report.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ 停车费本月实收
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_parking_fee_current_month_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.5},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/parking_fee_yearly_report.xml b/yuthon_report/views/parking_fee_yearly_report.xml
new file mode 100644
index 000000000..8b2f5f002
--- /dev/null
+++ b/yuthon_report/views/parking_fee_yearly_report.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ 停车费本年累计
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_parking_fee_yearly_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.5},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/penalty_detail_report.xml b/yuthon_report/views/penalty_detail_report.xml
new file mode 100644
index 000000000..af426a5fa
--- /dev/null
+++ b/yuthon_report/views/penalty_detail_report.xml
@@ -0,0 +1,45 @@
+
+
+
+
+ 违约金明细报表
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_penalty_detail_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "合同编号", "field": "合同编号", "flex": 1},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 1.6},
+ {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.2},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "违约金计费时间(天)", "field": "违约金计费时间(天)", "flex": 1},
+ {"headerName": "欠租金额", "field": "欠租金额", "flex": 1, "type": "number"},
+ {"headerName": "每天违约金(元/天)", "field": "每天违约金(元/天)", "flex": 1},
+ {"headerName": "违约金金额", "field": "违约金金额", "flex": 1, "type": "number"},
+ {"headerName": "总已收金额", "field": "总已收金额", "flex": 1, "type": "number"},
+ {"headerName": "总未收金额", "field": "总未收金额", "flex": 1, "type": "number"},
+ {"headerName": "当月收款时间", "field": "当月收款时间", "flex": 1},
+ {"headerName": "当月收款金额", "field": "当月收款金额", "flex": 1, "type": "number"},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "产权证号", "field": "产权证号", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/receivable_detail_report.xml b/yuthon_report/views/receivable_detail_report.xml
new file mode 100644
index 000000000..e49b204be
--- /dev/null
+++ b/yuthon_report/views/receivable_detail_report.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ 合同应收
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_receivable_detail_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "应收租期", "field": "应收租期", "flex": 1.4},
+ {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.4},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 1.8},
+ {"headerName": "产权证", "field": "产权证", "flex": 1.2},
+ {"headerName": "合同编号", "field": "合同编号", "flex": 1.2}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/rentable_area_report.xml b/yuthon_report/views/rentable_area_report.xml
new file mode 100644
index 000000000..98d68978b
--- /dev/null
+++ b/yuthon_report/views/rentable_area_report.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ 可租面积
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "yuthon.property",
+ "method": "get_rentable_area_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "物业ID", "field": "物业ID", "flex": 0.8},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "面积", "field": "面积", "flex": 1, "type": "number"},
+ {"headerName": "物业状态", "field": "物业状态", "flex": 1},
+ {"headerName": "空置时长", "field": "空置时长", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/rented_area_report.xml b/yuthon_report/views/rented_area_report.xml
new file mode 100644
index 000000000..9baedbf24
--- /dev/null
+++ b/yuthon_report/views/rented_area_report.xml
@@ -0,0 +1,38 @@
+
+
+
+
+ 已租面积
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "yuthon.property",
+ "method": "get_rented_area_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.5},
+ {"headerName": "面积", "field": "面积", "flex": 1, "type": "number"},
+ {"headerName": "单价", "field": "单价", "flex": 1, "type": "number"},
+ {"headerName": "租金", "field": "租金", "flex": 1, "type": "number"},
+ {"headerName": "管理员", "field": "管理员", "flex": 1},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/yearly_assessment_fee_report.xml b/yuthon_report/views/yearly_assessment_fee_report.xml
new file mode 100644
index 000000000..a12be209c
--- /dev/null
+++ b/yuthon_report/views/yearly_assessment_fee_report.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ 本年累计-评估费用
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_yearly_assessment_fee_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "编码", "field": "编码", "flex": 1},
+ {"headerName": "日期", "field": "日期", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "管理员", "field": "管理员", "flex": 1},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "备注", "field": "备注", "flex": 2}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/yearly_expense_report.xml b/yuthon_report/views/yearly_expense_report.xml
new file mode 100644
index 000000000..028650988
--- /dev/null
+++ b/yuthon_report/views/yearly_expense_report.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ 支出本年-合计
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_yearly_expense_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "支出日期", "field": "支出日期", "flex": 1.2},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "备注", "field": "备注", "flex": 2},
+ {"headerName": "管理员", "field": "管理员", "flex": 1},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/yearly_maintenance_fee_report.xml b/yuthon_report/views/yearly_maintenance_fee_report.xml
new file mode 100644
index 000000000..3f20f5424
--- /dev/null
+++ b/yuthon_report/views/yearly_maintenance_fee_report.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ 本年累计-维修费
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_yearly_maintenance_fee_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "编码", "field": "编码", "flex": 1},
+ {"headerName": "支出日期", "field": "支出日期", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "管理员", "field": "管理员", "flex": 1},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "备注", "field": "备注", "flex": 2}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/yearly_management_fee_report.xml b/yuthon_report/views/yearly_management_fee_report.xml
new file mode 100644
index 000000000..742217a8a
--- /dev/null
+++ b/yuthon_report/views/yearly_management_fee_report.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ 本年累计-管理费
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_yearly_management_fee_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "编码", "field": "编码", "flex": 1},
+ {"headerName": "支出日期", "field": "支出日期", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "管理员", "field": "管理员", "flex": 1},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "备注", "field": "备注", "flex": 2}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/yearly_other_fee_report.xml b/yuthon_report/views/yearly_other_fee_report.xml
new file mode 100644
index 000000000..a2565169e
--- /dev/null
+++ b/yuthon_report/views/yearly_other_fee_report.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ 本年累计-其他费用
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_yearly_other_fee_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "编码", "field": "编码", "flex": 1},
+ {"headerName": "支出日期", "field": "支出日期", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "管理员", "field": "管理员", "flex": 1},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "备注", "field": "备注", "flex": 2}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_report/views/yearly_penalty_report.xml b/yuthon_report/views/yearly_penalty_report.xml
new file mode 100644
index 000000000..e38a9dfaf
--- /dev/null
+++ b/yuthon_report/views/yearly_penalty_report.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ 违约金本年累计
+ receivable_report_client
+
+ {
+ "sideBar": true,
+ "report_config": {
+ "model": "property.accounts.receivable",
+ "method": "get_yearly_penalty_report",
+ "columns": [
+ {"headerName": "序号", "field": "序号", "flex": 0.6},
+ {"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
+ {"headerName": "物业名称", "field": "物业名称", "flex": 2},
+ {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.5},
+ {"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
+ {"headerName": "所属公司", "field": "所属公司", "flex": 1},
+ {"headerName": "管理员", "field": "管理员", "flex": 1}
+ ]
+ }
+ }
+
+
+
+
+
+
+
diff --git a/yuthon_trade/models/yuthon_trade_setting.py b/yuthon_trade/models/yuthon_trade_setting.py
index 5e5fb712f..c274a1af7 100644
--- a/yuthon_trade/models/yuthon_trade_setting.py
+++ b/yuthon_trade/models/yuthon_trade_setting.py
@@ -35,6 +35,11 @@ class SealsTypeSetting(models.Model):
is_effect = fields.Boolean(string="是否生效", compute="_compute_is_effect")
scope_setting_ids = fields.Many2many('seals.scope.setting', string="用章范围")
+ def _compute_display_name(self):
+ for record in self:
+ scope_name = record.scope_setting_id.name if record.scope_setting_id else "无"
+ record.display_name = f"{scope_name}{record.name}"
+
@api.depends('start_date', 'end_date')
def _compute_is_effect(self):
diff --git a/yuthon_trade/views/yuthon_business_approval_viwes.xml b/yuthon_trade/views/yuthon_business_approval_viwes.xml
index 40cba2aa4..f91a6bc3c 100644
--- a/yuthon_trade/views/yuthon_business_approval_viwes.xml
+++ b/yuthon_trade/views/yuthon_business_approval_viwes.xml
@@ -60,8 +60,7 @@
-
-
+
diff --git a/yuthon_trade/views/yuthon_seals_approval_views.xml b/yuthon_trade/views/yuthon_seals_approval_views.xml
index 85ddeac8a..62cb54c01 100644
--- a/yuthon_trade/views/yuthon_seals_approval_views.xml
+++ b/yuthon_trade/views/yuthon_seals_approval_views.xml
@@ -43,8 +43,7 @@
-
-
+