This commit is contained in:
李鹏宇
2026-06-17 18:01:58 +08:00
parent 30400df800
commit 4a4223a8b0
54 changed files with 822 additions and 373 deletions
+2
View File
@@ -75,6 +75,8 @@
'yuthon_report/static/src/components/yuthon_report/receivable_message.xml',
'yuthon_report/static/src/components/yuthon_report/yuthon_report.scss',
'yuthon_report/static/src/components/yuthon_report/receivable_report.js',
'yuthon_report/static/src/components/yuthon_report/multi_select_autocomplete.js',
'yuthon_report/static/src/components/yuthon_report/multi_select_autocomplete.xml',
'yuthon_report/static/src/components/yuthon_report/receivable_report.xml',
'yuthon_report/static/src/components/yuthon_report/receivable_flow_report.js',
'yuthon_report/static/src/components/yuthon_report/text_summary_report.js',
+1
View File
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from . import report_utils
from . import res_partner
from . import ledger_report
from . import receivable_report
@@ -3,6 +3,7 @@ import calendar
import logging
from odoo import api, models
from .report_utils import _id_list # 导入 ID 列表解析工具函数,支持多选参数
_logger = logging.getLogger(__name__)
@@ -55,14 +56,14 @@ class PropertyBillsList(models.Model):
]
# 公司/片区过滤
company_id = params.get('company_id')
partner_id = params.get('partner_id')
company_id = _id_list(params.get('company_id'))
partner_id = _id_list(params.get('partner_id')) # 解析片区 ID 为整数列表(多选支持)
if company_id:
domain.append(('lessor_id', '=', int(company_id)))
domain.append(('lessor_id', 'in', company_id))
if partner_id:
domain += ['|',
('contract_id.precinct_id', '=', int(partner_id)),
('receivables_id.precinct_id', '=', int(partner_id)),
('contract_id.precinct_id', 'in', partner_id), # 合同片区过滤(多选支持)
('receivables_id.precinct_id', 'in', partner_id), # 应收记录片区过滤(多选支持)
]
# 查询记录
@@ -3,6 +3,7 @@ import calendar
import logging
from odoo import api, models
from .report_utils import _id_list # 导入 ID 列表解析工具函数,支持多选参数
_logger = logging.getLogger(__name__)
@@ -55,14 +56,14 @@ class PropertyBillsList(models.Model):
]
# 公司/片区过滤
company_id = params.get('company_id')
partner_id = params.get('partner_id')
company_id = _id_list(params.get('company_id'))
partner_id = _id_list(params.get('partner_id')) # 解析片区 ID 为整数列表(多选支持)
if company_id:
domain.append(('lessor_id', '=', int(company_id)))
domain.append(('lessor_id', 'in', company_id))
if partner_id:
domain += ['|',
('contract_id.precinct_id', '=', int(partner_id)),
('receivables_id.precinct_id', '=', int(partner_id)),
('contract_id.precinct_id', 'in', partner_id), # 合同片区过滤(多选支持)
('receivables_id.precinct_id', 'in', partner_id), # 应收记录片区过滤(多选支持)
]
# 查询记录
+8 -5
View File
@@ -1,5 +1,6 @@
import datetime
from .report_utils import _id_list
from odoo import fields, models, api
@@ -16,7 +17,7 @@ class AttendanceReport(models.Model):
vacation_domain.append(("vacation_date", "<=", date_end))
salary_group_id = params.get('structure_id', False)
department_id = params.get('department_id', False)
company_id = params.get('company_id', False)
company_id = _id_list(params.get('company_id', False))
employee_domain = []
if salary_group_id:
@@ -24,7 +25,7 @@ class AttendanceReport(models.Model):
if department_id:
employee_domain.append(('department_id', '=', department_id))
if company_id:
employee_domain.append(('company_id', '=', company_id))
employee_domain.append(('company_id', 'in', company_id))
employee_ids = []
if employee_domain:
@@ -146,11 +147,13 @@ class AttendanceReport(models.Model):
@api.model
def get_domain(self, company_id=False):
if not company_id:
# 多选:company_id 标准化为列表
cid_list = _id_list(company_id)
if not cid_list:
return [], []
department_records = self.env['hr.department'].search([('company_id', '=', company_id)])
department_records = self.env['hr.department'].search([('company_id', 'in', cid_list)])
department_ids = [dept.id for dept in department_records]
salary_group_records = self.env['yuthon.salary.group'].search([('company_id', '=', company_id)])
salary_group_records = self.env['yuthon.salary.group'].search([('company_id', 'in', cid_list)])
salary_group_ids = [struct.id for struct in salary_group_records]
return department_ids, salary_group_ids
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from .report_utils import _id_list
from odoo import api, models
@@ -23,9 +24,9 @@ class BankStatementsReport(models.Model):
domain.append(('lending_direction', '=', 'loan'))
# 公司搜索条件
company_id = params.get('company_id')
company_id = _id_list(params.get('company_id'))
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
# 账号单位搜索条件
your_own_unit = params.get('your_own_unit')
+5 -4
View File
@@ -2,6 +2,7 @@ import calendar
import datetime
from odoo import api, models
from .report_utils import _id_list # 导入工具函数:将参数转换为列表格式,支持多选筛选
class CumArrearsReport(models.Model):
@@ -50,8 +51,8 @@ class CumArrearsReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 将partner_id转换为列表,用于多选筛选
company_id = _id_list(params.get('company_id'))
management_type_id = params.get('management_type_id')
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
@@ -60,9 +61,9 @@ class CumArrearsReport(models.Model):
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('receivable_id.precinct_id', '=', partner_id))
domain.append(('receivable_id.precinct_id', 'in', partner_id)) # 使用in操作符支持多选片区筛选
if company_id:
domain.append(('receivable_id.company_id', '=', company_id))
domain.append(('receivable_id.company_id', 'in', company_id))
if property_id:
domain.append(('receivable_id.property_ids', 'in', [property_id]))
if lessee_id:
+5 -4
View File
@@ -2,6 +2,7 @@ import calendar
import datetime
from odoo import api, models
from .report_utils import _id_list # 导入工具函数:将参数转换为列表格式,支持多选筛选
class CumIncomeReport(models.Model):
@@ -52,16 +53,16 @@ class CumIncomeReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 将partner_id转换为列表,用于多选筛选
company_id = _id_list(params.get('company_id'))
management_type_id = params.get('management_type_id')
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('receivable_id.precinct_id', '=', partner_id))
domain.append(('receivable_id.precinct_id', 'in', partner_id)) # 使用in操作符支持多选片区筛选
if company_id:
domain.append(('receivable_id.company_id', '=', company_id))
domain.append(('receivable_id.company_id', 'in', company_id))
if management_type_id:
domain.append(('receivable_id.management_type_id', '=', management_type_id))
else:
@@ -1,6 +1,7 @@
import datetime
from odoo import api, models
from .report_utils import _id_list # 导入工具函数:将参数转换为列表格式,支持多选筛选
class CurrentMonthReceivedReport(models.Model):
@@ -39,8 +40,8 @@ class CurrentMonthReceivedReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 将partner_id转换为列表,用于多选筛选
company_id = _id_list(params.get('company_id'))
management_type_id = params.get('management_type_id')
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
@@ -49,9 +50,9 @@ class CurrentMonthReceivedReport(models.Model):
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('receivable_id.precinct_id', '=', partner_id))
domain.append(('receivable_id.precinct_id', 'in', partner_id)) # 使用in操作符支持多选片区筛选
if company_id:
domain.append(('receivable_id.company_id', '=', company_id))
domain.append(('receivable_id.company_id', 'in', company_id))
if property_id:
domain.append(('receivable_id.property_ids', 'in', [property_id]))
if lessee_id:
@@ -1,6 +1,7 @@
import datetime
from odoo import api, models
from .report_utils import _id_list # 导入工具函数:将参数转换为列表格式,支持多选筛选
class CurrentReceivablePropertyFeeReport(models.Model):
@@ -36,16 +37,16 @@ class CurrentReceivablePropertyFeeReport(models.Model):
domain.append(("due_date", "<=", end_dt))
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 将partner_id转换为列表,用于多选筛选
company_id = _id_list(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))
domain.append(('precinct_id', 'in', partner_id)) # 使用in操作符支持多选片区筛选
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
if management_type_id:
domain.append(('management_type_id', '=', management_type_id))
@@ -2,6 +2,7 @@ import datetime
import calendar
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
class DepositCumArrearsReport(models.Model):
@@ -49,17 +50,17 @@ class DepositCumArrearsReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(params.get('company_id'))
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
if condition:
domain.append(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
if property_id:
domain.append(('property_ids', 'in', [property_id]))
if lessee_id:
@@ -142,13 +143,13 @@ class DepositCumArrearsReport(models.Model):
('is_invalid', '!=', True),
]
company_id = params.get('company_id')
company_id = _id_list(params.get('company_id'))
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
if property_id:
domain.append(('property_ids', 'in', [property_id]))
if lessee_id:
@@ -2,6 +2,7 @@ import datetime
import calendar
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
class DepositMonthReceivableReport(models.Model):
@@ -39,14 +40,14 @@ class DepositMonthReceivableReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(params.get('company_id'))
if condition:
domain.append(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
lines = self.sudo().search(domain, order="contract_id asc")
@@ -2,6 +2,7 @@ import datetime
import calendar
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
class DepositMonthReceivedReport(models.Model):
@@ -44,17 +45,17 @@ class DepositMonthReceivedReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(params.get('company_id'))
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
if condition:
domain.append(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
if property_id:
domain.append(('property_ids', 'in', [property_id]))
if lessee_id:
@@ -141,13 +142,13 @@ class DepositMonthReceivedReport(models.Model):
('is_invalid', '!=', True),
]
company_id = params.get('company_id')
company_id = _id_list(params.get('company_id'))
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
if property_id:
domain.append(('property_ids', 'in', [property_id]))
if lessee_id:
@@ -2,6 +2,7 @@ import datetime
import calendar
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
class DepositMonthRefundReport(models.Model):
@@ -42,8 +43,8 @@ class DepositMonthRefundReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(params.get('company_id'))
management_type_id = params.get('management_type_id')
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
@@ -51,9 +52,9 @@ class DepositMonthRefundReport(models.Model):
if condition:
domain.append(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
if property_id:
domain.append(('property_ids', 'in', [property_id]))
if lessee_id:
@@ -2,6 +2,7 @@ import datetime
import calendar
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
class DepositPreReceivedReport(models.Model):
@@ -44,17 +45,17 @@ class DepositPreReceivedReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(params.get('company_id'))
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
if condition:
domain.append(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
if property_id:
domain.append(('property_ids', 'in', [property_id]))
if lessee_id:
@@ -157,13 +158,13 @@ class DepositPreReceivedReport(models.Model):
('is_invalid', '!=', True),
]
company_id = params.get('company_id')
company_id = _id_list(params.get('company_id'))
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
if property_id:
domain.append(('property_ids', 'in', [property_id]))
if lessee_id:
+5 -4
View File
@@ -2,6 +2,7 @@ import datetime
import calendar
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
class DepositTotalReport(models.Model):
@@ -59,8 +60,8 @@ class DepositTotalReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(params.get('company_id'))
management_type_id = params.get('management_type_id')
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
@@ -68,9 +69,9 @@ class DepositTotalReport(models.Model):
if condition:
domain.append(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
if property_id:
domain.append(('property_ids', 'in', [property_id]))
if lessee_id:
+5 -4
View File
@@ -3,6 +3,7 @@ import datetime
import logging
from odoo import api, models
from .report_utils import _id_list # 导入 ID 列表解析工具函数,支持多选参数
_logger = logging.getLogger(__name__)
@@ -41,8 +42,8 @@ class LeasedAreaReport(models.Model):
keyword = params.get("keyword")
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 解析片区 ID 为整数列表(多选支持)
company_id = _id_list(params.get('company_id'))
# 前端侧边栏下拉选择器传入的 ID
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
@@ -202,7 +203,7 @@ class LeasedAreaReport(models.Model):
matched = False
for cl in lines:
ml = manage_map.get(cl.address_id.id) if cl.address_id else None
if ml and ml.precinct_id and ml.precinct_id.id == int(partner_id):
if ml and ml.precinct_id and ml.precinct_id.id in partner_id: # 片区匹配(多选支持)
matched = True
break
if not matched:
@@ -211,7 +212,7 @@ class LeasedAreaReport(models.Model):
matched = False
for cl in lines:
ml = manage_map.get(cl.address_id.id) if cl.address_id else None
if ml and ml.company_id and ml.company_id.id == int(company_id):
if ml and ml.company_id and ml.company_id.id in company_id:
matched = True
break
if not matched:
+5 -4
View File
@@ -2,6 +2,7 @@ import datetime
import calendar
from odoo import fields, models, api
from .report_utils import _id_list # partner_id 多选适配工具
class YuthonProperty(models.Model):
@@ -32,16 +33,16 @@ class YuthonProperty(models.Model):
# 基础过滤条件
condition = params.get('condition', False)
partner_id = params.get('partner_id', False)
company_id = params.get('company_id', False)
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(params.get('company_id', False))
domain = []
if condition:
domain.append(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
properties = self.search(domain, limit=500)
@@ -3,6 +3,7 @@ import calendar
import logging
from odoo import api, models
from .report_utils import _id_list # 导入 ID 列表解析工具函数,支持多选参数
_logger = logging.getLogger(__name__)
@@ -55,14 +56,14 @@ class PropertyBillsList(models.Model):
]
# 公司/片区过滤
company_id = params.get('company_id')
partner_id = params.get('partner_id')
company_id = _id_list(params.get('company_id'))
partner_id = _id_list(params.get('partner_id')) # 解析片区 ID 为整数列表(多选支持)
if company_id:
domain.append(('lessor_id', '=', int(company_id)))
domain.append(('lessor_id', 'in', company_id))
if partner_id:
domain += ['|',
('contract_id.precinct_id', '=', int(partner_id)),
('receivables_id.precinct_id', '=', int(partner_id)),
('contract_id.precinct_id', 'in', partner_id), # 合同片区过滤(多选支持)
('receivables_id.precinct_id', 'in', partner_id), # 应收记录片区过滤(多选支持)
]
# 查询记录
@@ -3,6 +3,7 @@ import calendar
import logging
from odoo import api, models
from .report_utils import _id_list # 导入 ID 列表解析工具函数,支持多选参数
_logger = logging.getLogger(__name__)
@@ -55,14 +56,14 @@ class PropertyBillsList(models.Model):
]
# 公司/片区过滤
company_id = params.get('company_id')
partner_id = params.get('partner_id')
company_id = _id_list(params.get('company_id'))
partner_id = _id_list(params.get('partner_id')) # 解析片区 ID 为整数列表(多选支持)
if company_id:
domain.append(('lessor_id', '=', int(company_id)))
domain.append(('lessor_id', 'in', company_id))
if partner_id:
domain += ['|',
('contract_id.precinct_id', '=', int(partner_id)),
('receivables_id.precinct_id', '=', int(partner_id)),
('contract_id.precinct_id', 'in', partner_id), # 合同片区过滤(多选支持)
('receivables_id.precinct_id', 'in', partner_id), # 应收记录片区过滤(多选支持)
]
# 查询记录
@@ -2,6 +2,7 @@ import datetime
import logging
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
_logger = logging.getLogger(__name__)
@@ -33,8 +34,8 @@ class ManagementAreaReport(models.Model):
keyword = params.get("keyword")
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(params.get('company_id'))
# 前端侧边栏下拉选择器传入的 ID
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
@@ -65,9 +66,9 @@ class ManagementAreaReport(models.Model):
if condition:
domain.append(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
# 物业名称下拉选择器:直接过滤 yuthon_property_id
if property_id:
domain.append(('yuthon_property_id', '=', int(property_id)))
@@ -3,6 +3,7 @@ import calendar
import logging
from odoo import api, models
from .report_utils import _id_list # 导入 ID 列表解析工具函数,支持多选参数
_logger = logging.getLogger(__name__)
@@ -55,14 +56,14 @@ class PropertyBillsList(models.Model):
]
# 公司/片区过滤
company_id = params.get('company_id')
partner_id = params.get('partner_id')
company_id = _id_list(params.get('company_id'))
partner_id = _id_list(params.get('partner_id')) # 解析片区 ID 为整数列表(多选支持)
if company_id:
domain.append(('lessor_id', '=', int(company_id)))
domain.append(('lessor_id', 'in', company_id))
if partner_id:
domain += ['|',
('contract_id.precinct_id', '=', int(partner_id)),
('receivables_id.precinct_id', '=', int(partner_id)),
('contract_id.precinct_id', 'in', partner_id), # 合同片区过滤(多选支持)
('receivables_id.precinct_id', 'in', partner_id), # 应收记录片区过滤(多选支持)
]
# 查询记录
@@ -3,6 +3,7 @@ import calendar
import logging
from odoo import api, models
from .report_utils import _id_list # 导入 ID 列表解析工具函数,支持多选参数
_logger = logging.getLogger(__name__)
@@ -55,14 +56,14 @@ class PropertyBillsList(models.Model):
]
# 公司/片区过滤
company_id = params.get('company_id')
partner_id = params.get('partner_id')
company_id = _id_list(params.get('company_id'))
partner_id = _id_list(params.get('partner_id')) # 解析片区 ID 为整数列表(多选支持)
if company_id:
domain.append(('lessor_id', '=', int(company_id)))
domain.append(('lessor_id', 'in', company_id))
if partner_id:
domain += ['|',
('contract_id.precinct_id', '=', int(partner_id)),
('receivables_id.precinct_id', '=', int(partner_id)),
('contract_id.precinct_id', 'in', partner_id), # 合同片区过滤(多选支持)
('receivables_id.precinct_id', 'in', partner_id), # 应收记录片区过滤(多选支持)
]
# 查询记录
@@ -2,6 +2,7 @@ import datetime
import calendar
from odoo import api, models
from .report_utils import _id_list # 导入工具函数:将参数转换为列表格式,支持多选筛选
class MonthReceivableReport(models.Model):
@@ -38,8 +39,8 @@ class MonthReceivableReport(models.Model):
('receivable_id.contract_id.contract_code', 'ilike', keyword)]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 将partner_id转换为列表,用于多选筛选
company_id = _id_list(params.get('company_id'))
management_type_id = params.get('management_type_id')
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
@@ -48,9 +49,9 @@ class MonthReceivableReport(models.Model):
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('receivable_id.precinct_id', '=', partner_id))
domain.append(('receivable_id.precinct_id', 'in', partner_id)) # 使用in操作符支持多选片区筛选
if company_id:
domain.append(('receivable_id.company_id', '=', company_id))
domain.append(('receivable_id.company_id', 'in', company_id))
if property_id:
domain.append(('receivable_id.property_ids', 'in', [property_id]))
if lessee_id:
@@ -2,6 +2,7 @@ import datetime
import calendar
from odoo import api, models
from .report_utils import _id_list # 导入工具函数:将参数转换为列表格式,支持多选筛选
class MonthlyPrecollectionReport(models.Model):
@@ -43,8 +44,8 @@ class MonthlyPrecollectionReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 将partner_id转换为列表,用于多选筛选
company_id = _id_list(params.get('company_id'))
management_type_id = params.get('management_type_id')
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
@@ -53,9 +54,9 @@ class MonthlyPrecollectionReport(models.Model):
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('receivable_id.precinct_id', '=', partner_id))
domain.append(('receivable_id.precinct_id', 'in', partner_id)) # 使用in操作符支持多选片区筛选
if company_id:
domain.append(('receivable_id.company_id', '=', company_id))
domain.append(('receivable_id.company_id', 'in', company_id))
if property_id:
domain.append(('receivable_id.property_ids', 'in', [property_id]))
if lessee_id:
@@ -1,6 +1,7 @@
import datetime
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
class ParkingFeeCurrentMonthReceivable(models.Model):
@@ -46,16 +47,16 @@ class ParkingFeeCurrentMonthReceivable(models.Model):
# 其他过滤
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(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))
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
# 管理类型
if management_type_id:
@@ -1,6 +1,7 @@
import datetime
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
class ParkingFeeCurrentMonthReport(models.Model):
@@ -41,16 +42,16 @@ class ParkingFeeCurrentMonthReport(models.Model):
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')
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(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))
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
# 管理类型
if management_type_id:
@@ -1,6 +1,7 @@
import datetime
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
class ParkingFeeYearlyReport(models.Model):
@@ -42,16 +43,16 @@ class ParkingFeeYearlyReport(models.Model):
# 过滤条件
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(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))
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
if management_type_id:
domain.append(('management_type_id', '=', management_type_id))
@@ -2,6 +2,7 @@ import datetime # 日期时间处理模块,用于日期解析、计算、格
import logging # 日志模块,用于调试输出
from odoo import api, models # Odoo 核心 API:api 装饰器、models 模型基类
from .report_utils import _id_list # 导入 ID 列表解析工具函数,支持多选参数
_logger = logging.getLogger(__name__) # 获取当前模块的日志记录器
@@ -42,8 +43,8 @@ class PenaltyArrearsReport(models.Model):
# ========== 二、构建公共筛选条件 ==========
keyword = params.get("keyword") # 关键字搜索
condition = params.get('condition') # 租赁类型
partner_id = params.get('partner_id') # 片区 ID
company_id = params.get('company_id') # 公司 ID
partner_id = _id_list(params.get('partner_id')) # 解析片区 ID 为整数列表(多选支持)
company_id = _id_list(params.get('company_id')) # 公司 ID
property_id = params.get('property_id') # 物业 ID
lessee_id = params.get('lessee_id') # 承租方 ID
contract_id = params.get('contract_id') # 合同 ID
@@ -64,9 +65,9 @@ class PenaltyArrearsReport(models.Model):
return False
if condition and rec.tenure_type != condition:
return False
if partner_id and rec.precinct_id.id != int(partner_id):
if partner_id and rec.precinct_id.id not in partner_id: # 片区过滤(多选支持)
return False
if company_id and rec.company_id.id != int(company_id):
if company_id and rec.company_id.id not in company_id:
return False
if property_id and int(property_id) not in (rec.property_ids.ids or []):
return False
@@ -3,6 +3,7 @@ import logging # 日志模块,用于调试输出
import re # 正则表达式模块,用于解析收费区间字符串
from odoo import api, models # Odoo 核心 API:api 装饰器、models 模型基类
from .report_utils import _id_list # 导入 ID 列表解析工具函数,支持多选参数
_logger = logging.getLogger(__name__) # 获取当前模块的日志记录器
@@ -42,8 +43,8 @@ class PenaltyMonthReceivedReport(models.Model):
# 其他筛选条件参数
keyword = params.get("keyword")
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 解析片区 ID 为整数列表(多选支持)
company_id = _id_list(params.get('company_id'))
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
@@ -64,9 +65,9 @@ class PenaltyMonthReceivedReport(models.Model):
return False
if condition and rec.tenure_type != condition:
return False
if partner_id and rec.precinct_id.id != int(partner_id):
if partner_id and rec.precinct_id.id not in partner_id: # 片区过滤(多选支持)
return False
if company_id and rec.company_id.id != int(company_id):
if company_id and rec.company_id.id not in company_id:
return False
if property_id and int(property_id) not in (rec.property_ids.ids or []):
return False
@@ -90,9 +91,9 @@ class PenaltyMonthReceivedReport(models.Model):
match = True
if not match:
return False
if partner_id and ml.precinct_id and ml.precinct_id.id != int(partner_id):
if partner_id and ml.precinct_id and ml.precinct_id.id not in partner_id: # modify.line 片区过滤(多选支持)
return False
if company_id and ml.company_id and ml.company_id.id != int(company_id):
if company_id and ml.company_id and ml.company_id.id not in company_id:
return False
if lessee_id and (not ml.lessee_id or ml.lessee_id.id != int(lessee_id)):
return False
@@ -3,6 +3,7 @@ import logging # 日志模块,用于调试输出
import re # 正则表达式模块,用于解析收费区间字符串
from odoo import api, models # Odoo 核心 API:api 装饰器、models 模型基类
from .report_utils import _id_list # 导入 ID 列表解析工具函数,支持多选参数
_logger = logging.getLogger(__name__) # 获取当前模块的日志记录器
@@ -44,8 +45,8 @@ class PenaltyYearCumReport(models.Model):
# 其他筛选条件参数
keyword = params.get("keyword")
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 解析片区 ID 为整数列表(多选支持)
company_id = _id_list(params.get('company_id'))
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
@@ -66,9 +67,9 @@ class PenaltyYearCumReport(models.Model):
return False
if condition and rec.tenure_type != condition:
return False
if partner_id and rec.precinct_id.id != int(partner_id):
if partner_id and rec.precinct_id.id not in partner_id: # 片区过滤(多选支持)
return False
if company_id and rec.company_id.id != int(company_id):
if company_id and rec.company_id.id not in company_id:
return False
if property_id and int(property_id) not in (rec.property_ids.ids or []):
return False
@@ -92,9 +93,9 @@ class PenaltyYearCumReport(models.Model):
match = True
if not match:
return False
if partner_id and ml.precinct_id and ml.precinct_id.id != int(partner_id):
if partner_id and ml.precinct_id and ml.precinct_id.id not in partner_id: # modify.line 片区过滤(多选支持)
return False
if company_id and ml.company_id and ml.company_id.id != int(company_id):
if company_id and ml.company_id and ml.company_id.id not in company_id:
return False
if lessee_id and (not ml.lessee_id or ml.lessee_id.id != int(lessee_id)):
return False
+5 -4
View File
@@ -1,6 +1,7 @@
import datetime
from odoo import api, models
from .report_utils import _id_list # 导入工具函数:将参数转换为列表格式,支持多选筛选
class PreAdvanceReport(models.Model):
@@ -50,8 +51,8 @@ class PreAdvanceReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 将partner_id转换为列表,用于多选筛选
company_id = _id_list(params.get('company_id'))
management_type_id = params.get('management_type_id')
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
@@ -60,9 +61,9 @@ class PreAdvanceReport(models.Model):
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('receivable_id.precinct_id', '=', partner_id))
domain.append(('receivable_id.precinct_id', 'in', partner_id)) # 使用in操作符支持多选片区筛选
if company_id:
domain.append(('receivable_id.company_id', '=', company_id))
domain.append(('receivable_id.company_id', 'in', company_id))
if property_id:
domain.append(('receivable_id.property_ids', 'in', [property_id]))
if lessee_id:
+10 -6
View File
@@ -2,6 +2,7 @@ import datetime
import logging
from odoo import api, models
from .report_utils import _id_list
_logger = logging.getLogger(__name__)
@@ -40,8 +41,9 @@ class PreArrearsReport(models.Model):
# 提取筛选参数
keyword = params.get("keyword")
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
# 多选标准化:partner_id 统一为列表
partner_id = _id_list(params.get('partner_id'))
company_id = _id_list(params.get('company_id'))
management_type_id = params.get('management_type_id')
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
@@ -67,9 +69,10 @@ class PreArrearsReport(models.Model):
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('receivable_id.precinct_id', '=', partner_id))
# 多选:片区管理员用 'in' 操作符
domain.append(('receivable_id.precinct_id', 'in', partner_id))
if company_id:
domain.append(('receivable_id.company_id', '=', company_id))
domain.append(('receivable_id.company_id', 'in', company_id))
if property_id:
domain.append(('receivable_id.property_ids', 'in', [property_id]))
if lessee_id:
@@ -151,9 +154,10 @@ class PreArrearsReport(models.Model):
if condition:
receivable_domain.append(('tenure_type', '=', condition))
if partner_id:
receivable_domain.append(('precinct_id', '=', partner_id))
# 多选:片区管理员用 'in' 操作符
receivable_domain.append(('precinct_id', 'in', partner_id))
if company_id:
receivable_domain.append(('company_id', '=', company_id))
receivable_domain.append(('company_id', 'in', company_id))
if property_id:
receivable_domain.append(('property_ids', 'in', [property_id]))
if lessee_id:
@@ -2,6 +2,7 @@ import datetime
import re
from odoo import api, models
from .report_utils import _id_list # 导入工具函数:将参数转换为列表格式,支持多选筛选
class ReceivableFlowReport(models.Model):
@@ -20,13 +21,13 @@ class ReceivableFlowReport(models.Model):
('is_invalid', '!=', True),
]
company_id = params.get('company_id')
company_id = _id_list(params.get('company_id'))
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
if property_id:
domain.append(('property_ids', 'in', [property_id]))
if lessee_id:
@@ -74,8 +75,8 @@ class ReceivableFlowReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 将partner_id转换为列表,用于多选筛选
company_id = _id_list(params.get('company_id'))
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
@@ -83,9 +84,9 @@ class ReceivableFlowReport(models.Model):
if condition:
domain.append(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('precinct_id', 'in', partner_id)) # 使用in操作符支持多选片区筛选
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
if property_id:
domain.append(('property_ids', 'in', [property_id]))
if lessee_id:
+10 -7
View File
@@ -1,6 +1,7 @@
import datetime
from odoo import fields, models, api
from .report_utils import _id_list # 导入工具函数:将参数转换为列表格式,支持多选筛选
class YuthonProperty(models.Model):
@@ -63,15 +64,15 @@ class YuthonProperty(models.Model):
domain.append(("due_date", "<=", date_end))
condition = params.get('condition', False)
partner_id = params.get('partner_id', False)
company_id = params.get('company_id', False)
partner_id = _id_list(params.get('partner_id', False)) # 将partner_id转换为列表,用于多选筛选
company_id = _id_list(params.get('company_id', False))
if condition:
domain.append(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('precinct_id', 'in', partner_id)) # 使用in操作符支持多选片区筛选
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
receivables = self.search(domain, order="contract_id asc, due_date asc")
@@ -163,11 +164,13 @@ class YuthonProperty(models.Model):
def get_domain(self, start_date=False, end_date=False, company_id=False):
if not start_date or not end_date:
return []
if not company_id:
# 多选:company_id 标准化为列表
cid_list = _id_list(company_id)
if not cid_list:
return []
# 这里进行条件的修改
property_records = self.env['yuthon.property'].search([('company_id', '=', company_id)])
# 按公司ID列表查询物业,获取片区管理员ID集合
property_records = self.env['yuthon.property'].search([('company_id', 'in', cid_list)])
user_ids = set()
for property_record in property_records:
+5 -4
View File
@@ -2,6 +2,7 @@ import datetime
import logging
from odoo import api, models
from .report_utils import _id_list # partner_id 多选适配工具
_logger = logging.getLogger(__name__)
@@ -33,8 +34,8 @@ class RentAreaReport(models.Model):
keyword = params.get("keyword")
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
partner_id = _id_list(params.get('partner_id')) # 支持多选,转为列表
company_id = _id_list(params.get('company_id'))
# 前端侧边栏下拉选择器传入的 ID
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
@@ -64,9 +65,9 @@ class RentAreaReport(models.Model):
if condition:
domain.append(('tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('precinct_id', 'in', partner_id)) # 多选用 in
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('company_id', 'in', company_id))
# 物业名称下拉选择器:直接过滤 yuthon_property_id
if property_id:
domain.append(('yuthon_property_id', '=', int(property_id)))
+30
View File
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
"""报表公共工具函数"""
import logging
_logger = logging.getLogger(__name__)
def _id_list(val):
"""将参数标准化为扁平整数列表,用于多选场景下 'in' 操作符
- 列表/元组 → 递归展平后返回列表(防止 [[20, 30]] 嵌套导致 SQL 类型错误)
- 单个整数 → [val]
- None/False/0/空字符串 → [](空列表,使 if partner_id 为 False,不追加条件)
单选时 _id_list(20) → [20],配合 'in' [20] 等价于原来的 = 20,数据一致
多选时 _id_list([20, 30]) → [20, 30],配合 'in' [20, 30] 正常
嵌套时 _id_list([[20, 30]]) → [20, 30],防止 psycopg2 把 list 转为 ARRAY 导致 SQL 类型错误
"""
if isinstance(val, (list, tuple)):
result = []
for item in val:
# 递归展平嵌套的列表/元组
if isinstance(item, (list, tuple)):
result.extend(_id_list(item))
elif item is not None and item is not False and item != 0 and item != '':
result.append(item)
return result
if val is None or val is False or val == 0 or val == '':
return []
return [val]
@@ -1,3 +1,4 @@
from .report_utils import _id_list
from odoo import api, models
from datetime import datetime
from dateutil.relativedelta import relativedelta
@@ -15,7 +16,7 @@ class StaffPositionReport(models.Model):
# 不再过滤is_enable和establishment_quotas,查询所有岗位
domain = []
company_id = params.get("company_id")
company_id = _id_list(params.get('company_id'))
department_id = params.get("department_id")
keyword = params.get("keyword")
date_end = params.get("date_end") # 获取截止日期
+2 -1
View File
@@ -2,6 +2,7 @@
import datetime
import calendar
from .report_utils import _id_list
from odoo import api, models
@@ -31,7 +32,7 @@ class TextSummaryReport(models.Model):
query_date = today
# 获取筛选条件
company_id = params.get('company_id')
company_id = _id_list(params.get('company_id'))
precinct_id = params.get('partner_id')
# 构建报表数据结构
@@ -2,6 +2,7 @@ import datetime
import calendar
from odoo import api, models
from .report_utils import _id_list
class TotalReceivedReport(models.Model):
@@ -38,8 +39,9 @@ class TotalReceivedReport(models.Model):
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
# 多选标准化:partner_id 统一为列表,配合 'in' 操作符
partner_id = _id_list(params.get('partner_id'))
company_id = _id_list(params.get('company_id'))
management_type_id = params.get('management_type_id')
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
@@ -47,10 +49,11 @@ class TotalReceivedReport(models.Model):
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
# 片区管理员:多选,用 'in' 操作符
if partner_id:
domain.append(('receivable_id.precinct_id', '=', partner_id))
domain.append(('receivable_id.precinct_id', 'in', partner_id))
if company_id:
domain.append(('receivable_id.company_id', '=', company_id))
domain.append(('receivable_id.company_id', 'in', company_id))
if management_type_id:
domain.append(('receivable_id.management_type_id', '=', management_type_id))
if property_id:
+39 -16
View File
@@ -4,6 +4,7 @@ import calendar
import logging
from odoo import api, models
from .report_utils import _id_list
_logger = logging.getLogger(__name__)
@@ -150,6 +151,7 @@ class YuthonDashboard(models.Model):
# ==================== 汇总信息 ====================
filters = {
'统计时间': f"{query_date.year}-{query_date.month:02d}",
# 多选:所属公司传ID列表
'所属公司': self._get_company_display(params.get('company_id')),
'管理员': self._get_admin_display(params.get('partner_id')),
}
@@ -157,10 +159,12 @@ class YuthonDashboard(models.Model):
# ==================== 构建筛选条件上下文(透传给子报表) ====================
# 子报表从 action.context.dashboard_filter 中读取并初始化筛选状态
# date_str: "YYYY-MM" 格式,子报表据此还原 DateTime 对象
# 多选:company_id/partner_id 透传ID列表,名称拼接
cid_list = _id_list(params.get('company_id'))
dashboard_filter = {
'date_str': f"{query_date.year}-{query_date.month:02d}" if has_date else '',
'company_id': params.get('company_id') or False,
'company_name': self._get_company_display(params.get('company_id')) if params.get('company_id') else '',
'company_id': cid_list or False,
'company_name': self._get_company_display(params.get('company_id')) if cid_list else '',
'partner_id': params.get('partner_id') or False,
'partner_name': self._get_admin_display(params.get('partner_id')) if params.get('partner_id') else '',
'condition': params.get('condition') or '',
@@ -276,24 +280,40 @@ class YuthonDashboard(models.Model):
# -------------------------------------------------------
def _get_company_display(self, company_id):
"""根据公司ID列表获取公司显示名(多选支持)
company_id 是 yuthon.property.company 的 ID 列表(多选)或单值
"""
if not company_id:
return '全部'
try:
company = self.env['yuthon.property.company'].browse(int(company_id))
return company.name if company else '未知'
# 多选:company_id 是列表,获取所有名称用逗号拼接
cid_list = _id_list(company_id)
if not cid_list:
return '全部'
companies = self.env['yuthon.property.company'].browse(cid_list)
names = [c.name for c in companies if c.name]
return ', '.join(names) if names else '未知'
except Exception:
return '未知'
def _get_admin_display(self, partner_id):
"""根据片区ID获取管理员显示名
partner_id 是 yuthon.precinct.message 的 ID(前端 Many2XAutocomplete 绑定该模型)
"""根据片区ID列表获取管理员显示名(多选支持)
partner_id 是 yuthon.precinct.message 的 ID 列表(多选)或单值
display_name 已自动拼接为 "{片区名称}{管理员姓名}",如 "二B梁嘉辉"
"""
if not partner_id:
return '全部'
try:
precinct = self.env['yuthon.precinct.message'].browse(int(partner_id))
return precinct.display_name if precinct else '未知'
# 多选:partner_id 是列表,获取所有名称用逗号拼接
pid_list = _id_list(partner_id)
if not pid_list:
return '全部'
names = []
for pid in pid_list:
precinct = self.env['yuthon.precinct.message'].browse(pid)
if precinct:
names.append(precinct.display_name)
return '、'.join(names) if names else '未知'
except Exception:
return '未知'
@@ -304,8 +324,9 @@ class YuthonDashboard(models.Model):
last_day = calendar.monthrange(query_date.year, query_date.month)[1]
month_last = query_date.replace(day=last_day)
company_id = base_params.get('company_id')
partner_id = base_params.get('partner_id')
company_id = _id_list(base_params.get('company_id'))
# 多选标准化:partner_id 统一为列表
partner_id = _id_list(base_params.get('partner_id'))
keyword = base_params.get('keyword')
# 按费用类型分别统计(数据源:property.bills.list)
@@ -323,11 +344,12 @@ class YuthonDashboard(models.Model):
('actual_date', '<=', month_last),
]
if company_id:
domain.append(('lessor_id', '=', int(company_id)))
domain.append(('lessor_id', 'in', company_id))
if partner_id:
# 多选:片区管理员用 'in' 操作符
domain += ['|',
('contract_id.precinct_id', '=', int(partner_id)),
('receivables_id.precinct_id', '=', int(partner_id)),
('contract_id.precinct_id', 'in', partner_id),
('receivables_id.precinct_id', 'in', partner_id),
]
if keyword:
domain += ['|', '|', '|',
@@ -347,11 +369,12 @@ class YuthonDashboard(models.Model):
('actual_date', '<=', month_last),
]
if company_id:
year_domain.append(('lessor_id', '=', int(company_id)))
year_domain.append(('lessor_id', 'in', company_id))
if partner_id:
# 多选:片区管理员用 'in' 操作符
year_domain += ['|',
('contract_id.precinct_id', '=', int(partner_id)),
('receivables_id.precinct_id', '=', int(partner_id)),
('contract_id.precinct_id', 'in', partner_id),
('receivables_id.precinct_id', 'in', partner_id),
]
year_records = self.env['property.bills.list'].sudo().search(year_domain)
year_total = sum((r.amount_actual or 0.0) for r in year_records)
@@ -5,6 +5,7 @@ import { Component, onMounted, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { standardActionServiceProps } from "@web/webclient/actions/action_service";
import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
import { MultiSelectAutocomplete } from "../yuthon_report/multi_select_autocomplete";
class YuthonDashboard extends Component {
setup() {
@@ -18,8 +19,10 @@ class YuthonDashboard extends Component {
loading: false,
collapsedSections: {},
// 筛选器
selectCompany: {},
selectPartner: {},
// 多选:所属公司改为数组
selectCompany: [],
// 多选:片区管理员改为数组
selectPartner: [],
condition: "",
conditions: [
{ name: "", value: "" },
@@ -37,12 +40,15 @@ class YuthonDashboard extends Component {
const cached = sessionStorage.getItem("yuthon_dashboard_cache");
if (cached) {
const snapshot = JSON.parse(cached);
// 从缓存恢复时,selectCompany/selectPartner 必须是数组,
// 旧格式({})或非数组值一律回退为空数组,防止 .map() 报错
const safeArr = (val) => (Array.isArray(val) ? val : []);
Object.assign(this.state, {
dateStart: snapshot.dateStart || this.state.dateStart,
selectCompany: snapshot.selectCompany || this.state.selectCompany,
selectPartner: snapshot.selectPartner || this.state.selectPartner,
selectCompany: safeArr(snapshot.selectCompany),
selectPartner: safeArr(snapshot.selectPartner),
condition: snapshot.condition || this.state.condition,
domainUsers: snapshot.domainUsers || this.state.domainUsers,
domainUsers: safeArr(snapshot.domainUsers),
dashboardData: snapshot.dashboardData || this.state.dashboardData,
collapsedSections: snapshot.collapsedSections || this.state.collapsedSections,
});
@@ -86,8 +92,8 @@ class YuthonDashboard extends Component {
}
getDomain() {
const isCompanyEmpty = Object.keys(this.state.selectCompany).length === 0;
if (isCompanyEmpty) {
// 多选:检查公司数组是否为空
if (!this.state.selectCompany.length) {
return [];
} else {
return [["id", "in", this.state.domainUsers]];
@@ -95,7 +101,8 @@ class YuthonDashboard extends Component {
}
async searchUserDomain() {
this.state.selectPartner = {};
// 多选:重置片区管理员为空数组
this.state.selectPartner = [];
// get_domain 需要 start_date 和 end_date,否则直接返回 []
// 从当前选择的月份推算,没选月份就用当月
@@ -115,6 +122,8 @@ class YuthonDashboard extends Component {
endDate = `${y}-${m}-${String(lastDay).padStart(2, "0")}`;
}
// 多选:传公司ID列表
const companyIdList = this.state.selectCompany.map(item => item.id);
const results = await this.serviceOrm.call(
"property.accounts.receivable",
"get_domain",
@@ -122,33 +131,28 @@ class YuthonDashboard extends Component {
{
start_date: startDate,
end_date: endDate,
company_id: this.state.selectCompany?.id,
company_id: companyIdList,
}
);
this.state.domainUsers = results || [];
}
async onUpdateCompany(v) {
if (!v || !v[0]) {
this.state.selectCompany = {};
this.state.selectPartner = {};
this.state.domainUsers = [];
await this.onSearch();
return;
async onUpdateCompany(items) {
// 多选:items 是已选项数组 [{id, display_name}, ...]
this.state.selectCompany = items;
// 多选:片区管理员重置为空数组
this.state.selectPartner = [];
this.state.domainUsers = [];
// 有公司选中时才查询domain
if (items.length) {
await this.searchUserDomain();
}
const values = await this.serviceOrm.call(
"yuthon.property.company",
"read",
[[v[0].id]],
{ fields: ["display_name"] }
);
this.state.selectCompany = values[0] || {};
await this.searchUserDomain();
await this.onSearch();
}
async onUpdatePartner(v) {
this.state.selectPartner = v[0] || {};
async onUpdatePartner(items) {
// 多选:items 是已选项数组 [{id, display_name}, ...]
this.state.selectPartner = items;
await this.onSearch();
}
@@ -164,8 +168,10 @@ class YuthonDashboard extends Component {
const params = {
date_start: this.state.dateStart,
keyword: this.state.keyword,
company_id: this.state.selectCompany?.id || false,
partner_id: this.state.selectPartner?.id || false,
// 多选:所属公司传ID列表
company_id: this.state.selectCompany.map(item => item.id),
// 多选:片区管理员传ID列表
partner_id: this.state.selectPartner.map(item => item.id),
condition: this.state.condition || false,
};
@@ -205,10 +211,12 @@ class YuthonDashboard extends Component {
// 构建当前看板的筛选参数,透传给子报表
const dashboardFilter = {
date_str: this.state.dateStart || "", // "YYYY-MM"
company_id: this.state.selectCompany?.id || false,
company_name: this.state.selectCompany?.display_name || "",
partner_id: this.state.selectPartner?.id || false,
partner_name: this.state.selectPartner?.display_name || "",
// 多选:所属公司传ID列表和名称拼接
company_id: this.state.selectCompany.map(item => item.id),
company_name: this.state.selectCompany.map(item => item.display_name).join(", "),
// 多选:片区管理员传ID列表和名称拼接
partner_id: this.state.selectPartner.map(item => item.id),
partner_name: this.state.selectPartner.map(item => item.display_name).join(", "),
condition: this.state.condition || "",
};
@@ -247,6 +255,7 @@ YuthonDashboard.props = { ...standardActionServiceProps };
YuthonDashboard.template = "yuthon_dashboard.YuthonDashboard";
YuthonDashboard.components = {
Many2XAutocomplete,
MultiSelectAutocomplete,
};
registry.category("actions").add("yuthon_dashboard_client", YuthonDashboard);
@@ -18,14 +18,15 @@
</div>
<div style="width: 160px;">
<Many2XAutocomplete
value="state.selectCompany?.display_name"
getDomain="getDomainCompany"
<!-- 所属公司(多选) -->
<MultiSelectAutocomplete
value="state.selectCompany"
getDomain="() => this.getDomainCompany()"
resModel="'yuthon.property.company'"
fieldString="'所属公司'"
placeholder="'所属公司'"
activeActions="{}"
update.bind="onUpdateCompany"
update="(items) => this.onUpdateCompany(items)"
onSearch="() => this.onSearch()"
/>
</div>
@@ -43,14 +44,15 @@
</div>
<div style="width: 160px;">
<Many2XAutocomplete
value="state.selectPartner?.display_name"
getDomain.bind="getDomain"
<!-- 片区管理员(多选) -->
<MultiSelectAutocomplete
value="state.selectPartner"
getDomain="() => this.getDomain()"
resModel="'yuthon.precinct.message'"
fieldString="'片区管理员'"
placeholder="'片区管理员'"
activeActions="{}"
update.bind="onUpdatePartner"
update="(items) => this.onUpdatePartner(items)"
onSearch="() => this.onSearch()"
/>
</div>
@@ -10,6 +10,7 @@ import {
import {DateTimeInput} from "@web/core/datetime/datetime_input";
import {CheckBox} from "@web/core/checkbox/checkbox";
import {Many2XAutocomplete} from "@web/views/fields/relational_utils";
import {MultiSelectAutocomplete} from "./multi_select_autocomplete";
import {formatDate} from "@web/core/l10n/dates"
const {DateTime} = luxon;
@@ -27,7 +28,8 @@ class ReceivableReport extends Component {
keyword: "",
booleanValue: true,
selectDepartment: {},
selectCompany: {},
// 多选:所属公司改为数组
selectCompany: [],
selectStructure: {},
domainDepartment:[],
domainStructure:[],
@@ -101,8 +103,8 @@ class ReceivableReport extends Component {
getDomainStructure() {
console.log(this.state)
const isCompanyEmpty = Object.keys(this.state.selectCompany).length === 0;
if (isCompanyEmpty) {
// 多选:检查公司数组是否为空
if (!this.state.selectCompany.length) {
return [];
} else {
return [['id','in', this.state.domainStructure]];
@@ -112,8 +114,8 @@ class ReceivableReport extends Component {
getDomainDepartment() {
console.log(this.state)
const isCompanyEmpty = Object.keys(this.state.selectCompany).length === 0;
if (isCompanyEmpty) {
// 多选:检查公司数组是否为空
if (!this.state.selectCompany.length) {
return [];
} else {
return [['id','in', this.state.domainDepartment]];
@@ -121,12 +123,14 @@ class ReceivableReport extends Component {
}
async searchDomain() {
// 多选:传公司ID列表
const companyIdList = this.state.selectCompany.map(item => item.id)
let results = await this.serviceOrm.call(
'hr.attendance',
'get_domain',
[],
{
company_id: this.state.selectCompany?.id,
company_id: companyIdList,
}
)
console.log(results)
@@ -143,7 +147,8 @@ class ReceivableReport extends Component {
date_start: `${startDate} 00:00:00`,
date_end: `${endDate} 23:59:59`,
department_id: this.state.selectDepartment?.id,
company_id: this.state.selectCompany?.id,
// 多选:所属公司传ID列表
company_id: this.state.selectCompany.map(item => item.id),
structure_id: this.state.selectStructure?.id,
};
const result = await this.serviceOrm.call(
@@ -169,9 +174,12 @@ class ReceivableReport extends Component {
});
}
async onUpdateCompany(v) {
this.state.selectCompany = v[0] || {}
await this.searchDomain()
async onUpdateCompany(items) {
// 多选:items 是已选项数组
this.state.selectCompany = items
if (items.length) {
await this.searchDomain()
}
// 自动搜索
await this.onSearch()
}
@@ -211,6 +219,7 @@ ReceivableReport.components = {
DateTimeInput,
CheckBox,
Many2XAutocomplete,
MultiSelectAutocomplete,
};
registry.category("actions").add("att_report_client", ReceivableReport);
@@ -36,16 +36,16 @@
</div>
</div>
<!-- 所属公司 -->
<!-- 所属公司(多选) -->
<div class="col">
<Many2XAutocomplete
value="state.selectCompany?.display_name"
getDomain="getDomainCompany"
<MultiSelectAutocomplete
value="state.selectCompany"
getDomain="() => this.getDomainCompany()"
resModel="'res.company'"
fieldString="'所属公司'"
placeholder="'所属公司'"
activeActions="{}"
update.bind="onUpdateCompany"
update="(items) => this.onUpdateCompany(items)"
onSearch="() => this.onSearch()"
/>
</div>
@@ -0,0 +1,220 @@
/** @odoo-module **/
/**
* 多选自动完成组件(MultiSelectAutocomplete)
* 用于报表搜索栏,支持选择多个记录,以标签(chip)形式显示已选项
* 基于 Odoo 的 name_search API 实现搜索
*/
import { Component, onMounted, useRef, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
export class MultiSelectAutocomplete extends Component {
setup() {
this.orm = useService("orm");
this.inputRef = useRef("input");
this.containerRef = useRef("container");
// 响应式状态
this.state = useState({
searchText: "", // 输入框中的搜索文字
options: [], // 下拉候选列表
showDropdown: false, // 是否显示下拉框
isLoading: false, // 是否正在加载
});
// 绑定外部点击关闭事件
this._boundCloseOutside = this._closeOnOutside.bind(this);
}
mounted() {
document.addEventListener("click", this._boundCloseOutside);
}
willUnmount() {
document.removeEventListener("click", this._boundCloseOutside);
}
/**
* 点击外部区域时关闭下拉框
*/
_closeOnOutside(ev) {
if (this.containerRef.el && !this.containerRef.el.contains(ev.target)) {
this.state.showDropdown = false;
}
}
/** 已选项列表(从 props.value 获取) */
get selectedItems() {
return this.props.value || [];
}
/** 获取已选ID列表(用于排除已选的) */
getSelectedIds() {
return this.selectedItems.map((item) => item.id);
}
/**
* 调用 Odoo 的 name_search 搜索候选记录
*/
async _searchRecords(name) {
const limit = this.props.searchLimit || 20;
let domain = [];
// getDomain 是可选的,没有传则用空 domain(不过滤)
if (this.props.getDomain) {
try {
domain = this.props.getDomain();
} catch (e) {
console.error("[MultiSelect] getDomain error:", e);
domain = [];
}
}
// 排除已选中的记录
const excludeIds = this.getSelectedIds();
if (excludeIds.length > 0) {
domain.push(["id", "not in", excludeIds]);
}
// 调用后端 name_search 接口获取候选列表
const results = await this.orm.call(
this.props.resModel,
"name_search",
[],
{
name: name || "",
operator: "ilike",
args: domain,
limit: limit,
context: this.props.context || {},
}
);
// 将 [id, display_name] 格式转为对象数组
return results.map((r) => ({
id: r[0],
display_name: r[1] ? r[1].split("\n")[0] : "未命名",
}));
}
/**
* 输入框获得焦点时:立即打开下拉并开始搜索
*/
async onInputFocus() {
// 立即显示下拉框(先显示加载中,数据到了再更新)
this.state.showDropdown = true;
await this._doSearch();
}
/**
* 输入框失焦时:延迟关闭下拉(让点击候选项的事件先执行)
*/
onInputBlur() {
// 延迟关闭,避免点击候选项时先 blur 关闭了下拉,导致点击失效
setTimeout(() => {
this.state.showDropdown = false;
}, 150);
}
/**
* 输入内容变化时:重新搜索
*/
async onInputChange(ev) {
// 从 DOM 直接读取输入值(避免 t-att-value 重置问题)
this.state.searchText = ev.target.value;
this.state.showDropdown = true;
await this._doSearch();
}
/**
* 执行搜索:调用后端接口更新候选项
*/
async _doSearch() {
this.state.isLoading = true;
try {
const options = await this._searchRecords(this.state.searchText);
this.state.options = options;
} catch (e) {
console.error("[MultiSelect] search error:", e);
this.state.options = [];
} finally {
this.state.isLoading = false;
}
}
/**
* 点击下拉候选项:添加到已选列表
*/
onSelect(option) {
const newItem = { id: option.id, display_name: option.display_name };
const currentItems = [...this.selectedItems];
// 防止重复添加
if (!currentItems.find((item) => item.id === newItem.id)) {
currentItems.push(newItem);
}
// 通知父组件更新
this.props.update(currentItems);
// 清空搜索文字,关闭下拉
this.state.searchText = "";
this.state.showDropdown = false;
// 选中后触发搜索回调(如报表查询)
if (this.props.searchOnChange !== false && this.props.onSearch) {
this.props.onSearch();
}
}
/**
* 点击标签删除按钮:移除一个已选项
*/
onRemoveItem(id) {
const currentItems = this.selectedItems.filter((item) => item.id !== id);
this.props.update([...currentItems]);
if (this.props.searchOnChange !== false && this.props.onSearch) {
this.props.onSearch();
}
}
/**
* 点击清空按钮:清空所有已选项
*/
clearAll() {
this.props.update([]);
if (this.props.searchOnChange !== false && this.props.onSearch) {
this.props.onSearch();
}
}
/**
* 键盘事件:Backspace 删除最后一项,Enter 阻止默认行为
*/
onKeyDown(ev) {
if (ev.key === "Backspace" && !this.state.searchText && this.selectedItems.length > 0) {
// 删除键且输入为空时,移除最后一个已选项
const lastItem = this.selectedItems[this.selectedItems.length - 1];
this.onRemoveItem(lastItem.id);
} else if (ev.key === "Enter") {
// 回车时阻止表单提交
ev.preventDefault();
}
}
}
// 注册模板和属性
MultiSelectAutocomplete.template = "yuthon_report.MultiSelectAutocomplete";
MultiSelectAutocomplete.props = {
value: { type: Array, optional: true },
resModel: String,
getDomain: { type: Function, optional: true },
update: Function,
placeholder: { type: String, optional: true },
fieldString: { type: String, optional: true },
context: { type: Object, optional: true },
searchLimit: { type: Number, optional: true },
/** 选中/删除后是否自动调用 onSearch 回调,默认 true */
searchOnChange: { type: Boolean, optional: true },
/** 选中/删除后的回调(如触发报表查询) */
onSearch: { type: Function, optional: true },
};
MultiSelectAutocomplete.defaultProps = {
value: [],
searchLimit: 20,
searchOnChange: true,
context: {},
};
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<!-- 多选自动完成组件:标签 + 输入框 + 下拉候选列表 -->
<t t-name="yuthon_report.MultiSelectAutocomplete">
<div class="multi-select-autocomplete" t-ref="container" style="position:relative; width:100%;">
<!-- 已选标签区域 + 搜索输入框 -->
<div class="multi-select-input-wrapper"
style="display:flex; flex-wrap:wrap; align-items:center; gap:3px;
border:1px solid #ced4da; border-radius:0.25rem; padding:2px 6px;
min-height:34px; background:#fff; cursor:text;"
t-on-click="() => { this.inputRef.el &amp;&amp; this.inputRef.el.focus(); }">
<!-- 已选标签(chip) -->
<t t-foreach="selectedItems" t-as="item" t-key="item.id">
<span class="badge bg-light text-dark border d-flex align-items-center"
style="font-size:0.78rem; padding:2px 7px; cursor:pointer;"
t-on-mousedown="(ev) => ev.preventDefault()"
title="点击删除">
<t t-esc="item.display_name"/>
<i class="oi oi-close ms-1" style="font-size:10px;" role="presentation"
t-on-click.stop.prevent="() => this.onRemoveItem(item.id)"/>
</span>
</t>
<!-- 搜索输入框(不使用 t-att-value 避免重置问题) -->
<input type="text"
t-ref="input"
class="multi-select-search-input border-0 outline-none"
style="flex:1; min-width:80px; padding:3px 0; font-size:0.88rem; background:transparent;"
t-att-placeholder="props.placeholder || props.fieldString || '搜索...'"
t-on-focus="onInputFocus"
t-on-blur="onInputBlur"
t-on-input="onInputChange"
t-on-keydown="onKeyDown"/>
</div>
<!-- 下拉候选列表(点击输入框或获得焦点时显示) -->
<div t-if="state.showDropdown"
class="dropdown-menu show position-absolute w-100 mt-1 shadow"
style="max-height:220px; overflow-y:auto; z-index:1050; display:block;">
<!-- 状态1:正在加载 -->
<div t-if="state.isLoading" class="dropdown-item text-muted text-center py-2">
搜索中...
</div>
<!-- 状态2:加载完成 - 分两种情况 -->
<t t-if="!state.isLoading">
<!-- 2a:没有候选项 -->
<div t-if="state.options.length === 0"
class="dropdown-item text-muted text-center py-2">
无匹配记录
</div>
<!-- 2b:有候选项列表 -->
<t t-if="state.options.length > 0">
<a t-foreach="state.options" t-as="opt" t-key="opt.id"
class="dropdown-item d-flex justify-content-between align-items-center"
href="#"
style="cursor:pointer; font-size:0.88rem;"
t-on-click.stop.prevent="(ev) => this.onSelect(opt)"
t-on-mousedown="(ev) => ev.preventDefault()">
<span t-esc="opt.display_name"/>
</a>
</t>
</t>
</div>
</div>
</t>
</templates>
@@ -10,6 +10,7 @@ import {
import {DateTimeInput} from "@web/core/datetime/datetime_input";
import {CheckBox} from "@web/core/checkbox/checkbox";
import {Many2XAutocomplete} from "@web/views/fields/relational_utils";
import {MultiSelectAutocomplete} from "./multi_select_autocomplete";
import {formatDate} from "@web/core/l10n/dates"
const {DateTime} = luxon;
@@ -30,8 +31,9 @@ class ReceivableFlowReport extends Component {
singleDate: DateTime.now().startOf("month"),
keyword: "",
booleanValue: true,
selectPartner: {},
selectCompany: {},
// 多选:片区管理员和所属公司改为数组
selectPartner: [],
selectCompany: [],
selectManagementType: {},
selectProperty: {},
selectLessee: {},
@@ -287,30 +289,33 @@ class ReceivableFlowReport extends Component {
}
getDomain() {
const isCompanyEmpty = Object.keys(this.state.selectCompany).length === 0;
if (isCompanyEmpty) {
// 多选:检查公司数组是否为空
if (!this.state.selectCompany.length) {
return [];
} else {
return [['id', 'in', this.state.domainUsers]];
}
}
async onUpdatePartner(v) {
this.state.selectPartner = v[0]
async onUpdatePartner(items) {
// 多选:items 是已选项数组
this.state.selectPartner = items
await this.onSearch()
}
async searchUserDomain() {
this.state.selectPartner = {}
this.state.selectPartner = []
let startDate = formatDate(this.state.startDate, {format: "yyyy-MM-dd"})
let endDate = formatDate(this.state.endDate, {format: "yyyy-MM-dd"})
// 多选:传公司ID列表
const companyIdList = this.state.selectCompany.map(item => item.id)
let results = await this.serviceOrm.call(
'property.accounts.receivable',
'get_domain',
[],
{
company_id: this.state.selectCompany?.id,
company_id: companyIdList,
start_date: startDate,
end_date: endDate,
}
@@ -318,24 +323,14 @@ class ReceivableFlowReport extends Component {
this.state.domainUsers = results
}
async onUpdateCompany(v) {
if (!v || !v[0]) {
this.state.selectCompany = {};
this.state.selectPartner = {};
this.state.domainUsers = [];
await this.onSearch();
return;
async onUpdateCompany(items) {
// 多选:items 是已选项数组
this.state.selectCompany = items
this.state.selectPartner = [];
this.state.domainUsers = [];
if (items.length) {
await this.searchUserDomain()
}
let values = await this.serviceOrm.call(
'yuthon.property.company',
'read',
[[v[0].id]],
{
fields: ['display_name'],
}
)
this.state.selectCompany = values[0] || {}
await this.searchUserDomain()
await this.onSearch()
}
@@ -369,8 +364,9 @@ class ReceivableFlowReport extends Component {
date_end: `${singleDate} 23:59:59`,
keyword: this.state.keyword || false,
condition: this.state.condition || false,
partner_id: this.state.selectPartner?.id,
company_id: this.state.selectCompany?.id,
// 多选:片区管理员、所属公司传ID列表
partner_id: this.state.selectPartner.map(item => item.id),
company_id: this.state.selectCompany.map(item => item.id),
management_type_id: this.state.selectManagementType?.id || false,
property_id: this.state.selectProperty?.id || false,
lessee_id: this.state.selectLessee?.id || false,
@@ -384,8 +380,9 @@ class ReceivableFlowReport extends Component {
date_end: `${endDate} 23:59:59`,
keyword: this.state.keyword || false,
condition: this.state.condition || false,
partner_id: this.state.selectPartner?.id,
company_id: this.state.selectCompany?.id,
// 多选:片区管理员、所属公司传ID列表
partner_id: this.state.selectPartner.map(item => item.id),
company_id: this.state.selectCompany.map(item => item.id),
management_type_id: this.state.selectManagementType?.id || false,
property_id: this.state.selectProperty?.id || false,
lessee_id: this.state.selectLessee?.id || false,
@@ -552,6 +549,7 @@ ReceivableFlowReport.components = {
DateTimeInput,
CheckBox,
Many2XAutocomplete,
MultiSelectAutocomplete,
};
registry.category("actions").add("receivable_flow_report_client", ReceivableFlowReport);
@@ -36,29 +36,31 @@
</div>
</div>
<!-- 所属片区 -->
<!-- 所属片区(多选) -->
<div class="col-3">
<label>所属片区</label>
<Many2XAutocomplete
value="state.selectPrecinct?.display_name"
getDomain="getDomainPrecinct"
<MultiSelectAutocomplete
value="state.selectPrecinct"
getDomain="() => this.getDomainPrecinct()"
resModel="'yuthon.precinct.message'"
fieldString="'所属片区'"
activeActions="{}"
update.bind="onUpdatePrecinct"
placeholder="'所属片区'"
update="(items) => this.onUpdatePrecinct(items)"
onSearch="() => this.onSearch()"
/>
</div>
<!-- 所属公司 -->
<!-- 所属公司(多选) -->
<div class="col-3">
<label>所属公司</label>
<Many2XAutocomplete
value="state.selectCompany?.display_name"
getDomain="getDomainCompany"
<MultiSelectAutocomplete
value="state.selectCompany"
getDomain="() => this.getDomainCompany()"
resModel="'yuthon.property.company'"
fieldString="'所属公司'"
activeActions="{}"
update.bind="onUpdateCompany"
placeholder="'所属公司'"
update="(items) => this.onUpdateCompany(items)"
onSearch="() => this.onSearch()"
/>
</div>
@@ -6,6 +6,7 @@ import {useService} from "@web/core/utils/hooks";
import {standardActionServiceProps} from "@web/webclient/actions/action_service";
import {DateTimeInput} from "@web/core/datetime/datetime_input";
import {Many2XAutocomplete} from "@web/views/fields/relational_utils";
import {MultiSelectAutocomplete} from "./multi_select_autocomplete";
import {formatDate} from "@web/core/l10n/dates"
const {DateTime} = luxon;
@@ -19,8 +20,9 @@ class ReceiptLedgerReport extends Component {
this.state = useState({
startDate: null,
endDate: null,
selectPrecinct: {},
selectCompany: {},
// 多选:片区管理员和所属公司改为数组
selectPrecinct: [],
selectCompany: [],
selectManagementType: {},
conditions: [{
'name': '自有',
@@ -74,15 +76,16 @@ class ReceiptLedgerReport extends Component {
return [];
}
// 选择所属片区
async onUpdatePrecinct(v) {
console.log("选择所属片区:", v);
this.state.selectPrecinct = v[0] || {};
// 选择所属片区(多选)
async onUpdatePrecinct(items) {
console.log("选择所属片区:", items);
this.state.selectPrecinct = items;
}
async onUpdateCompany(v) {
console.log("选择所属公司:", v);
this.state.selectCompany = v[0] || {};
// 选择所属公司(多选)
async onUpdateCompany(items) {
console.log("选择所属公司:", items);
this.state.selectCompany = items;
}
async onUpdateManagementType(v) {
@@ -109,10 +112,11 @@ class ReceiptLedgerReport extends Component {
const params = {
date_start: this.state.startDate,
date_end: this.state.endDate,
precinct_id: this.state.selectPrecinct?.id || false,
// 多选:片区管理员和所属公司传ID列表
precinct_id: this.state.selectPrecinct.map(item => item.id),
condition: this.state.condition || false,
company_id: this.state.selectCompany?.id || false,
company_id: this.state.selectCompany.map(item => item.id),
management_type_id: this.state.selectManagementType?.id || false,
};
@@ -142,6 +146,7 @@ ReceiptLedgerReport.template = "yuthon_report.ReceiptLedgerReport";
ReceiptLedgerReport.components = {
DateTimeInput,
Many2XAutocomplete,
MultiSelectAutocomplete,
};
registry.category("actions").add("receipt_ledger_report_client", ReceiptLedgerReport);
@@ -10,6 +10,7 @@ import {
import {DateTimeInput} from "@web/core/datetime/datetime_input";
import {CheckBox} from "@web/core/checkbox/checkbox";
import {Many2XAutocomplete} from "@web/views/fields/relational_utils";
import {MultiSelectAutocomplete} from "./multi_select_autocomplete";
import {formatDate} from "@web/core/l10n/dates"
const {DateTime} = luxon;
@@ -40,14 +41,32 @@ class ReceivableReport extends Component {
initSingleDate = parsed.startOf("month");
}
}
// 解析所属公司:有 id+name 才初始化,否则为空
const initCompany = df.company_id
? { id: df.company_id, display_name: df.company_name || '' }
: {};
// 解析片区管理员
const initPartner = df.partner_id
? { id: df.partner_id, display_name: df.partner_name || '' }
: {};
// 解析所属公司(多选:从看板跳转时 company_id 可能是数组 [1, 2] 或单值 1)
let initCompany = [];
if (df.company_id) {
// company_name 可能是逗号拼接的多个名称
const nameList = (df.company_name || '').split(/[,,、]/).map(s => s.trim()).filter(Boolean);
// 统一转为数组
const cidArr = Array.isArray(df.company_id) ? df.company_id : [df.company_id];
// 每个 ID 创建一个选项对象
initCompany = cidArr.map((cid, idx) => ({
id: cid,
display_name: nameList[idx] || '',
}));
}
// 解析片区管理员(多选:从看板跳转时 partner_id 可能是数组 [20, 30] 或单值 20)
let initPartner = [];
if (df.partner_id) {
// partner_name 可能是逗号拼接的多个名称
const nameList = (df.partner_name || '').split(/[,,、]/).map(s => s.trim()).filter(Boolean);
// 统一转为数组
const pidArr = Array.isArray(df.partner_id) ? df.partner_id : [df.partner_id];
// 每个 ID 创建一个选项对象
initPartner = pidArr.map((pid, idx) => ({
id: pid,
display_name: nameList[idx] || '',
}));
}
// 解析权属类型
const initCondition = df.condition || false;
@@ -313,8 +332,8 @@ class ReceivableReport extends Component {
getDomain() {
console.log(this.state)
const isCompanyEmpty = Object.keys(this.state.selectCompany).length === 0;
if (isCompanyEmpty) {
// 多选:检查公司数组是否为空
if (!this.state.selectCompany.length) {
return [];
} else {
return [['id', 'in', this.state.domainUsers]];
@@ -322,24 +341,27 @@ class ReceivableReport extends Component {
}
async onUpdatePartner(v) {
console.log(v)
this.state.selectPartner = v[0]
async onUpdatePartner(items) {
// 多选:items 是已选项数组 [{id, display_name}, ...]
this.state.selectPartner = items
// 自动搜索
await this.onSearch()
}
async searchUserDomain() {
this.state.selectPartner = {}
// 多选:重置片区管理员为空数组
this.state.selectPartner = []
let startDate = formatDate(this.state.startDate, {format: "yyyy-MM-dd"})
let endDate = formatDate(this.state.endDate, {format: "yyyy-MM-dd"})
// 多选:传公司ID列表
const companyIdList = this.state.selectCompany.map(item => item.id)
let results = await this.serviceOrm.call(
'property.accounts.receivable',
'get_domain',
[],
{
company_id: this.state.selectCompany?.id,
company_id: companyIdList,
start_date: startDate,
end_date: endDate,
}
@@ -349,32 +371,16 @@ class ReceivableReport extends Component {
}
async onUpdateCompany(v) {
console.log(v)
// 检查v是否为空或v[0]不存在
if (!v || !v[0]) {
this.state.selectCompany = {};
this.state.selectPartner = {};
this.state.domainUsers = [];
await this.onSearch();
return;
async onUpdateCompany(items) {
// 多选:items 是已选项数组 [{id, display_name}, ...]
this.state.selectCompany = items
// 多选:片区管理员重置为空数组
this.state.selectPartner = [];
this.state.domainUsers = [];
// 有公司选中时才查询domain
if (items.length) {
await this.searchUserDomain()
}
// 执行一次 查询
// 通过 read 重新查询一次, 更多只返回了 id
let values = await this.serviceOrm.call(
'yuthon.property.company',
'read',
[[v[0].id]],
{
fields: ['display_name'],
}
)
console.log(values)
this.state.selectCompany = values[0] || {}
// 查询这个过程中的 user_id 组合成列表.返回.
// 重置 partner 筛选结果
// 修改过滤条件
await this.searchUserDomain()
// 自动搜索
await this.onSearch()
}
@@ -413,8 +419,10 @@ class ReceivableReport extends Component {
date_end: `${singleDate} 23:59:59`,
keyword: this.state.keyword || false,
condition: this.state.condition || false,
partner_id: this.state.selectPartner?.id,
company_id: this.state.selectCompany?.id,
// 多选:片区管理员传ID列表
partner_id: this.state.selectPartner.map(item => item.id),
// 多选:所属公司传ID列表
company_id: this.state.selectCompany.map(item => item.id),
management_type_id: this.state.selectManagementType?.id || false,
property_id: this.state.selectProperty?.id || false,
lessee_id: this.state.selectLessee?.id || false,
@@ -428,8 +436,10 @@ class ReceivableReport extends Component {
date_end: `${endDate} 23:59:59`,
keyword: this.state.keyword || false,
condition: this.state.condition || false,
partner_id: this.state.selectPartner?.id,
company_id: this.state.selectCompany?.id,
// 多选:片区管理员传ID列表
partner_id: this.state.selectPartner.map(item => item.id),
// 多选:所属公司传ID列表
company_id: this.state.selectCompany.map(item => item.id),
management_type_id: this.state.selectManagementType?.id || false,
property_id: this.state.selectProperty?.id || false,
lessee_id: this.state.selectLessee?.id || false,
@@ -619,6 +629,7 @@ ReceivableReport.components = {
DateTimeInput,
CheckBox,
Many2XAutocomplete,
MultiSelectAutocomplete,
};
registry.category("actions").add("receivable_report_client", ReceivableReport);
@@ -63,16 +63,16 @@
</t>
</div>
<!-- 所属公司 -->
<!-- 所属公司(多选) -->
<div class="col">
<Many2XAutocomplete
value="state.selectCompany?.display_name"
getDomain="getDomainCompany"
<MultiSelectAutocomplete
value="state.selectCompany"
getDomain="() => this.getDomainCompany()"
resModel="'yuthon.property.company'"
fieldString="'所属公司'"
placeholder="'所属公司'"
activeActions="{}"
update.bind="onUpdateCompany"
update="(items) => this.onUpdateCompany(items)"
onSearch="() => this.onSearch()"
/>
</div>
<div class="col">
@@ -86,16 +86,16 @@
</option>
</select>
</div>
<!-- 片区管理员 -->
<!-- 片区管理员(多选) -->
<div class="col">
<Many2XAutocomplete
value="state.selectPartner?.display_name"
getDomain.bind="getDomain"
<MultiSelectAutocomplete
value="state.selectPartner"
getDomain="() => this.getDomain()"
resModel="'yuthon.precinct.message'"
fieldString="'片区管理员'"
placeholder="'片区管理员'"
activeActions="{}"
update.bind="onUpdatePartner"
update="(items) => this.onUpdatePartner(items)"
onSearch="() => this.onSearch()"
/>
</div>
@@ -8,6 +8,7 @@ import { standardActionServiceProps } from "@web/webclient/actions/action_servic
import { DateTimeInput } from "@web/core/datetime/datetime_input";
import { CheckBox } from "@web/core/checkbox/checkbox";
import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
import { MultiSelectAutocomplete } from "./multi_select_autocomplete";
import {formatDate} from "@web/core/l10n/dates"
const {DateTime} = luxon;
@@ -34,12 +35,26 @@ class YouthonReport extends Component {
initEndDate = parsed.endOf("month");
}
}
const initCompany = df.company_id
? { id: df.company_id, display_name: df.company_name || '' }
: {};
const initPartner = df.partner_id
? { id: df.partner_id, display_name: df.partner_name || '' }
: {};
// 多选:解析所属公司
let initCompany = [];
if (df.company_id) {
const nameList = (df.company_name || '').split(/[,,、]/).map(s => s.trim()).filter(Boolean);
const cidArr = Array.isArray(df.company_id) ? df.company_id : [df.company_id];
initCompany = cidArr.map((cid, idx) => ({
id: cid,
display_name: nameList[idx] || '',
}));
}
// 多选:解析片区管理员
let initPartner = [];
if (df.partner_id) {
const nameList = (df.partner_name || '').split(/[,,、]/).map(s => s.trim()).filter(Boolean);
const pidArr = Array.isArray(df.partner_id) ? df.partner_id : [df.partner_id];
initPartner = pidArr.map((pid, idx) => ({
id: pid,
display_name: nameList[idx] || '',
}));
}
const initCondition = df.condition || false;
// 是否从经营看板跳转过来(控制"返回看板"按钮显示)
@@ -53,8 +68,7 @@ class YouthonReport extends Component {
endDate: initEndDate,
keyword: "",
selectPartner: initPartner,
selectCompany: initCompany,
condition: initCondition,
selectCompany: initCompany, condition: initCondition,
conditions:[{
'name':'自有',
'value':'self'
@@ -104,13 +118,14 @@ class YouthonReport extends Component {
// 过滤条件
return [['id', 'in', [107,106, 215,112,110,214,218,173,219]]]
}
async onUpdatePartner(v){
console.log(v)
this.state.selectPartner = v[0]
async onUpdatePartner(items){
// 多选:items 是已选项数组
this.state.selectPartner = items
}
async onUpdateCompany(v){
this.state.selectCompany = v[0]
async onUpdateCompany(items){
// 多选:items 是已选项数组
this.state.selectCompany = items
}
async onSearch() {
@@ -130,8 +145,9 @@ class YouthonReport extends Component {
date_end: endStr ? `${endStr} 23:59:59` : false,
keyword: this.state.keyword || false,
condition:this.state.condition || false,
partner_id:this.state.selectPartner?.id,
company_id:this.state.selectCompany?.id ,
// 多选:片区管理员、所属公司传ID列表
partner_id: this.state.selectPartner.map(item => item.id),
company_id: this.state.selectCompany.map(item => item.id),
};
const result = await this.serviceOrm.call(
this.config.model,
@@ -178,6 +194,7 @@ YouthonReport.components = {
DateTimeInput,
CheckBox,
Many2XAutocomplete,
MultiSelectAutocomplete,
};
registry.category("actions").add("youthon_report_client", YouthonReport);
@@ -87,16 +87,17 @@
</div>
</div>
<!-- 所属公司 -->
<!-- 所属公司(多选) -->
<div class="col-3">
<label>所属公司</label>
<Many2XAutocomplete
value="state.selectCompany?.display_name"
getDomain="getDomainCompany"
<MultiSelectAutocomplete
value="state.selectCompany"
getDomain="() => this.getDomainCompany()"
resModel="'yuthon.property.company'"
fieldString="'所属公司'"
activeActions="{}"
update.bind="onUpdateCompany"
placeholder="'所属公司'"
update="(items) => this.onUpdateCompany(items)"
onSearch="() => this.onSearch()"
/>
</div>
<div class="col-3">
@@ -108,16 +109,17 @@
<t t-esc="cond.name"/>
</option>
</select>
</div><!-- 片区管理员 -->
</div><!-- 片区管理员(多选) -->
<div class="col-3">
<label>片区管理员</label>
<Many2XAutocomplete
value="state.selectPartner?.display_name"
getDomain="getDomain"
<MultiSelectAutocomplete
value="state.selectPartner"
getDomain="() => this.getDomain()"
resModel="'res.users'"
fieldString="'片区管理员'"
activeActions="{}"
update.bind="onUpdatePartner"
placeholder="'片区管理员'"
update="(items) => this.onUpdatePartner(items)"
onSearch="() => this.onSearch()"
/>
</div>