py-chanjet-toolkit 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_chajet_toolkit/__init__.py +26 -0
- py_chajet_toolkit/tplus/__init__.py +15 -0
- py_chajet_toolkit/tplus/zkhb/__init__.py +145 -0
- py_chajet_toolkit/tplus/zkhb/responses.py +32 -0
- py_chajet_toolkit/tplus/zkhb/utils.py +376 -0
- py_chanjet_toolkit-1.0.0.dist-info/METADATA +274 -0
- py_chanjet_toolkit-1.0.0.dist-info/RECORD +10 -0
- py_chanjet_toolkit-1.0.0.dist-info/WHEEL +5 -0
- py_chanjet_toolkit-1.0.0.dist-info/licenses/LICENSE +21 -0
- py_chanjet_toolkit-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
py_chajet 包入口模块
|
|
5
|
+
|
|
6
|
+
该包是畅捷通 T+ 系列产品的 Python 客户端库,提供与畅捷通 T+ 系统的 API 交互能力。
|
|
7
|
+
|
|
8
|
+
包结构:
|
|
9
|
+
- py_chajet/
|
|
10
|
+
- tplus/ # T+ 系列产品模块
|
|
11
|
+
- zkhb/ # 智慧社区业务模块
|
|
12
|
+
- __init__.py # 智慧社区服务客户端类
|
|
13
|
+
- utils.py # 工具函数集合
|
|
14
|
+
|
|
15
|
+
主要功能:
|
|
16
|
+
- 提供畅捷通 T+ 智慧社区 WebService 接口的同步和异步调用
|
|
17
|
+
- 封装 SOAP 请求的构建和响应解析
|
|
18
|
+
- 提供数据校验、日期处理、SQL 生成等辅助工具函数
|
|
19
|
+
|
|
20
|
+
使用示例:
|
|
21
|
+
from py_chajet.tplus.zkhb import ForcelandEstateService
|
|
22
|
+
|
|
23
|
+
service = ForcelandEstateService(base_url="http://your-server/estate")
|
|
24
|
+
response = service.get_data_set()
|
|
25
|
+
data = convert_to_get_data_set_results(response)
|
|
26
|
+
"""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
py_chajet.tplus 模块
|
|
5
|
+
|
|
6
|
+
该模块包含畅捷通 T+ 系列产品的客户端实现,目前主要包含智慧社区(zkhb)业务模块。
|
|
7
|
+
|
|
8
|
+
子模块:
|
|
9
|
+
- zkhb: 智慧社区业务模块,提供与智慧社区系统的 WebService 接口交互能力
|
|
10
|
+
|
|
11
|
+
未来扩展计划:
|
|
12
|
+
- 添加更多 T+ 产品线的客户端支持
|
|
13
|
+
- 提供统一的 API 调用接口
|
|
14
|
+
- 增加数据模型和类型定义
|
|
15
|
+
"""
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from py_httpx_toolkit import Httpx
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ForcelandEstateService(Httpx):
|
|
10
|
+
"""
|
|
11
|
+
畅捷通T+智慧社区服务客户端
|
|
12
|
+
|
|
13
|
+
该类封装了与畅捷通T+智慧社区系统的WebService接口交互,提供同步和异步两种调用方式。
|
|
14
|
+
通过SOAP协议与服务端通信,主要用于获取数据集信息。
|
|
15
|
+
|
|
16
|
+
核心功能:
|
|
17
|
+
- 支持同步和异步HTTP客户端
|
|
18
|
+
- 封装GetDataSet接口调用
|
|
19
|
+
- 自动处理请求头和参数配置
|
|
20
|
+
- 支持自定义客户端配置
|
|
21
|
+
|
|
22
|
+
典型应用场景:
|
|
23
|
+
- 查询物业收费数据
|
|
24
|
+
- 获取小区信息
|
|
25
|
+
- 查询房间详情
|
|
26
|
+
- 获取收费项目列表
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
base_url: 服务端基础URL
|
|
30
|
+
client_kwargs: HTTP客户端配置参数
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
base_url: Optional[str] = None,
|
|
36
|
+
client_kwargs: Optional[dict] = None,
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
初始化智慧社区服务客户端
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
base_url: WebService服务基础URL,如 "http://example.com/estate"
|
|
43
|
+
client_kwargs: 额外的HTTP客户端配置参数,如headers、timeout等
|
|
44
|
+
|
|
45
|
+
Note:
|
|
46
|
+
- base_url末尾的斜杠会被自动去除
|
|
47
|
+
- 默认超时时间为60秒
|
|
48
|
+
- 默认关闭SSL证书验证
|
|
49
|
+
"""
|
|
50
|
+
# 处理base_url,去除末尾斜杠(如果存在)
|
|
51
|
+
self.base_url = base_url or ""
|
|
52
|
+
self.base_url = self.base_url[:-1] if self.base_url.endswith("/") else self.base_url
|
|
53
|
+
|
|
54
|
+
# 合并默认配置与用户配置
|
|
55
|
+
self.client_kwargs = client_kwargs or {}
|
|
56
|
+
self.client_kwargs = {
|
|
57
|
+
"base_url": self.base_url,
|
|
58
|
+
"timeout": 60, # 默认超时时间60秒
|
|
59
|
+
"verify": False, # 默认关闭SSL验证(适用于内部服务)
|
|
60
|
+
**self.client_kwargs,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
def get_data_set(
|
|
64
|
+
self,
|
|
65
|
+
client: Optional[httpx.Client] = None,
|
|
66
|
+
client_kwargs: Optional[dict] = None,
|
|
67
|
+
**kwargs
|
|
68
|
+
):
|
|
69
|
+
"""
|
|
70
|
+
同步调用GetDataSet接口
|
|
71
|
+
|
|
72
|
+
通过SOAP协议请求智慧社区服务端的GetDataSet方法,获取数据集信息。
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
client: 可选的HTTP客户端实例,如果提供则使用该客户端;否则创建新客户端
|
|
76
|
+
client_kwargs: 可选的HTTP客户端配置参数,如headers、timeout等
|
|
77
|
+
**kwargs: 额外的请求参数,会覆盖默认配置
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
httpx.Response: HTTP响应对象,包含XML格式的SOAP响应
|
|
81
|
+
|
|
82
|
+
Note:
|
|
83
|
+
- 默认Content-Type为text/xml; charset=utf-8
|
|
84
|
+
- 默认请求路径为 /estate/webService/ForcelandEstateService.asmx
|
|
85
|
+
- 默认HTTP方法为POST
|
|
86
|
+
"""
|
|
87
|
+
# 合并默认参数与用户参数,用户参数优先级更高
|
|
88
|
+
client = client or None
|
|
89
|
+
client_kwargs = client_kwargs or {}
|
|
90
|
+
kwargs = kwargs or {}
|
|
91
|
+
kwargs = {
|
|
92
|
+
"url": "/estate/webService/ForcelandEstateService.asmx", # WebService接口路径
|
|
93
|
+
"method": "POST", # SOAP请求使用POST方法
|
|
94
|
+
"params": {
|
|
95
|
+
"op": "GetDataSet", # 操作名称
|
|
96
|
+
},
|
|
97
|
+
"headers": {
|
|
98
|
+
"Content-Type": "text/xml; charset=utf-8", # SOAP请求必须的Content-Type
|
|
99
|
+
},
|
|
100
|
+
**kwargs,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return self.request(client=client, client_kwargs=client_kwargs, **kwargs)
|
|
104
|
+
|
|
105
|
+
async def async_get_data_set(
|
|
106
|
+
self,
|
|
107
|
+
client: Optional[httpx.AsyncClient] = None,
|
|
108
|
+
client_kwargs: Optional[dict] = None,
|
|
109
|
+
**kwargs
|
|
110
|
+
):
|
|
111
|
+
"""
|
|
112
|
+
异步调用GetDataSet接口
|
|
113
|
+
|
|
114
|
+
通过SOAP协议异步请求智慧社区服务端的GetDataSet方法,获取数据集信息。
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
client: 可选的异步HTTP客户端实例,如果提供则使用该客户端;否则创建新客户端
|
|
118
|
+
client_kwargs: 可选的异步HTTP客户端配置参数,如headers、timeout等
|
|
119
|
+
**kwargs: 额外的请求参数,会覆盖默认配置
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
httpx.Response: HTTP响应对象,包含XML格式的SOAP响应
|
|
123
|
+
|
|
124
|
+
Note:
|
|
125
|
+
- 默认Content-Type为text/xml; charset=utf-8
|
|
126
|
+
- 默认请求路径为 /estate/webService/ForcelandEstateService.asmx
|
|
127
|
+
- 默认HTTP方法为POST
|
|
128
|
+
"""
|
|
129
|
+
# 合并默认参数与用户参数,用户参数优先级更高
|
|
130
|
+
client = client or None
|
|
131
|
+
client_kwargs = client_kwargs or {}
|
|
132
|
+
kwargs = kwargs or {}
|
|
133
|
+
kwargs = {
|
|
134
|
+
"url": "/estate/webService/ForcelandEstateService.asmx", # WebService接口路径
|
|
135
|
+
"method": "POST", # SOAP请求使用POST方法
|
|
136
|
+
"params": {
|
|
137
|
+
"op": "GetDataSet", # 操作名称
|
|
138
|
+
},
|
|
139
|
+
"headers": {
|
|
140
|
+
"Content-Type": "text/xml; charset=utf-8", # SOAP请求必须的Content-Type
|
|
141
|
+
},
|
|
142
|
+
**kwargs,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return await self.async_request(client=client, client_kwargs=client_kwargs, **kwargs)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, AwareDatetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ActualPaymentItem(BaseModel):
|
|
9
|
+
ChargeMListID: int = Field(..., title="收费项目列表ID", description="收费项目列表ID")
|
|
10
|
+
ChargeMListNo: str = Field(..., title="收费项目列表编号", description="收费项目列表编号")
|
|
11
|
+
ChargeTime: datetime = Field(..., title="收费时间", description="收费时间")
|
|
12
|
+
PayerName: str = Field(..., title="缴费人姓名", description="缴费人姓名")
|
|
13
|
+
ChargePersonName: str = Field(..., title="收费人姓名", description="收费人姓名")
|
|
14
|
+
ActualPayMoney: float = Field(..., title="实际支付金额", description="实际支付金额")
|
|
15
|
+
ActualAmount: float = Field(..., title="实际支付金额", description="实际支付金额")
|
|
16
|
+
EstateID: int = Field(..., title="小区ID", description="小区ID")
|
|
17
|
+
EstateName: str = Field(..., title="小区名称", description="小区名称")
|
|
18
|
+
ItemName: str = Field(..., title="收费项目名称", description="收费项目名称")
|
|
19
|
+
ItemNames: str = Field(..., title="收费项目名称列表", description="收费项目名称列表")
|
|
20
|
+
ChargeFeeItemID: int = Field(..., title="收费项目ID", description="收费项目ID")
|
|
21
|
+
SDate: AwareDatetime = Field(..., title="开始日期", description="开始日期")
|
|
22
|
+
EDate: AwareDatetime = Field(..., title="结束日期", description="结束日期")
|
|
23
|
+
RmId: int = Field(..., title="房间ID", description="房间ID")
|
|
24
|
+
RmNo: str = Field(..., title="房间编号", description="房间编号")
|
|
25
|
+
IsPayFull: bool = Field(..., title="是否全付", description="是否全付")
|
|
26
|
+
PayDays: int = Field(..., title="支付天数", description="支付天数")
|
|
27
|
+
CreateTime: AwareDatetime = Field(..., title="开始日期", description="开始日期")
|
|
28
|
+
LastUpdateTime: AwareDatetime = Field(..., title="结束日期", description="结束日期")
|
|
29
|
+
|
|
30
|
+
model_config = {
|
|
31
|
+
"extra": "allow" # 允许额外动态字段,适配 API 响应的灵活性
|
|
32
|
+
}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
from collections import Counter, defaultdict
|
|
4
|
+
from datetime import tzinfo
|
|
5
|
+
from typing import Any, Optional, Union
|
|
6
|
+
|
|
7
|
+
import arrow
|
|
8
|
+
import httpx
|
|
9
|
+
from jsonpath_ng import parse
|
|
10
|
+
from jsonschema.validators import Draft202012Validator
|
|
11
|
+
import xmltodict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def json_find_first(expression: str, data: Any) -> Any:
|
|
15
|
+
"""
|
|
16
|
+
使用 JSONPath 表达式从嵌套的 JSON 数据中查找第一个匹配项
|
|
17
|
+
|
|
18
|
+
JSONPath 是一种用于查询 JSON 结构的表达式语言,类似于 XPath 之于 XML。
|
|
19
|
+
该函数通过解析表达式并在数据中执行查询,返回第一个匹配的值。
|
|
20
|
+
|
|
21
|
+
典型应用场景:
|
|
22
|
+
- 从复杂的 API 响应中提取特定字段
|
|
23
|
+
- 在嵌套结构中快速定位目标数据
|
|
24
|
+
- 动态提取不确定位置的数据
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
expression: JSONPath 表达式字符串,用于指定数据查询路径
|
|
28
|
+
示例表达式:
|
|
29
|
+
- "$.name" - 获取根节点下的 name 字段
|
|
30
|
+
- "$.users[0].id" - 获取 users 数组第一个元素的 id
|
|
31
|
+
- "$..id" - 获取所有层级下的 id 字段
|
|
32
|
+
data: 待查询的 JSON 数据,可以是字典、列表或任意嵌套结构
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Any: 第一个匹配到的值,如果表达式无效或没有找到匹配项则返回 None
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
>>> data = {"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}
|
|
39
|
+
>>> json_find_first("$.users[0].name", data)
|
|
40
|
+
'Alice'
|
|
41
|
+
>>> json_find_first("$..id", data)
|
|
42
|
+
1
|
|
43
|
+
"""
|
|
44
|
+
# 使用 jsonpath_ng 库解析表达式并执行查询
|
|
45
|
+
# parse() 将表达式字符串转换为可执行的 JSONPath 对象
|
|
46
|
+
# find() 在数据中查找所有匹配项,返回 Match 对象列表
|
|
47
|
+
# 列表推导式提取每个匹配项的 value 属性
|
|
48
|
+
results = [i.value for i in parse(expression).find(data)]
|
|
49
|
+
|
|
50
|
+
# 检查查询结果是否为非空列表
|
|
51
|
+
# 如果有匹配项,返回第一个;否则返回 None
|
|
52
|
+
if isinstance(results, list) and len(results) > 0:
|
|
53
|
+
return results[0]
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def json_is_valid(schema: Optional[dict], data: Any) -> bool:
|
|
58
|
+
"""
|
|
59
|
+
校验 JSON 数据是否符合指定的 JSON Schema 规范
|
|
60
|
+
|
|
61
|
+
使用 JSON Schema Draft 2020-12 版本进行校验,确保数据结构和类型符合预期。
|
|
62
|
+
该函数常用于 API 响应数据的完整性校验,防止因数据格式异常导致的程序错误。
|
|
63
|
+
|
|
64
|
+
典型应用场景:
|
|
65
|
+
- 验证登录响应是否包含必要的用户信息字段
|
|
66
|
+
- 校验 API 返回的数据结构是否符合预期
|
|
67
|
+
- 在数据处理前进行格式检查,提高代码健壮性
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
schema: JSON Schema 字典,定义了数据应遵循的结构规范
|
|
71
|
+
Schema 应符合 JSON Schema Draft 2020-12 规范
|
|
72
|
+
可以包含 type、properties、required、minLength 等关键字
|
|
73
|
+
data: 待校验的 JSON 数据,可以是任意类型
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
bool: 校验结果,True 表示数据符合 Schema,False 表示不符合
|
|
77
|
+
|
|
78
|
+
Examples:
|
|
79
|
+
>>> schema = {
|
|
80
|
+
... "type": "object",
|
|
81
|
+
... "properties": {"id": {"type": "string"}},
|
|
82
|
+
... "required": ["id"]
|
|
83
|
+
... }
|
|
84
|
+
>>> json_is_valid(schema, {"id": "123"})
|
|
85
|
+
True
|
|
86
|
+
>>> json_is_valid(schema, {"name": "Alice"})
|
|
87
|
+
False
|
|
88
|
+
"""
|
|
89
|
+
# 使用 Draft202012Validator 创建校验器实例
|
|
90
|
+
# is_valid() 方法执行完整校验并返回布尔结果
|
|
91
|
+
return Draft202012Validator(schema).is_valid(data)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def build_get_data_set_post_xml(data: Optional[dict] = None) -> str:
|
|
95
|
+
"""
|
|
96
|
+
将请求数据转换为 SOAP GetDataSet 接口所需的 XML 格式
|
|
97
|
+
|
|
98
|
+
该函数用于构建符合畅捷通 T+ 智慧社区 WebService 规范的 SOAP 请求体。
|
|
99
|
+
将用户提供的查询参数(如 sql、url)封装到标准的 SOAP Envelope 结构中。
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
data: 可选的请求参数字典,可包含 sql、url 等字段
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
str: 完整的 SOAP XML 请求体字符串
|
|
106
|
+
|
|
107
|
+
Note:
|
|
108
|
+
- 默认添加 sql 和 url 字段,值为空字符串
|
|
109
|
+
- 用户传入的数据会覆盖默认值
|
|
110
|
+
- 返回的 XML 包含标准的 SOAP 命名空间声明
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
>>> build_get_data_set_post_data({"sql": "SELECT * FROM EstateDetail"})
|
|
114
|
+
'<?xml version="1.0" encoding="utf-8"?><soap:Envelope ...><GetDataSet><sql>SELECT * FROM EstateDetail</sql><url/></GetDataSet></soap:Envelope>'
|
|
115
|
+
"""
|
|
116
|
+
# 处理默认参数,确保 data 不为 None
|
|
117
|
+
data = data or {}
|
|
118
|
+
|
|
119
|
+
# 合并默认字段与用户字段,用户字段优先级更高
|
|
120
|
+
data = {
|
|
121
|
+
"sql": "", # 默认空SQL语句
|
|
122
|
+
"url": "", # 默认空URL
|
|
123
|
+
**data, # 用户传入的数据覆盖默认值
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# 使用 xmltodict 将字典转换为 XML 字符串
|
|
127
|
+
return xmltodict.unparse(
|
|
128
|
+
{
|
|
129
|
+
"soap:Envelope": {
|
|
130
|
+
# SOAP 标准命名空间声明
|
|
131
|
+
"@xmlns:soap": "http://schemas.xmlsoap.org/soap/envelope/",
|
|
132
|
+
# XML Schema 实例命名空间
|
|
133
|
+
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
|
134
|
+
# XML Schema 命名空间
|
|
135
|
+
"@xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
|
|
136
|
+
"soap:Body": {
|
|
137
|
+
"GetDataSet": {
|
|
138
|
+
# 畅捷通智慧社区 WebService 命名空间
|
|
139
|
+
"@xmlns": "http://zkhb.com.cn/",
|
|
140
|
+
**data, # 展开用户提供的请求数据
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def build_get_data_set_results(response: Optional[Any] = None) -> list:
|
|
149
|
+
"""
|
|
150
|
+
从 GetDataSet 接口响应中提取实际数据列表
|
|
151
|
+
|
|
152
|
+
该函数用于解析畅捷通 T+ 智慧社区 API 返回的 SOAP 响应,提取嵌套在复杂 XML 结构中的实际业务数据。
|
|
153
|
+
支持直接传入 httpx.Response 对象或已解析的 XML 字典。
|
|
154
|
+
|
|
155
|
+
数据提取路径说明:
|
|
156
|
+
SOAP响应 -> Envelope -> Body -> GetDataSetResponse -> GetDataSetResult -> diffgram -> NewDataSet -> Table
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
response: 从畅捷通 tplus API 返回的响应数据
|
|
160
|
+
可以是 httpx.Response 对象,也可以是已解析的 XML 字典
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
list: 包含实际业务数据的列表,每个元素是一个字典
|
|
164
|
+
|
|
165
|
+
Note:
|
|
166
|
+
- 如果提取结果是单个字典(单行数据),自动包装为列表
|
|
167
|
+
- 如果未找到匹配数据,返回空列表
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
>>> # 假设 response 是包含单行数据的响应
|
|
171
|
+
>>> build_get_data_set_results(response)
|
|
172
|
+
[{'ChargeMListID': '1', 'ChargeMListNo': 'CF20240101'}]
|
|
173
|
+
"""
|
|
174
|
+
results = []
|
|
175
|
+
|
|
176
|
+
# 根据响应类型选择不同的解析方式
|
|
177
|
+
if isinstance(response, httpx.Response):
|
|
178
|
+
# 如果是 httpx.Response 对象,先解析 XML 为字典
|
|
179
|
+
results = json_find_first(
|
|
180
|
+
"$.'soap:Envelope'.'soap:Body'.GetDataSetResponse.GetDataSetResult.'diffgr:diffgram'.NewDataSet.Table",
|
|
181
|
+
xmltodict.parse(response.text)
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
# 如果是已解析的字典,直接查询
|
|
185
|
+
results = json_find_first(
|
|
186
|
+
"$.'soap:Envelope'.'soap:Body'.GetDataSetResponse.GetDataSetResult.'diffgr:diffgram'.NewDataSet.Table",
|
|
187
|
+
response
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# 如果查询结果是单个字典(单行数据),转换为列表形式
|
|
191
|
+
if isinstance(results, dict):
|
|
192
|
+
return [results]
|
|
193
|
+
|
|
194
|
+
# 返回结果(可能是列表或 None)
|
|
195
|
+
return results
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def build_actual_payment_items_query_sql(
|
|
199
|
+
columns: str = "",
|
|
200
|
+
conditions: str = "",
|
|
201
|
+
order_by: str = "order by cfi.ChargeFeeItemID"
|
|
202
|
+
) -> str:
|
|
203
|
+
"""
|
|
204
|
+
生成查询实际支付项目的 SQL 语句
|
|
205
|
+
|
|
206
|
+
该函数用于构建查询物业收费相关数据的 SQL 语句,支持自定义查询列、条件和排序方式。
|
|
207
|
+
默认查询包含收费主列表、小区详情、收费项目、房间详情等多表关联数据。
|
|
208
|
+
|
|
209
|
+
表关联关系说明:
|
|
210
|
+
- chargeMasterList (cml): 收费主列表(主表)
|
|
211
|
+
- EstateDetail (ed): 小区详情(通过 EstateID 关联)
|
|
212
|
+
- ChargeFeeItem (cfi): 收费项目(通过 ChargeMListID 关联)
|
|
213
|
+
- RoomDetail (rd): 房间详情(通过 RmId 关联)
|
|
214
|
+
- ChargeBillItem (cbi): 收费账单项目(通过 CBillItemID 关联)
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
columns: 额外的查询列(默认空字符串),会添加到默认列前面
|
|
218
|
+
conditions: 查询条件(默认空字符串),会拼接到 WHERE 子句后
|
|
219
|
+
order_by: 排序方式(默认 "order by cfi.ChargeFeeItemID")
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
str: 完整的 SQL 查询语句
|
|
223
|
+
|
|
224
|
+
Note:
|
|
225
|
+
- 使用 LEFT JOIN 确保即使关联表无数据也能返回结果
|
|
226
|
+
- WHERE 子句使用 "1=1" 便于追加额外条件
|
|
227
|
+
|
|
228
|
+
Examples:
|
|
229
|
+
>>> build_actual_payment_items_query_sql(conditions="and cml.EstateID='E001'")
|
|
230
|
+
'select cml.ChargeMListID,cml.ChargeMListNo,... from chargeMasterList as cml ... where 1=1 and cml.EstateID='E001' order by cfi.ChargeFeeItemID;'
|
|
231
|
+
"""
|
|
232
|
+
# 定义默认查询列集合,包含收费相关的主要字段
|
|
233
|
+
default_columns = [
|
|
234
|
+
"cml.ChargeMListID", # 收费主列表ID
|
|
235
|
+
"cml.ChargeMListNo", # 收费单号
|
|
236
|
+
"cml.ChargeTime", # 收费时间
|
|
237
|
+
"cml.PayerName", # 付款人姓名
|
|
238
|
+
"cml.ChargePersonName", # 收费人姓名
|
|
239
|
+
"cml.ActualPayMoney", # 实际支付金额
|
|
240
|
+
"cml.EstateID", # 小区ID
|
|
241
|
+
"cml.ItemNames", # 项目名称
|
|
242
|
+
"ed.Caption as EstateName", # 小区名称(别名)
|
|
243
|
+
"cfi.ChargeFeeItemID", # 收费项目ID
|
|
244
|
+
"cfi.ActualAmount", # 实际金额
|
|
245
|
+
"cfi.SDate", # 开始日期
|
|
246
|
+
"cfi.EDate", # 结束日期
|
|
247
|
+
"cfi.RmId", # 房间ID
|
|
248
|
+
"rd.RmNo", # 房间号
|
|
249
|
+
"cml.CreateTime", # 创建时间
|
|
250
|
+
"cml.LastUpdateTime", # 最后更新时间
|
|
251
|
+
"cbi.ItemName", # 项目名称
|
|
252
|
+
"cbi.IsPayFull", # 是否已全额支付
|
|
253
|
+
"ABS(DATEDIFF(dd,cfi.EDate, cfi.SDate)) as PayDays", # 支付天数(计算字段)
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
# 定义默认表连接关系,使用 LEFT JOIN 确保即使关联表无数据也能返回结果
|
|
257
|
+
table_joins = "".join([
|
|
258
|
+
" from chargeMasterList as cml", # 主表:收费主列表
|
|
259
|
+
" left join EstateDetail as ed on cml.EstateID=ed.EstateID", # 左联:小区详情
|
|
260
|
+
" left join ChargeFeeItem as cfi on cml.ChargeMListID=cfi.ChargeMListID", # 左联:收费项目
|
|
261
|
+
" left join RoomDetail as rd on cfi.RmId=rd.RmId", # 左联:房间详情
|
|
262
|
+
" left join ChargeBillItem as cbi on cfi.CBillItemID=cbi.CBillItemID", # 左联:收费账单项目
|
|
263
|
+
])
|
|
264
|
+
|
|
265
|
+
# 构建并返回完整的 SQL 语句
|
|
266
|
+
# 使用 1=1 便于无条件时仍保持语法正确
|
|
267
|
+
return f"select {columns} {','.join(default_columns)} {table_joins} where 1=1 {conditions} {order_by};"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def payment_date_range_is_valid(
|
|
271
|
+
daily_fee: Union[int, float] = 0,
|
|
272
|
+
total_amount: Union[int, float] = 0,
|
|
273
|
+
start: str = "",
|
|
274
|
+
start_tz: Union[str, tzinfo] = "Asia/Shanghai",
|
|
275
|
+
end: str = "",
|
|
276
|
+
end_tz: Union[str, tzinfo] = "Asia/Shanghai",
|
|
277
|
+
):
|
|
278
|
+
"""
|
|
279
|
+
验证支付金额与日期范围是否匹配
|
|
280
|
+
|
|
281
|
+
根据每日费用、总金额和日期范围,验证总金额是否足够支付指定日期范围内的费用。
|
|
282
|
+
主要用于校验物业费、租金等按日计费场景的金额是否合理。
|
|
283
|
+
|
|
284
|
+
计算逻辑:
|
|
285
|
+
1. 将每日费用和总金额转换为浮点数
|
|
286
|
+
2. 如果总金额小于等于0,直接返回 False
|
|
287
|
+
3. 使用 arrow 库解析开始日期和结束日期
|
|
288
|
+
4. 计算日期范围内的总天数(包含起止两天)
|
|
289
|
+
5. 验证总金额是否 >= 每日费用 × 总天数
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
daily_fee: 每日费用金额
|
|
293
|
+
total_amount: 实际支付的总金额
|
|
294
|
+
start: 计费开始日期字符串(支持多种格式)
|
|
295
|
+
start_tz: 开始日期的时区,默认为 "Asia/Shanghai"
|
|
296
|
+
end: 计费结束日期字符串(支持多种格式)
|
|
297
|
+
end_tz: 结束日期的时区,默认为 "Asia/Shanghai"
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
bool: 如果总金额大于等于每日费用乘以天数则返回 True,否则返回 False
|
|
301
|
+
|
|
302
|
+
Note:
|
|
303
|
+
- 日期范围包含开始日期和结束日期两天
|
|
304
|
+
- 总金额小于等于0时直接返回 False
|
|
305
|
+
- 使用 arrow 库处理时区转换,避免时区问题导致的天数计算错误
|
|
306
|
+
|
|
307
|
+
Examples:
|
|
308
|
+
>>> payment_date_range_is_valid(daily_fee=10, total_amount=300, start="2024-01-01", end="2024-01-31")
|
|
309
|
+
True
|
|
310
|
+
>>> payment_date_range_is_valid(daily_fee=10, total_amount=290, start="2024-01-01", end="2024-01-31")
|
|
311
|
+
False
|
|
312
|
+
"""
|
|
313
|
+
# 将输入转换为浮点数,确保数值计算准确性
|
|
314
|
+
daily_fee = float(daily_fee)
|
|
315
|
+
total_amount = float(total_amount)
|
|
316
|
+
|
|
317
|
+
# 总金额小于等于0时直接返回 False(无效金额)
|
|
318
|
+
if float(total_amount) <= 0:
|
|
319
|
+
return False
|
|
320
|
+
|
|
321
|
+
# 使用 arrow 库解析日期,支持多种日期格式和时区
|
|
322
|
+
start_arrow: arrow.Arrow = arrow.get(start, tzinfo=start_tz)
|
|
323
|
+
end_arrow: arrow.Arrow = arrow.get(end, tzinfo=end_tz)
|
|
324
|
+
|
|
325
|
+
# 计算日期范围内的总天数(包含起止两天)
|
|
326
|
+
# 使用 interval 方法生成每天的时间点,然后统计数量
|
|
327
|
+
total_days = sum(1 for _ in start_arrow.interval(
|
|
328
|
+
frame="days",
|
|
329
|
+
start=start_arrow.datetime,
|
|
330
|
+
end=end_arrow.datetime
|
|
331
|
+
))
|
|
332
|
+
|
|
333
|
+
# 验证总金额是否足够支付指定天数的费用
|
|
334
|
+
return total_amount >= daily_fee * total_days
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def filter_actual_payment_items(actual_payment_items: Optional[list[dict]] = None) -> list[dict]:
|
|
338
|
+
"""
|
|
339
|
+
过滤实际支付记录,移除配对记录
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
actual_payment_items: 每条记录包含 ChargeMListID, ActualAmount, SDate, EDate
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
移除配对记录后的列表
|
|
346
|
+
"""
|
|
347
|
+
# 1. 按 (start_date, end_date, abs(amount)) 分组
|
|
348
|
+
actual_payment_items_groups = defaultdict(list)
|
|
349
|
+
for item in actual_payment_items:
|
|
350
|
+
sdate = item.get("SDate", "")
|
|
351
|
+
edate = item.get("EDate", "")
|
|
352
|
+
actual_amount = float(item.get("ActualAmount", 0))
|
|
353
|
+
group_key = (sdate, edate, abs(float(actual_amount)))
|
|
354
|
+
actual_payment_items_groups[group_key].append(item)
|
|
355
|
+
# return actual_payment_items_groups
|
|
356
|
+
|
|
357
|
+
# 2. 找出需要移除的 id
|
|
358
|
+
remove_ids = set()
|
|
359
|
+
|
|
360
|
+
for group_value in actual_payment_items_groups.values():
|
|
361
|
+
# 分离正金额(充值)和负金额(退款)
|
|
362
|
+
positives = [r for r in group_value if float(r.get("ActualAmount", 0)) > 0]
|
|
363
|
+
negatives = [r for r in group_value if float(r.get("ActualAmount", 0)) < 0]
|
|
364
|
+
|
|
365
|
+
# 按 id 排序(也可以按其他字段,比如 created_at)
|
|
366
|
+
positives.sort(key=lambda x: x['ChargeMListID'])
|
|
367
|
+
negatives.sort(key=lambda x: x['ChargeMListID'])
|
|
368
|
+
|
|
369
|
+
# 一一配对移除
|
|
370
|
+
paired_count = min(len(positives), len(negatives))
|
|
371
|
+
for i in range(paired_count):
|
|
372
|
+
remove_ids.add(positives[i]['ChargeMListID'])
|
|
373
|
+
remove_ids.add(negatives[i]['ChargeMListID'])
|
|
374
|
+
|
|
375
|
+
# 3. 返回未移除的记录
|
|
376
|
+
return [item for item in actual_payment_items if item['ChargeMListID'] not in remove_ids]
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-chanjet-toolkit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 畅捷通 T+ Python 客户端库,提供与畅捷通 T+ 智慧社区系统的 WebService 接口交互能力,支持同步和异步调用。
|
|
5
|
+
Author-email: Guolei <174000902@qq.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 郭磊
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://gitee.com/guolei19850528/py_chanjet_toolkit
|
|
29
|
+
Project-URL: Repository, https://gitee.com/guolei19850528/py_chanjet_toolkit.git
|
|
30
|
+
Project-URL: Issues, https://gitee.com/guolei19850528/py_chanjet_toolkit/issues
|
|
31
|
+
Project-URL: Documentation, https://gitee.com/guolei19850528/py_chanjet_toolkit/wikis
|
|
32
|
+
Keywords: chanjet,tplus,畅捷通,智慧社区,webservice,soap,api,client,async
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Development Status :: 4 - Beta
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: Intended Audience :: System Administrators
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Operating System :: OS Independent
|
|
43
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
44
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
45
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
46
|
+
Classifier: Framework :: AsyncIO
|
|
47
|
+
Classifier: Typing :: Typed
|
|
48
|
+
Requires-Python: >=3.10
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
License-File: LICENSE
|
|
51
|
+
Requires-Dist: pydantic>=2.0
|
|
52
|
+
Requires-Dist: jsonpath-ng>=1.5.3
|
|
53
|
+
Requires-Dist: jsonschema>=4.21.0
|
|
54
|
+
Requires-Dist: xmltodict>=1.0.4
|
|
55
|
+
Requires-Dist: arrow>=1.4.0
|
|
56
|
+
Requires-Dist: py-httpx-toolkit>=1.0.1
|
|
57
|
+
Dynamic: license-file
|
|
58
|
+
|
|
59
|
+
# py-chajet-toolkit
|
|
60
|
+
|
|
61
|
+
畅捷通 T+ 系列产品的 Python 客户端库,提供与畅捷通 T+ 系统的 API 交互能力。
|
|
62
|
+
|
|
63
|
+
## 功能特性
|
|
64
|
+
|
|
65
|
+
- 提供畅捷通 T+ 智慧社区 WebService 接口的同步和异步调用
|
|
66
|
+
- 封装 SOAP 请求的构建和响应解析
|
|
67
|
+
- 提供数据校验、日期处理、SQL 生成等辅助工具函数
|
|
68
|
+
- 支持 JSONPath 查询和 JSON Schema 校验
|
|
69
|
+
|
|
70
|
+
## 安装方式
|
|
71
|
+
|
|
72
|
+
### 使用 pip 安装
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install py-chajet-toolkit
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 使用 uv 安装
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
uv add py-chajet-toolkit
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 快速开始
|
|
85
|
+
|
|
86
|
+
### 基础使用示例
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from py_chajet_toolkit.tplus.zkhb import ForcelandEstateService
|
|
90
|
+
from py_chajet_toolkit.tplus.zkhb.utils import (
|
|
91
|
+
build_get_data_set_post_xml,
|
|
92
|
+
build_get_data_set_results,
|
|
93
|
+
build_actual_payment_items_query_sql
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# 初始化服务客户端
|
|
97
|
+
service = ForcelandEstateService(base_url="http://your-server/estate")
|
|
98
|
+
|
|
99
|
+
# 构建 SOAP 请求体
|
|
100
|
+
xml_data = build_get_data_set_post_xml({"sql": "SELECT * FROM EstateDetail"})
|
|
101
|
+
|
|
102
|
+
# 同步调用接口
|
|
103
|
+
response = service.get_data_set(content=xml_data)
|
|
104
|
+
|
|
105
|
+
# 解析响应数据
|
|
106
|
+
results = build_get_data_set_results(response)
|
|
107
|
+
print(results)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 异步调用示例
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import asyncio
|
|
114
|
+
from py_chajet_toolkit.tplus.zkhb import ForcelandEstateService
|
|
115
|
+
from py_chajet_toolkit.tplus.zkhb.utils import build_get_data_set_post_xml, build_get_data_set_results
|
|
116
|
+
|
|
117
|
+
async def main():
|
|
118
|
+
service = ForcelandEstateService(base_url="http://your-server/estate")
|
|
119
|
+
xml_data = build_get_data_set_post_xml({"sql": "SELECT * FROM ChargeMasterList"})
|
|
120
|
+
|
|
121
|
+
# 异步调用接口
|
|
122
|
+
response = await service.async_get_data_set(content=xml_data)
|
|
123
|
+
results = build_get_data_set_results(response)
|
|
124
|
+
print(results)
|
|
125
|
+
|
|
126
|
+
asyncio.run(main())
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## API 说明
|
|
130
|
+
|
|
131
|
+
### ForcelandEstateService
|
|
132
|
+
|
|
133
|
+
畅捷通T+智慧社区服务客户端类,封装了与畅捷通T+智慧社区系统的WebService接口交互。
|
|
134
|
+
|
|
135
|
+
#### 初始化
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
service = ForcelandEstateService(
|
|
139
|
+
base_url="http://your-server/estate",
|
|
140
|
+
client_kwargs={"timeout": 120}
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### 同步方法
|
|
145
|
+
|
|
146
|
+
- `get_data_set(client=None, client_kwargs=None, **kwargs)` - 调用 GetDataSet 接口
|
|
147
|
+
|
|
148
|
+
#### 异步方法
|
|
149
|
+
|
|
150
|
+
- `async_get_data_set(client=None, client_kwargs=None, **kwargs)` - 异步调用 GetDataSet 接口
|
|
151
|
+
|
|
152
|
+
## Utils 工具函数
|
|
153
|
+
|
|
154
|
+
### json_find_first
|
|
155
|
+
|
|
156
|
+
使用 JSONPath 表达式从嵌套的 JSON 数据中查找第一个匹配项。
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from py_chajet_toolkit.tplus.zkhb.utils import json_find_first
|
|
160
|
+
|
|
161
|
+
data = {"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}
|
|
162
|
+
result = json_find_first("$.users[0].name", data)
|
|
163
|
+
# 返回: 'Alice'
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### json_is_valid
|
|
167
|
+
|
|
168
|
+
校验 JSON 数据是否符合指定的 JSON Schema 规范。
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from py_chajet_toolkit.tplus.zkhb.utils import json_is_valid
|
|
172
|
+
|
|
173
|
+
schema = {
|
|
174
|
+
"type": "object",
|
|
175
|
+
"properties": {"id": {"type": "string"}},
|
|
176
|
+
"required": ["id"]
|
|
177
|
+
}
|
|
178
|
+
is_valid = json_is_valid(schema, {"id": "123"})
|
|
179
|
+
# 返回: True
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### build_get_data_set_post_xml
|
|
183
|
+
|
|
184
|
+
将请求数据转换为 SOAP GetDataSet 接口所需的 XML 格式。
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from py_chajet_toolkit.tplus.zkhb.utils import build_get_data_set_post_xml
|
|
188
|
+
|
|
189
|
+
xml = build_get_data_set_post_xml({"sql": "SELECT * FROM EstateDetail"})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### build_get_data_set_results
|
|
193
|
+
|
|
194
|
+
从 GetDataSet 接口响应中提取实际数据列表。
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from py_chajet_toolkit.tplus.zkhb.utils import build_get_data_set_results
|
|
198
|
+
|
|
199
|
+
results = build_get_data_set_results(response)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### build_actual_payment_items_query_sql
|
|
203
|
+
|
|
204
|
+
生成查询实际支付项目的 SQL 语句。
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from py_chajet_toolkit.tplus.zkhb.utils import build_actual_payment_items_query_sql
|
|
208
|
+
|
|
209
|
+
sql = build_actual_payment_items_query_sql(
|
|
210
|
+
conditions="and cml.EstateID='E001'",
|
|
211
|
+
order_by="order by cml.CreateTime desc"
|
|
212
|
+
)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### payment_date_range_is_valid
|
|
216
|
+
|
|
217
|
+
验证支付金额与日期范围是否匹配。
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from py_chajet_toolkit.tplus.zkhb.utils import payment_date_range_is_valid
|
|
221
|
+
|
|
222
|
+
is_valid = payment_date_range_is_valid(
|
|
223
|
+
daily_fee=10,
|
|
224
|
+
total_amount=300,
|
|
225
|
+
start="2024-01-01",
|
|
226
|
+
end="2024-01-31"
|
|
227
|
+
)
|
|
228
|
+
# 返回: True
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### filter_actual_payment_items
|
|
232
|
+
|
|
233
|
+
过滤实际支付记录,移除配对的充值和退款记录。
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
from py_chajet_toolkit.tplus.zkhb.utils import filter_actual_payment_items
|
|
237
|
+
|
|
238
|
+
filtered_items = filter_actual_payment_items(actual_payment_items)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## 项目结构
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
py_chajet_toolkit/
|
|
245
|
+
├── tplus/
|
|
246
|
+
│ ├── zkhb/
|
|
247
|
+
│ │ ├── __init__.py # 智慧社区服务客户端类
|
|
248
|
+
│ │ ├── utils.py # 工具函数集合
|
|
249
|
+
│ │ └── responses.py # 响应处理模块
|
|
250
|
+
│ └── __init__.py
|
|
251
|
+
└── __init__.py
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## 依赖
|
|
255
|
+
|
|
256
|
+
- arrow - 日期时间处理
|
|
257
|
+
- httpx - HTTP 客户端
|
|
258
|
+
- jsonpath_ng - JSONPath 查询
|
|
259
|
+
- jsonschema - JSON Schema 校验
|
|
260
|
+
- xmltodict - XML 与字典转换
|
|
261
|
+
- py_httpx_toolkit - HTTP 工具封装
|
|
262
|
+
|
|
263
|
+
## 项目主页
|
|
264
|
+
|
|
265
|
+
[https://gitee.com/guolei19850528/py_chanjet_toolkit](https://gitee.com/guolei19850528/py_chanjet_toolkit)
|
|
266
|
+
|
|
267
|
+
## 作者
|
|
268
|
+
|
|
269
|
+
**Author**: Lei Guo
|
|
270
|
+
**Email**: guolei@example.com
|
|
271
|
+
|
|
272
|
+
## 许可证
|
|
273
|
+
|
|
274
|
+
MIT License
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
py_chajet_toolkit/__init__.py,sha256=xKia_U8WeX_VlXAZQd5ucHvKF82oSeBadiDTvN-wRyg,862
|
|
2
|
+
py_chajet_toolkit/tplus/__init__.py,sha256=9cHhA80MfeXYcf-Z_2F2qpNpAuXDqPl8rDYgkl9gDOg,445
|
|
3
|
+
py_chajet_toolkit/tplus/zkhb/__init__.py,sha256=n2dhiCurjJQtSPyKKV24_1uO7LMG29ASEkI93BWwP4c,5166
|
|
4
|
+
py_chajet_toolkit/tplus/zkhb/responses.py,sha256=oWlTUbGZHZHdXPwADq5g_QuW0NpFzaXNmskmXnPDils,2087
|
|
5
|
+
py_chajet_toolkit/tplus/zkhb/utils.py,sha256=Tq8rCF5wxOwktIQlMO5zyFRRcajcdMJEps7FQyjdbW0,15314
|
|
6
|
+
py_chanjet_toolkit-1.0.0.dist-info/licenses/LICENSE,sha256=HazFJNaQP3rbY1dvg3hAmO-FZ6v7DwM0YHwXvWsGG3A,1063
|
|
7
|
+
py_chanjet_toolkit-1.0.0.dist-info/METADATA,sha256=q3vChqme-EgRNsFFTKfXyOMIx-KYvVHN2uZFkgAFFL4,8011
|
|
8
|
+
py_chanjet_toolkit-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
9
|
+
py_chanjet_toolkit-1.0.0.dist-info/top_level.txt,sha256=OxwP24CYWVpzRu4AqrlD4dSVE-ocT3IBVlCm37ukAoI,18
|
|
10
|
+
py_chanjet_toolkit-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 郭磊
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
py_chajet_toolkit
|