deepfos 1.1.60__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.
- deepfos/__init__.py +6 -0
- deepfos/_version.py +21 -0
- deepfos/algo/__init__.py +0 -0
- deepfos/algo/graph.py +171 -0
- deepfos/algo/segtree.py +31 -0
- deepfos/api/V1_1/__init__.py +0 -0
- deepfos/api/V1_1/business_model.py +119 -0
- deepfos/api/V1_1/dimension.py +599 -0
- deepfos/api/V1_1/models/__init__.py +0 -0
- deepfos/api/V1_1/models/business_model.py +1033 -0
- deepfos/api/V1_1/models/dimension.py +2768 -0
- deepfos/api/V1_2/__init__.py +0 -0
- deepfos/api/V1_2/dimension.py +285 -0
- deepfos/api/V1_2/models/__init__.py +0 -0
- deepfos/api/V1_2/models/dimension.py +2923 -0
- deepfos/api/__init__.py +0 -0
- deepfos/api/account.py +167 -0
- deepfos/api/accounting_engines.py +147 -0
- deepfos/api/app.py +626 -0
- deepfos/api/approval_process.py +198 -0
- deepfos/api/base.py +983 -0
- deepfos/api/business_model.py +160 -0
- deepfos/api/consolidation.py +129 -0
- deepfos/api/consolidation_process.py +106 -0
- deepfos/api/datatable.py +341 -0
- deepfos/api/deep_pipeline.py +61 -0
- deepfos/api/deepconnector.py +36 -0
- deepfos/api/deepfos_task.py +92 -0
- deepfos/api/deepmodel.py +188 -0
- deepfos/api/dimension.py +486 -0
- deepfos/api/financial_model.py +319 -0
- deepfos/api/journal_model.py +119 -0
- deepfos/api/journal_template.py +132 -0
- deepfos/api/memory_financial_model.py +98 -0
- deepfos/api/models/__init__.py +3 -0
- deepfos/api/models/account.py +483 -0
- deepfos/api/models/accounting_engines.py +756 -0
- deepfos/api/models/app.py +1338 -0
- deepfos/api/models/approval_process.py +1043 -0
- deepfos/api/models/base.py +234 -0
- deepfos/api/models/business_model.py +805 -0
- deepfos/api/models/consolidation.py +711 -0
- deepfos/api/models/consolidation_process.py +248 -0
- deepfos/api/models/datatable_mysql.py +427 -0
- deepfos/api/models/deep_pipeline.py +55 -0
- deepfos/api/models/deepconnector.py +28 -0
- deepfos/api/models/deepfos_task.py +386 -0
- deepfos/api/models/deepmodel.py +308 -0
- deepfos/api/models/dimension.py +1576 -0
- deepfos/api/models/financial_model.py +1796 -0
- deepfos/api/models/journal_model.py +341 -0
- deepfos/api/models/journal_template.py +854 -0
- deepfos/api/models/memory_financial_model.py +478 -0
- deepfos/api/models/platform.py +178 -0
- deepfos/api/models/python.py +221 -0
- deepfos/api/models/reconciliation_engine.py +411 -0
- deepfos/api/models/reconciliation_report.py +161 -0
- deepfos/api/models/role_strategy.py +884 -0
- deepfos/api/models/smartlist.py +237 -0
- deepfos/api/models/space.py +1137 -0
- deepfos/api/models/system.py +1065 -0
- deepfos/api/models/variable.py +463 -0
- deepfos/api/models/workflow.py +946 -0
- deepfos/api/platform.py +199 -0
- deepfos/api/python.py +90 -0
- deepfos/api/reconciliation_engine.py +181 -0
- deepfos/api/reconciliation_report.py +64 -0
- deepfos/api/role_strategy.py +234 -0
- deepfos/api/smartlist.py +69 -0
- deepfos/api/space.py +582 -0
- deepfos/api/system.py +372 -0
- deepfos/api/variable.py +154 -0
- deepfos/api/workflow.py +264 -0
- deepfos/boost/__init__.py +6 -0
- deepfos/boost/py_jstream.py +89 -0
- deepfos/boost/py_pandas.py +20 -0
- deepfos/cache.py +121 -0
- deepfos/config.py +6 -0
- deepfos/core/__init__.py +27 -0
- deepfos/core/cube/__init__.py +10 -0
- deepfos/core/cube/_base.py +462 -0
- deepfos/core/cube/constants.py +21 -0
- deepfos/core/cube/cube.py +408 -0
- deepfos/core/cube/formula.py +707 -0
- deepfos/core/cube/syscube.py +532 -0
- deepfos/core/cube/typing.py +7 -0
- deepfos/core/cube/utils.py +238 -0
- deepfos/core/dimension/__init__.py +11 -0
- deepfos/core/dimension/_base.py +506 -0
- deepfos/core/dimension/dimcreator.py +184 -0
- deepfos/core/dimension/dimension.py +472 -0
- deepfos/core/dimension/dimexpr.py +271 -0
- deepfos/core/dimension/dimmember.py +155 -0
- deepfos/core/dimension/eledimension.py +22 -0
- deepfos/core/dimension/filters.py +99 -0
- deepfos/core/dimension/sysdimension.py +168 -0
- deepfos/core/logictable/__init__.py +5 -0
- deepfos/core/logictable/_cache.py +141 -0
- deepfos/core/logictable/_operator.py +663 -0
- deepfos/core/logictable/nodemixin.py +673 -0
- deepfos/core/logictable/sqlcondition.py +609 -0
- deepfos/core/logictable/tablemodel.py +497 -0
- deepfos/db/__init__.py +36 -0
- deepfos/db/cipher.py +660 -0
- deepfos/db/clickhouse.py +191 -0
- deepfos/db/connector.py +195 -0
- deepfos/db/daclickhouse.py +171 -0
- deepfos/db/dameng.py +101 -0
- deepfos/db/damysql.py +189 -0
- deepfos/db/dbkits.py +358 -0
- deepfos/db/deepengine.py +99 -0
- deepfos/db/deepmodel.py +82 -0
- deepfos/db/deepmodel_kingbase.py +83 -0
- deepfos/db/edb.py +214 -0
- deepfos/db/gauss.py +83 -0
- deepfos/db/kingbase.py +83 -0
- deepfos/db/mysql.py +184 -0
- deepfos/db/oracle.py +131 -0
- deepfos/db/postgresql.py +192 -0
- deepfos/db/sqlserver.py +99 -0
- deepfos/db/utils.py +135 -0
- deepfos/element/__init__.py +89 -0
- deepfos/element/accounting.py +348 -0
- deepfos/element/apvlprocess.py +215 -0
- deepfos/element/base.py +398 -0
- deepfos/element/bizmodel.py +1269 -0
- deepfos/element/datatable.py +2467 -0
- deepfos/element/deep_pipeline.py +186 -0
- deepfos/element/deepconnector.py +59 -0
- deepfos/element/deepmodel.py +1806 -0
- deepfos/element/dimension.py +1254 -0
- deepfos/element/fact_table.py +427 -0
- deepfos/element/finmodel.py +1485 -0
- deepfos/element/journal.py +840 -0
- deepfos/element/journal_template.py +943 -0
- deepfos/element/pyscript.py +412 -0
- deepfos/element/reconciliation.py +553 -0
- deepfos/element/rolestrategy.py +243 -0
- deepfos/element/smartlist.py +457 -0
- deepfos/element/variable.py +756 -0
- deepfos/element/workflow.py +560 -0
- deepfos/exceptions/__init__.py +239 -0
- deepfos/exceptions/hook.py +86 -0
- deepfos/lazy.py +104 -0
- deepfos/lazy_import.py +84 -0
- deepfos/lib/__init__.py +0 -0
- deepfos/lib/_javaobj.py +366 -0
- deepfos/lib/asynchronous.py +879 -0
- deepfos/lib/concurrency.py +107 -0
- deepfos/lib/constant.py +39 -0
- deepfos/lib/decorator.py +310 -0
- deepfos/lib/deepchart.py +778 -0
- deepfos/lib/deepux.py +477 -0
- deepfos/lib/discovery.py +273 -0
- deepfos/lib/edb_lexer.py +789 -0
- deepfos/lib/eureka.py +156 -0
- deepfos/lib/filterparser.py +751 -0
- deepfos/lib/httpcli.py +106 -0
- deepfos/lib/jsonstreamer.py +80 -0
- deepfos/lib/msg.py +394 -0
- deepfos/lib/nacos.py +225 -0
- deepfos/lib/patch.py +92 -0
- deepfos/lib/redis.py +241 -0
- deepfos/lib/serutils.py +181 -0
- deepfos/lib/stopwatch.py +99 -0
- deepfos/lib/subtask.py +572 -0
- deepfos/lib/sysutils.py +703 -0
- deepfos/lib/utils.py +1003 -0
- deepfos/local.py +160 -0
- deepfos/options.py +670 -0
- deepfos/translation.py +237 -0
- deepfos-1.1.60.dist-info/METADATA +33 -0
- deepfos-1.1.60.dist-info/RECORD +175 -0
- deepfos-1.1.60.dist-info/WHEEL +5 -0
- deepfos-1.1.60.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,943 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Union, Type, Dict, List, Any, Tuple, Set, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
from deepfos.api.journal_template import JournalTemplateAPI
|
|
9
|
+
from deepfos.api.models.journal_template import *
|
|
10
|
+
from deepfos.element.apvlprocess import ApprovalProcess, AsyncApprovalProcess
|
|
11
|
+
from deepfos.element.base import ElementBase, SyncMeta
|
|
12
|
+
from deepfos.element.datatable import (
|
|
13
|
+
DataTableMySQL, AsyncDataTableMySQL, get_table_class,
|
|
14
|
+
T_AsyncDatatableClass, T_DatatableClass
|
|
15
|
+
)
|
|
16
|
+
from deepfos.element.rolestrategy import RoleStrategy, AsyncRoleStrategy
|
|
17
|
+
from deepfos.lib.asynchronous import future_property
|
|
18
|
+
from deepfos.lib.decorator import cached_property
|
|
19
|
+
|
|
20
|
+
__all__ = ['AsyncJournalTemplate', 'JournalTemplate', 'FullPostingParameter']
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ORDER_TYPE = "orderType"
|
|
24
|
+
FILTER_VALUE = "filterValue"
|
|
25
|
+
FILTER_TYPE = "filterType"
|
|
26
|
+
FIELD_TYPE = "fieldType"
|
|
27
|
+
TABLE_TYPE = "tableType"
|
|
28
|
+
COLUMN_CODE = "columnCode"
|
|
29
|
+
COMMON_PARAMETERS = 'Common_parameters'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Order(Enum):
|
|
33
|
+
asc = "asc"
|
|
34
|
+
desc = "desc"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _valid_and_maybe_join_value(where):
|
|
38
|
+
type_error = {}
|
|
39
|
+
for dim, value in where.items():
|
|
40
|
+
if isinstance(value, list):
|
|
41
|
+
where[dim] = ",".join([str(v) for v in value])
|
|
42
|
+
elif isinstance(value, str):
|
|
43
|
+
pass
|
|
44
|
+
else:
|
|
45
|
+
type_error[dim] = value
|
|
46
|
+
|
|
47
|
+
if type_error:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
"\n".join(
|
|
50
|
+
[f"参数: {dim}的值需为维度成员或维度成员组成的列表,实际值: {value}"
|
|
51
|
+
for dim, value in type_error.items()
|
|
52
|
+
]
|
|
53
|
+
))
|
|
54
|
+
|
|
55
|
+
return where
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class FullPostingParameter:
|
|
59
|
+
"""完整过账筛选信息类
|
|
60
|
+
|
|
61
|
+
将根据提供的过账参数类别自动生成与参数类别名一致的成员变量,并可通过成员变量设置该类别的过账筛选条件
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
param_categories: 过账参数类别
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, param_categories: Union[List, Tuple, Set]):
|
|
69
|
+
if not isinstance(param_categories, (List, Tuple, Set)):
|
|
70
|
+
raise TypeError("预期参数类别为列表、元组或集合")
|
|
71
|
+
|
|
72
|
+
self._all_categories = {}
|
|
73
|
+
|
|
74
|
+
for c in set(list(param_categories)):
|
|
75
|
+
if c.startswith('_'):
|
|
76
|
+
raise ValueError(f'参数类别不可以下划线开头:{c}')
|
|
77
|
+
self._all_categories[c] = {}
|
|
78
|
+
|
|
79
|
+
def __setattr__(self, name: str, value: Dict[str, Union[str, List[str]]]):
|
|
80
|
+
if name == '_all_categories':
|
|
81
|
+
super().__setattr__(name, value)
|
|
82
|
+
else:
|
|
83
|
+
if name not in self._all_categories:
|
|
84
|
+
raise ValueError(f'成员: {name} 未设置为已知参数类别,'
|
|
85
|
+
f'已有参数类别:\n{set(self._all_categories.keys())}')
|
|
86
|
+
self._all_categories[name] = value
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ColumnNode:
|
|
90
|
+
"""组织批量筛选信息中的字段部分,可进一步使用其中的成员方法表示筛选字段的表达式
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
column_code: 字段编码
|
|
94
|
+
table_type: 表类型编码
|
|
95
|
+
field_type: 字段类型编码
|
|
96
|
+
|
|
97
|
+
See Also:
|
|
98
|
+
:class:`QueryBuilder` , :class:`Table`
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(self, column_code, table_type, field_type):
|
|
103
|
+
self.column_code = column_code
|
|
104
|
+
self.filter_type = None
|
|
105
|
+
self.filter_value = None
|
|
106
|
+
self.field_type = field_type
|
|
107
|
+
self.table_type = table_type
|
|
108
|
+
self.order = Order.asc.value
|
|
109
|
+
|
|
110
|
+
def is_in(self, values: Union[List[str], str]) -> 'ColumnNode':
|
|
111
|
+
"""在列表中"""
|
|
112
|
+
if isinstance(values, List):
|
|
113
|
+
values = ",".join([str(v) for v in values])
|
|
114
|
+
|
|
115
|
+
self.filter_type = "in"
|
|
116
|
+
self.filter_value = values
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
def not_in(self, values: Union[List[str], str]) -> 'ColumnNode':
|
|
120
|
+
"""不在列表中"""
|
|
121
|
+
if isinstance(values, List):
|
|
122
|
+
values = ",".join([str(v) for v in values])
|
|
123
|
+
|
|
124
|
+
self.filter_type = "not in"
|
|
125
|
+
self.filter_value = values
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def eq(self, other) -> 'ColumnNode':
|
|
129
|
+
"""等于"""
|
|
130
|
+
self.filter_type = "="
|
|
131
|
+
self.filter_value = other
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
def ne(self, other) -> 'ColumnNode':
|
|
135
|
+
"""不等于"""
|
|
136
|
+
self.filter_type = "!="
|
|
137
|
+
self.filter_value = other
|
|
138
|
+
return self
|
|
139
|
+
|
|
140
|
+
def contains(self, value) -> 'ColumnNode':
|
|
141
|
+
"""包含"""
|
|
142
|
+
self.filter_type = "like"
|
|
143
|
+
self.filter_value = value
|
|
144
|
+
return self
|
|
145
|
+
|
|
146
|
+
def gt(self, other) -> 'ColumnNode':
|
|
147
|
+
"""大于或晚于"""
|
|
148
|
+
self.filter_type = ">"
|
|
149
|
+
self.filter_value = other
|
|
150
|
+
return self
|
|
151
|
+
|
|
152
|
+
def gte(self, other) -> 'ColumnNode':
|
|
153
|
+
"""大于等于"""
|
|
154
|
+
self.filter_type = ">="
|
|
155
|
+
self.filter_value = other
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
def lt(self, other) -> 'ColumnNode':
|
|
159
|
+
"""小于或早于"""
|
|
160
|
+
self.filter_type = "<"
|
|
161
|
+
self.filter_value = other
|
|
162
|
+
return self
|
|
163
|
+
|
|
164
|
+
def lte(self, other) -> 'ColumnNode':
|
|
165
|
+
"""小于等于"""
|
|
166
|
+
self.filter_type = "<="
|
|
167
|
+
self.filter_value = other
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
latter_than = gt
|
|
171
|
+
|
|
172
|
+
earlier_than = lt
|
|
173
|
+
|
|
174
|
+
def asc(self) -> 'ColumnNode':
|
|
175
|
+
"""升序"""
|
|
176
|
+
self.order = Order.asc.value
|
|
177
|
+
return self
|
|
178
|
+
|
|
179
|
+
def desc(self) -> 'ColumnNode':
|
|
180
|
+
"""降序"""
|
|
181
|
+
self.order = Order.desc.value
|
|
182
|
+
return self
|
|
183
|
+
|
|
184
|
+
def to_columns(self) -> Dict[str, Union[str, int]]:
|
|
185
|
+
return {COLUMN_CODE: self.column_code,
|
|
186
|
+
TABLE_TYPE: self.table_type,
|
|
187
|
+
FIELD_TYPE: self.field_type}
|
|
188
|
+
|
|
189
|
+
def to_where(self) -> Dict[str, Union[str, int]]:
|
|
190
|
+
return {COLUMN_CODE: self.column_code,
|
|
191
|
+
FILTER_TYPE: self.filter_type,
|
|
192
|
+
FILTER_VALUE: self.filter_value,
|
|
193
|
+
TABLE_TYPE: self.table_type,
|
|
194
|
+
FIELD_TYPE: self.field_type}
|
|
195
|
+
|
|
196
|
+
def to_orderby(self) -> Dict[str, str]:
|
|
197
|
+
return {COLUMN_CODE: self.column_code,
|
|
198
|
+
ORDER_TYPE: self.order}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class Table:
|
|
202
|
+
"""组织批量筛选信息中的表,从中访问成员等同于指定表的某列,将获得以相应成员名命名的 :class:`ColumnNode` 对象
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
field_type: 单据模型元素内的主表和子表的字段信息字典,键为字段编码,值为主子表标识与字段类型标识的元组
|
|
206
|
+
|
|
207
|
+
See Also:
|
|
208
|
+
:class:`QueryBuilder` , :class:`ColumnNode`
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
def __init__(self, field_type):
|
|
213
|
+
self._field_type = field_type
|
|
214
|
+
|
|
215
|
+
def __getattr__(self, item) -> ColumnNode:
|
|
216
|
+
if item not in self._field_type:
|
|
217
|
+
raise ValueError(f"字段名需为当前单据模板表中字段,现有字段: {list(self._field_type.keys())}")
|
|
218
|
+
return ColumnNode(item, *self._field_type[item])
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class QueryBuilder:
|
|
222
|
+
"""组织批量筛选信息的主体
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
field_type: 单据模型元素内的主表和子表的字段信息字典,键为字段编码,值为主子表标识与字段类型标识的元组
|
|
226
|
+
|
|
227
|
+
See Also:
|
|
228
|
+
:class:`Table` , :class:`ColumnNode`
|
|
229
|
+
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
def __init__(self, field_type):
|
|
233
|
+
self._field_type = field_type
|
|
234
|
+
self._columns = []
|
|
235
|
+
self._filters = []
|
|
236
|
+
self._orderby = []
|
|
237
|
+
|
|
238
|
+
def columns(self, cols: List[Union[ColumnNode, str]]):
|
|
239
|
+
"""提供结果列信息"""
|
|
240
|
+
for col in cols:
|
|
241
|
+
if not isinstance(col, (ColumnNode, str)):
|
|
242
|
+
raise ValueError("需提供ColumnNode类的对象或字段名组成的列表作为列信息,ColumnNode可从Table对象创建")
|
|
243
|
+
|
|
244
|
+
if isinstance(col, ColumnNode):
|
|
245
|
+
self._columns.append(col.to_columns())
|
|
246
|
+
else:
|
|
247
|
+
if col not in self._field_type:
|
|
248
|
+
raise ValueError(f"字段名需为当前单据模板表中字段,现有字段: {list(self._field_type.keys())}")
|
|
249
|
+
self._columns.append({
|
|
250
|
+
COLUMN_CODE: col,
|
|
251
|
+
TABLE_TYPE: self._field_type[col][0],
|
|
252
|
+
FIELD_TYPE: self._field_type[col][1]
|
|
253
|
+
})
|
|
254
|
+
return self
|
|
255
|
+
|
|
256
|
+
def where(self, filter_list: List[ColumnNode]):
|
|
257
|
+
"""提供筛选列信息"""
|
|
258
|
+
for col in filter_list:
|
|
259
|
+
if not isinstance(col, ColumnNode):
|
|
260
|
+
raise ValueError("需提供ColumnNode类的对象作为筛选条件,ColumnNode可从Table对象创建")
|
|
261
|
+
self._filters.append(col.to_where())
|
|
262
|
+
return self
|
|
263
|
+
|
|
264
|
+
def order_by(self, by: List[ColumnNode]):
|
|
265
|
+
"""提供排序列信息"""
|
|
266
|
+
for col in by:
|
|
267
|
+
if not isinstance(col, ColumnNode):
|
|
268
|
+
raise ValueError("需提供ColumnNode类的对象作为排序信息,ColumnNode可从Table对象创建")
|
|
269
|
+
self._orderby.append(col.to_orderby())
|
|
270
|
+
return self
|
|
271
|
+
|
|
272
|
+
def get_payload(self):
|
|
273
|
+
"""从当前Query组织内容中获得组织结果"""
|
|
274
|
+
if len(self._columns) == 0:
|
|
275
|
+
for (column_code, (table_type, field_type)) in self._field_type.items():
|
|
276
|
+
self._columns.append({
|
|
277
|
+
COLUMN_CODE: column_code,
|
|
278
|
+
FIELD_TYPE: field_type,
|
|
279
|
+
TABLE_TYPE: table_type
|
|
280
|
+
})
|
|
281
|
+
return {
|
|
282
|
+
'headerParams': self._filters,
|
|
283
|
+
'columnParams': self._columns,
|
|
284
|
+
'orderColumns': self._orderby
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class AsyncJournalTemplate(ElementBase[JournalTemplateAPI]):
|
|
289
|
+
"""单据模板"""
|
|
290
|
+
@future_property
|
|
291
|
+
async def meta(self) -> JournalTemplateDetail:
|
|
292
|
+
"""元信息"""
|
|
293
|
+
api = await self.wait_for('async_api')
|
|
294
|
+
ele_info = await self.wait_for('element_info')
|
|
295
|
+
return await api.journaltemplate.query_detail(
|
|
296
|
+
JournalTemplateQuery.construct_from(
|
|
297
|
+
ele_info
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
@cached_property
|
|
302
|
+
def basic_info(self) -> JournalTemplateBaseInfo:
|
|
303
|
+
"""基本信息"""
|
|
304
|
+
return self.meta.journalTemplateInfo.baseInfo
|
|
305
|
+
|
|
306
|
+
@cached_property
|
|
307
|
+
def posting_info(self) -> PostingInfoDto:
|
|
308
|
+
"""过账信息"""
|
|
309
|
+
return self.meta.postingInfo
|
|
310
|
+
|
|
311
|
+
@cached_property
|
|
312
|
+
def _datatable_class(self) -> T_AsyncDatatableClass:
|
|
313
|
+
if (server_name := self.basic_info.approveRecord.serverName) is None:
|
|
314
|
+
return AsyncDataTableMySQL
|
|
315
|
+
|
|
316
|
+
return get_table_class(server_name, sync=False)
|
|
317
|
+
|
|
318
|
+
@cached_property
|
|
319
|
+
def approval_record(self):
|
|
320
|
+
"""审批记录数据表元素"""
|
|
321
|
+
if self.basic_info.approveRecord is None:
|
|
322
|
+
raise ValueError('无审批记录表')
|
|
323
|
+
|
|
324
|
+
return self._datatable_class(
|
|
325
|
+
element_name=self.basic_info.approveRecord.dbName,
|
|
326
|
+
folder_id=self.basic_info.approveRecord.folderId,
|
|
327
|
+
path=self.basic_info.approveRecord.dbPath,
|
|
328
|
+
table_name=self.basic_info.approveRecord.actualTableName
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
@cached_property
|
|
332
|
+
def close_account(self):
|
|
333
|
+
"""关账流程数据表元素"""
|
|
334
|
+
if self.basic_info.closeAccountDB is None:
|
|
335
|
+
raise ValueError('无关账流程表')
|
|
336
|
+
|
|
337
|
+
return self._datatable_class(
|
|
338
|
+
element_name=self.basic_info.closeAccountDB.dbName,
|
|
339
|
+
folder_id=self.basic_info.closeAccountDB.folderId,
|
|
340
|
+
path=self.basic_info.closeAccountDB.dbPath,
|
|
341
|
+
table_name=self.basic_info.closeAccountDB.actualTableName
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
@cached_property
|
|
345
|
+
def approval_process(self) -> AsyncApprovalProcess:
|
|
346
|
+
"""审批流元素"""
|
|
347
|
+
approval_info = self.meta.journalTemplateInfo.approvalProcessInfo
|
|
348
|
+
|
|
349
|
+
if approval_info is None or approval_info.elementDetail is None:
|
|
350
|
+
raise ValueError('无审批流元素信息')
|
|
351
|
+
|
|
352
|
+
approval_ele = approval_info.elementDetail
|
|
353
|
+
|
|
354
|
+
return AsyncApprovalProcess(element_name=approval_ele.elementName,
|
|
355
|
+
folder_id=approval_ele.folderId,
|
|
356
|
+
path=approval_ele.path)
|
|
357
|
+
|
|
358
|
+
@cached_property
|
|
359
|
+
def role_strategy(self) -> AsyncRoleStrategy:
|
|
360
|
+
"""角色方案元素"""
|
|
361
|
+
if (rs_detail := self.basic_info.rsElementDetail) is None:
|
|
362
|
+
raise ValueError('无角色方案元素信息')
|
|
363
|
+
|
|
364
|
+
return AsyncRoleStrategy(
|
|
365
|
+
element_name=rs_detail.elementName,
|
|
366
|
+
folder_id=rs_detail.folderId,
|
|
367
|
+
path=rs_detail.path,
|
|
368
|
+
server_name=rs_detail.serverName
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
@future_property
|
|
372
|
+
async def posting_params(self) -> PostingParam:
|
|
373
|
+
"""过账参数信息"""
|
|
374
|
+
api = await self.wait_for('async_api')
|
|
375
|
+
ele_info = await self.wait_for('element_info')
|
|
376
|
+
res = await api.posting.query_posting_param(
|
|
377
|
+
PostingParamQueryDto.construct_from(ele_info)
|
|
378
|
+
)
|
|
379
|
+
return res.params
|
|
380
|
+
|
|
381
|
+
@cached_property
|
|
382
|
+
def fixed_posting_dims(self):
|
|
383
|
+
res = {}
|
|
384
|
+
for cate, dim_list in self.posting_params.items():
|
|
385
|
+
res[cate] = set([detail['dimensionCode'] for detail in dim_list])
|
|
386
|
+
return res
|
|
387
|
+
|
|
388
|
+
@cached_property
|
|
389
|
+
def header_info(self) -> JournalTemplateAreaInfo:
|
|
390
|
+
"""日记账头信息"""
|
|
391
|
+
return self.meta.journalTemplateInfo.headerInfo
|
|
392
|
+
|
|
393
|
+
@cached_property
|
|
394
|
+
def header_columns(self):
|
|
395
|
+
"""日记账头列信息"""
|
|
396
|
+
return [
|
|
397
|
+
col.dataTableInfo.columnCode
|
|
398
|
+
for col in self.header_info.columns
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
@cached_property
|
|
402
|
+
def body_info(self) -> JournalTemplateAreaInfo:
|
|
403
|
+
"""日记账体信息"""
|
|
404
|
+
return self.meta.journalTemplateInfo.bodyInfo
|
|
405
|
+
|
|
406
|
+
@cached_property
|
|
407
|
+
def body_columns(self):
|
|
408
|
+
"""日记账体列信息"""
|
|
409
|
+
return [
|
|
410
|
+
col.dataTableInfo.columnCode
|
|
411
|
+
for col in self.body_info.columns
|
|
412
|
+
]
|
|
413
|
+
|
|
414
|
+
@cached_property
|
|
415
|
+
def foot_info(self) -> JournalTemplateAreaInfo:
|
|
416
|
+
"""单据尾信息"""
|
|
417
|
+
return self.meta.journalTemplateInfo.footInfo
|
|
418
|
+
|
|
419
|
+
@cached_property
|
|
420
|
+
def foot_columns(self):
|
|
421
|
+
"""单据列信息"""
|
|
422
|
+
return [
|
|
423
|
+
col.dataTableInfo.columnCode
|
|
424
|
+
for col in self.foot_info.columns
|
|
425
|
+
]
|
|
426
|
+
|
|
427
|
+
@cached_property
|
|
428
|
+
def field_type(self):
|
|
429
|
+
"""字段信息"""
|
|
430
|
+
field_type = {col.dataTableInfo.columnCode: (1, col.logicInfo.valueType)
|
|
431
|
+
for col in self.header_info.columns}
|
|
432
|
+
field_type.update({col.dataTableInfo.columnCode: (2, col.logicInfo.valueType)
|
|
433
|
+
for col in self.body_info.columns})
|
|
434
|
+
return field_type
|
|
435
|
+
|
|
436
|
+
@cached_property
|
|
437
|
+
def table(self) -> Table:
|
|
438
|
+
"""组织批量筛选信息中的表对象"""
|
|
439
|
+
return Table(self.field_type)
|
|
440
|
+
|
|
441
|
+
def new_query(self) -> QueryBuilder:
|
|
442
|
+
"""获得新的组织批量筛选信息的对象"""
|
|
443
|
+
return QueryBuilder(self.field_type)
|
|
444
|
+
|
|
445
|
+
def _gen_single_data(self, main_row, body, main_column, body_column):
|
|
446
|
+
main_index = main_row.name
|
|
447
|
+
if isinstance(body.loc[main_index], pd.DataFrame):
|
|
448
|
+
body_data = JournalBodyData(
|
|
449
|
+
bodyActualTableName=self.body_info.dataTableBaseInfo.actualTableName,
|
|
450
|
+
data=body.loc[main_index][body_column].to_dict(orient='records'),
|
|
451
|
+
delEntryCode=[])
|
|
452
|
+
else:
|
|
453
|
+
body_data = JournalBodyData(
|
|
454
|
+
bodyActualTableName=self.body_info.dataTableBaseInfo.actualTableName,
|
|
455
|
+
data=[body.loc[main_index][body_column].to_dict()],
|
|
456
|
+
delEntryCode=[])
|
|
457
|
+
single_data = JournalData(
|
|
458
|
+
mainData=JournalMainData(
|
|
459
|
+
journalCode='',
|
|
460
|
+
columnCode='',
|
|
461
|
+
mainActualTableName=self.header_info.dataTableBaseInfo.actualTableName,
|
|
462
|
+
data=main_row[main_column].to_dict()
|
|
463
|
+
),
|
|
464
|
+
bodyData=body_data)
|
|
465
|
+
|
|
466
|
+
return single_data
|
|
467
|
+
|
|
468
|
+
@staticmethod
|
|
469
|
+
def _yield_data(data: List[JournalData], chunksize: int):
|
|
470
|
+
for start in range(0, len(data), chunksize):
|
|
471
|
+
yield data[start: start + chunksize]
|
|
472
|
+
|
|
473
|
+
async def batch_save(
|
|
474
|
+
self,
|
|
475
|
+
main: pd.DataFrame,
|
|
476
|
+
body: pd.DataFrame,
|
|
477
|
+
columns: List[str] = None,
|
|
478
|
+
chunksize: int = 1000
|
|
479
|
+
):
|
|
480
|
+
"""批量保存日记账头和日记账体数据
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
main: 日记账头数据Dataframe
|
|
484
|
+
body: 日记账体数据Dataframe
|
|
485
|
+
columns: 日记账头和日记账体的关联列,如提供,将以此作为日记账头和日记账体的索引,并关联组织批量保存的数据
|
|
486
|
+
chunksize: 保存单批次数据大小(以单行日记账头为单位)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
.. admonition:: 示例
|
|
490
|
+
|
|
491
|
+
#. 初始化日记账元素
|
|
492
|
+
|
|
493
|
+
.. code-block:: python
|
|
494
|
+
|
|
495
|
+
from deepfos.element.journal_template import JournalTemplate
|
|
496
|
+
jt = JournalTemplate('Journal_elimadj')
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
#. 准备日记账头和日记账体数据
|
|
500
|
+
|
|
501
|
+
.. code-block:: python
|
|
502
|
+
|
|
503
|
+
main = pd.DataFrame([
|
|
504
|
+
{"journal_name": "foo", "year": "2022", "period": "1", "entity": "JH", "rate": 1},
|
|
505
|
+
{"journal_name": "bar", "year": "2021", "period": "2", "entity": "JH", "rate": 2}
|
|
506
|
+
])
|
|
507
|
+
|
|
508
|
+
body = pd.DataFrame([
|
|
509
|
+
{"ICP": "HC", "account": "101", "rate": 1, "original_credit": 222, "credit": 222},
|
|
510
|
+
{"ICP": "HCX", "account": "o1", "rate": 2, "original_credit": 111, "credit": 111},
|
|
511
|
+
{"ICP": "HC", "account": "102", "rate": 1, "original_credit": 222, "credit": 222},
|
|
512
|
+
{"ICP": "HCX", "account": "o2", "rate": 2, "original_credit": 111, "credit": 111}
|
|
513
|
+
]
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
#. 在传入 `batch_save` 方法前,通过设定日记账头和日记账体的index的方式准备关联关系,然后传入 `batch_save` 方法
|
|
517
|
+
|
|
518
|
+
.. code-block:: python
|
|
519
|
+
|
|
520
|
+
main = main.set_index(['rate'], drop=False)
|
|
521
|
+
body = body.set_index(['rate'], drop=False)
|
|
522
|
+
|
|
523
|
+
jt.batch_save(main, body)
|
|
524
|
+
|
|
525
|
+
#. 对于关联列为相同列名时,亦可直接在 `batch_save` 的入参中提供列信息,将以该列信息做后续关联
|
|
526
|
+
|
|
527
|
+
.. code-block:: python
|
|
528
|
+
|
|
529
|
+
jt.batch_save(main, body, columns=['rate'])
|
|
530
|
+
|
|
531
|
+
#. 这两种写法在当前例子中等价
|
|
532
|
+
|
|
533
|
+
保存的batchData:
|
|
534
|
+
|
|
535
|
+
.. code-block:: python
|
|
536
|
+
|
|
537
|
+
[
|
|
538
|
+
JournalData(
|
|
539
|
+
mainData=JournalMainData(
|
|
540
|
+
mainActualTableName='main_table_actual_name',
|
|
541
|
+
data={'journal_name': 'foo', 'entity': 'JH', 'period': '1', 'year': '2022'}
|
|
542
|
+
),
|
|
543
|
+
bodyData=JournalBodyData(
|
|
544
|
+
bodyActualTableName='body_table_actual_name',
|
|
545
|
+
data=[
|
|
546
|
+
{'original_credit': '222', 'account': '101', 'ICP': 'HC', 'credit': '222', 'rate': '1'},
|
|
547
|
+
{'original_credit': '222', 'account': '102', 'ICP': 'HC', 'credit': '222', 'rate': '1'}
|
|
548
|
+
]
|
|
549
|
+
)
|
|
550
|
+
),
|
|
551
|
+
JournalData(
|
|
552
|
+
mainData=JournalMainData(
|
|
553
|
+
mainActualTableName='main_table_actual_name',
|
|
554
|
+
data={'journal_name': 'bar', 'entity': 'JH', 'period': '2', 'year': '2021'}
|
|
555
|
+
),
|
|
556
|
+
bodyData=JournalBodyData(
|
|
557
|
+
bodyActualTableName='body_table_actual_name',
|
|
558
|
+
data=[
|
|
559
|
+
{'original_credit': '111', 'account': 'o1', 'ICP': 'HCX', 'credit': '111', 'rate': '2'},
|
|
560
|
+
{'original_credit': '111', 'account': 'o2', 'ICP': 'HCX', 'credit': '111', 'rate': '2'}
|
|
561
|
+
]
|
|
562
|
+
)
|
|
563
|
+
)
|
|
564
|
+
]
|
|
565
|
+
|
|
566
|
+
"""
|
|
567
|
+
if columns is not None:
|
|
568
|
+
if set(columns)-set(main.columns) != set() or set(columns)-set(body.columns) != set():
|
|
569
|
+
raise ValueError("关联列不同时属于提供的主表和子表DataFrame")
|
|
570
|
+
|
|
571
|
+
main = main.set_index(columns, drop=False)
|
|
572
|
+
body = body.set_index(columns, drop=False)
|
|
573
|
+
|
|
574
|
+
main = main.fillna('').astype(str)
|
|
575
|
+
body = body.fillna('').astype(str)
|
|
576
|
+
|
|
577
|
+
try:
|
|
578
|
+
main_column = list(set(main.columns).intersection(self.header_columns))
|
|
579
|
+
body_column = list(set(body.columns).intersection(self.body_columns))
|
|
580
|
+
batch_data = main.apply(self._gen_single_data,
|
|
581
|
+
body=body, main_column=main_column, body_column=body_column,
|
|
582
|
+
axis=1).to_list()
|
|
583
|
+
except KeyError:
|
|
584
|
+
logger.exception('组织日记账头与日记账体中发生错误')
|
|
585
|
+
raise ValueError('日记账头与日记账体无法正确关联')
|
|
586
|
+
|
|
587
|
+
for part in self._yield_data(batch_data, chunksize):
|
|
588
|
+
await self.async_api.journal.batch_save(
|
|
589
|
+
JournalBatchDataDTO(
|
|
590
|
+
path=self._path,
|
|
591
|
+
folderId=self.element_info.folderId,
|
|
592
|
+
templateCode=self.element_name,
|
|
593
|
+
isNew=True,
|
|
594
|
+
batchData=part
|
|
595
|
+
)
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
async def delete(self, where: Union[pd.DataFrame, Dict[str, List]]) -> DataTableCustomSqlResultDTO:
|
|
599
|
+
"""按条件删除日记账数据
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
where: 日记账头内字段与值的列表组成的字典,或需删除的数据所组成的dataFrame
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
"""
|
|
606
|
+
if isinstance(where, pd.DataFrame):
|
|
607
|
+
where = where.fillna('')
|
|
608
|
+
where = where.astype(str)
|
|
609
|
+
where = where.to_dict(orient='list')
|
|
610
|
+
|
|
611
|
+
if len(set(self.header_columns).union(where.keys())) != len(set(self.header_columns)):
|
|
612
|
+
raise ValueError(f'筛选字段需为日记账头内字段,日记账头字段: {self.header_columns}')
|
|
613
|
+
|
|
614
|
+
for k, v in where.items():
|
|
615
|
+
where[k] = [e for e in set(v) if e != '']
|
|
616
|
+
|
|
617
|
+
return await self.async_api.journal.delete_by_filter(
|
|
618
|
+
JournalDataBatchDel(
|
|
619
|
+
templateCode=self.element_name,
|
|
620
|
+
path=self._path,
|
|
621
|
+
folderId=self.element_info.folderId,
|
|
622
|
+
memberInfo=where
|
|
623
|
+
)
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
def _get_posting_payload(self, where) -> PostingRequestDto:
|
|
627
|
+
param_body = self.posting_params
|
|
628
|
+
fixed_para_category = None
|
|
629
|
+
|
|
630
|
+
if len(param_body) == 1:
|
|
631
|
+
fixed_para_category = list(param_body.keys())[0]
|
|
632
|
+
else:
|
|
633
|
+
if not isinstance(where, FullPostingParameter):
|
|
634
|
+
if COMMON_PARAMETERS in param_body:
|
|
635
|
+
fixed_para_category = COMMON_PARAMETERS
|
|
636
|
+
else:
|
|
637
|
+
raise ValueError('过账参数类别不唯一,且无“共有参数”,需提供具体过账类别信息,'
|
|
638
|
+
f'当前已有过账类别:[{list(param_body.keys())}]\n'
|
|
639
|
+
f'具体过账参数信息可通过 posting_params 和 posting_info 查看')
|
|
640
|
+
|
|
641
|
+
where_with_category = {}
|
|
642
|
+
|
|
643
|
+
not_pov_dim = set()
|
|
644
|
+
|
|
645
|
+
if isinstance(where, FullPostingParameter):
|
|
646
|
+
for category, value in where._all_categories.items(): # noqa
|
|
647
|
+
value = _valid_and_maybe_join_value(value)
|
|
648
|
+
where_with_category[category] = value
|
|
649
|
+
not_pov_dim = not_pov_dim.union(set(value.keys())-self.fixed_posting_dims[category])
|
|
650
|
+
else:
|
|
651
|
+
where = _valid_and_maybe_join_value(where)
|
|
652
|
+
where_with_category[fixed_para_category] = where
|
|
653
|
+
not_pov_dim = set(where.keys()) - self.fixed_posting_dims[fixed_para_category]
|
|
654
|
+
|
|
655
|
+
if unknown_param := (set(where_with_category.keys()) - set(param_body.keys())):
|
|
656
|
+
raise ValueError(f"参数类型: {unknown_param} 不在当前日记账模板中")
|
|
657
|
+
|
|
658
|
+
if not_pov_dim:
|
|
659
|
+
raise ValueError(f"{not_pov_dim} 不在过账筛选的固定维度中")
|
|
660
|
+
|
|
661
|
+
for category, where in where_with_category.items():
|
|
662
|
+
for dim_filter in param_body[category]:
|
|
663
|
+
if (dim := dim_filter['dimensionCode']) in where:
|
|
664
|
+
dim_filter['defaultValue'] = where[dim]
|
|
665
|
+
|
|
666
|
+
return PostingRequestDto(
|
|
667
|
+
templateQuery=JournalTemplateQuery.construct_from(self.element_info),
|
|
668
|
+
postingInfoParam=PostingParam(params=param_body)
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
async def get_posting(
|
|
672
|
+
self,
|
|
673
|
+
where: Union[Dict[str, Union[str, List[str]]], FullPostingParameter]
|
|
674
|
+
) -> Any:
|
|
675
|
+
"""按条件选取过账
|
|
676
|
+
|
|
677
|
+
Args:
|
|
678
|
+
where: 筛选条件,如仅提供字段名与值组成的字典,则在共有参数中进行筛选,
|
|
679
|
+
如需增加其他内存财务模型中特有的参数,请使用FullPostingParameter类构造特有参数信息
|
|
680
|
+
|
|
681
|
+
See Also:
|
|
682
|
+
:class:`FullPostingParameter`
|
|
683
|
+
|
|
684
|
+
"""
|
|
685
|
+
return await self.async_api.posting.get_posting(self._get_posting_payload(where))
|
|
686
|
+
|
|
687
|
+
async def cancel_posting(
|
|
688
|
+
self,
|
|
689
|
+
where: Union[Dict[str, Union[str, List[str]]], FullPostingParameter]
|
|
690
|
+
) -> Any:
|
|
691
|
+
"""按条件选取取消过账
|
|
692
|
+
|
|
693
|
+
Args:
|
|
694
|
+
where: 筛选条件,如仅提供字段名与值组成的字典,则在共有参数中进行筛选,
|
|
695
|
+
如需增加其他内存财务模型中特有的参数,请使用FullPostingParameter类构造特有参数信息
|
|
696
|
+
|
|
697
|
+
See Also:
|
|
698
|
+
:class:`FullPostingParameter`
|
|
699
|
+
|
|
700
|
+
"""
|
|
701
|
+
return await self.async_api.posting.cancel_posting(self._get_posting_payload(where))
|
|
702
|
+
|
|
703
|
+
async def batch_query_raw(
|
|
704
|
+
self,
|
|
705
|
+
query: QueryBuilder,
|
|
706
|
+
show_detail: bool = False,
|
|
707
|
+
need_desc: bool = False
|
|
708
|
+
) -> List[Dict]:
|
|
709
|
+
"""批量查询日记账数据
|
|
710
|
+
|
|
711
|
+
Args:
|
|
712
|
+
query: QueryBuilder类的对象,表示了筛选条件全信息
|
|
713
|
+
show_detail: 在涉及维度筛选时,是否包括其下明细,默认为False
|
|
714
|
+
need_desc: 返回数据是否包括描述,默认为False
|
|
715
|
+
|
|
716
|
+
Returns:
|
|
717
|
+
日记账查询的原始数据
|
|
718
|
+
|
|
719
|
+
.. admonition:: 示例
|
|
720
|
+
|
|
721
|
+
#. 初始化
|
|
722
|
+
|
|
723
|
+
.. code-block:: python
|
|
724
|
+
|
|
725
|
+
from deepfos.element.journal_template import JournalTemplate
|
|
726
|
+
jt = JournalTemplate('Journal_elimadj')
|
|
727
|
+
t = jt.table
|
|
728
|
+
q = jt.query
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
#. 在不提供列信息时,等同于查询所有列,返回值将包括符合条件的所有非全空列数据
|
|
732
|
+
|
|
733
|
+
.. code-block:: python
|
|
734
|
+
|
|
735
|
+
# 查询account字段等于1001001且ICP字段不等于1.0的所有非全空列数据
|
|
736
|
+
# 以period倒序,posting_status倒序的方式排序
|
|
737
|
+
q = jt.new_query()
|
|
738
|
+
data = jt.batch_query_raw(
|
|
739
|
+
q.where([
|
|
740
|
+
t.account.eq("1001001"),
|
|
741
|
+
t.ICP.ne("1.0"),
|
|
742
|
+
])
|
|
743
|
+
.order_by([t.period.desc(), t.posting_status.desc()])
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
#. 在提供列信息时,将查询指定列
|
|
747
|
+
|
|
748
|
+
.. code-block:: python
|
|
749
|
+
|
|
750
|
+
# 查询ICP字段等于HC且period大于2的posting_status、invalid_status、data_status、remark列非全空列数据
|
|
751
|
+
q = jt.new_query()
|
|
752
|
+
data = jt.batch_query_raw(
|
|
753
|
+
q.columns([t.posting_status, t.invalid_status, t.data_status, t.remark])
|
|
754
|
+
.where([
|
|
755
|
+
t.ICP.eq("HC"),
|
|
756
|
+
t.period.gt("2"),
|
|
757
|
+
]
|
|
758
|
+
)
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
See Also:
|
|
762
|
+
:meth:`new_query`,如果希望返回 ``DataFrame`` 的数据,可以使用 :meth:`batch_query`
|
|
763
|
+
|
|
764
|
+
"""
|
|
765
|
+
if not isinstance(query, QueryBuilder):
|
|
766
|
+
raise TypeError("该方法的query参数应为QueryBuilder类的对象")
|
|
767
|
+
|
|
768
|
+
payload = JournalOrderDataBatchQuery(
|
|
769
|
+
templateCode=self.element_name,
|
|
770
|
+
folderId=self.element_info.folderId,
|
|
771
|
+
path=self._path,
|
|
772
|
+
needDesc=need_desc,
|
|
773
|
+
showDetail=show_detail,
|
|
774
|
+
**query.get_payload()
|
|
775
|
+
)
|
|
776
|
+
res = await self.async_api.journal.get_batch_data(payload)
|
|
777
|
+
return res.selectResult
|
|
778
|
+
|
|
779
|
+
async def batch_query(
|
|
780
|
+
self,
|
|
781
|
+
query: QueryBuilder,
|
|
782
|
+
show_detail: bool = False
|
|
783
|
+
) -> pd.DataFrame:
|
|
784
|
+
"""批量查询日记账数据
|
|
785
|
+
|
|
786
|
+
Args:
|
|
787
|
+
query: QueryBuilder类的对象,表示了筛选条件全信息
|
|
788
|
+
show_detail: 在涉及维度筛选时,是否包括其下明细,为True则包括
|
|
789
|
+
|
|
790
|
+
Returns:
|
|
791
|
+
日记账查询的结果DataFrame
|
|
792
|
+
|
|
793
|
+
.. admonition:: 示例
|
|
794
|
+
|
|
795
|
+
#. 初始化
|
|
796
|
+
|
|
797
|
+
.. code-block:: python
|
|
798
|
+
|
|
799
|
+
from deepfos.element.journal_template import JournalTemplate
|
|
800
|
+
jt = JournalTemplate('Journal_elimadj')
|
|
801
|
+
t = jt.table
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
#. 在不提供列信息时,等同于查询所有列,返回值将包括符合条件的所有非全空列数据
|
|
805
|
+
|
|
806
|
+
.. code-block:: python
|
|
807
|
+
|
|
808
|
+
# 查询account字段等于1001001且ICP字段不等于1.0的所有非全空列数据
|
|
809
|
+
# 以period倒序,posting_status倒序的方式排序
|
|
810
|
+
q = jt.new_query()
|
|
811
|
+
data = jt.batch_query(
|
|
812
|
+
q.where([
|
|
813
|
+
t.account.eq("1001001"),
|
|
814
|
+
t.ICP.ne("1.0"),
|
|
815
|
+
])
|
|
816
|
+
.order_by([t.period.desc(), t.posting_status.desc()])
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
#. 在提供列信息时,将查询指定列
|
|
820
|
+
|
|
821
|
+
.. code-block:: python
|
|
822
|
+
|
|
823
|
+
# 查询ICP字段等于HC且period大于2的posting_status、invalid_status、data_status、remark列非全空列数据
|
|
824
|
+
q = jt.new_query()
|
|
825
|
+
data = jt.batch_query(
|
|
826
|
+
q.columns([t.posting_status, t.invalid_status, t.data_status, t.remark])
|
|
827
|
+
.where([
|
|
828
|
+
t.ICP.eq("HC"),
|
|
829
|
+
t.period.gt("2"),
|
|
830
|
+
]
|
|
831
|
+
)
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
See Also:
|
|
835
|
+
:meth:`new_query`,如果希望返回原始数据,可以使用 :meth:`batch_query_raw`
|
|
836
|
+
|
|
837
|
+
"""
|
|
838
|
+
raw = await self.batch_query_raw(query, show_detail)
|
|
839
|
+
return pd.DataFrame(raw)
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
class JournalTemplate(AsyncJournalTemplate, metaclass=SyncMeta):
|
|
843
|
+
synchronize = ('batch_save', 'batch_query', 'batch_query_raw',
|
|
844
|
+
'delete', 'get_posting', 'cancel_posting')
|
|
845
|
+
|
|
846
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
847
|
+
def batch_save(self, main: pd.DataFrame, body: pd.DataFrame, columns: List[str] = None, chunksize: int = 1000):
|
|
848
|
+
...
|
|
849
|
+
|
|
850
|
+
def batch_query(
|
|
851
|
+
self,
|
|
852
|
+
query: QueryBuilder,
|
|
853
|
+
show_detail: bool = False
|
|
854
|
+
) -> pd.DataFrame:
|
|
855
|
+
...
|
|
856
|
+
|
|
857
|
+
def batch_query_raw(
|
|
858
|
+
self,
|
|
859
|
+
query: QueryBuilder,
|
|
860
|
+
show_detail: bool = False,
|
|
861
|
+
need_desc: bool = False
|
|
862
|
+
) -> List[Dict]:
|
|
863
|
+
...
|
|
864
|
+
|
|
865
|
+
def delete(self, where: Union[pd.DataFrame, Dict[str, List]]) -> DataTableCustomSqlResultDTO:
|
|
866
|
+
...
|
|
867
|
+
|
|
868
|
+
def get_posting(
|
|
869
|
+
self,
|
|
870
|
+
where: Union[Dict[str, Union[str, List[str]]],
|
|
871
|
+
FullPostingParameter]
|
|
872
|
+
) -> Any:
|
|
873
|
+
...
|
|
874
|
+
|
|
875
|
+
def cancel_posting(
|
|
876
|
+
self,
|
|
877
|
+
where: Union[Dict[str, Union[str, List[str]]],
|
|
878
|
+
FullPostingParameter]
|
|
879
|
+
) -> Any:
|
|
880
|
+
...
|
|
881
|
+
|
|
882
|
+
@cached_property
|
|
883
|
+
def _datatable_class(self) -> T_DatatableClass:
|
|
884
|
+
if (server_name := self.basic_info.approveRecord.serverName) is None:
|
|
885
|
+
return DataTableMySQL
|
|
886
|
+
|
|
887
|
+
return get_table_class(server_name)
|
|
888
|
+
|
|
889
|
+
@cached_property
|
|
890
|
+
def approval_record(self):
|
|
891
|
+
"""审批记录数据表元素"""
|
|
892
|
+
if self.basic_info.approveRecord is None:
|
|
893
|
+
raise ValueError('无审批记录表')
|
|
894
|
+
|
|
895
|
+
return self._datatable_class(
|
|
896
|
+
element_name=self.basic_info.approveRecord.dbName,
|
|
897
|
+
folder_id=self.basic_info.approveRecord.folderId,
|
|
898
|
+
path=self.basic_info.approveRecord.dbPath,
|
|
899
|
+
table_name=self.basic_info.approveRecord.actualTableName
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
@cached_property
|
|
903
|
+
def close_account(self):
|
|
904
|
+
"""关账流程数据表元素"""
|
|
905
|
+
if self.basic_info.closeAccountDB is None:
|
|
906
|
+
raise ValueError('无关账流程表')
|
|
907
|
+
|
|
908
|
+
return self._datatable_class(
|
|
909
|
+
element_name=self.basic_info.closeAccountDB.dbName,
|
|
910
|
+
folder_id=self.basic_info.closeAccountDB.folderId,
|
|
911
|
+
path=self.basic_info.closeAccountDB.dbPath,
|
|
912
|
+
table_name=self.basic_info.closeAccountDB.actualTableName
|
|
913
|
+
)
|
|
914
|
+
|
|
915
|
+
@cached_property
|
|
916
|
+
def approval_process(self) -> ApprovalProcess:
|
|
917
|
+
"""审批流元素"""
|
|
918
|
+
approval_info = self.meta.journalTemplateInfo.approvalProcessInfo
|
|
919
|
+
|
|
920
|
+
if approval_info is None or approval_info.elementDetail is None:
|
|
921
|
+
raise ValueError('无审批流元素信息')
|
|
922
|
+
|
|
923
|
+
approval_ele = approval_info.elementDetail
|
|
924
|
+
|
|
925
|
+
return ApprovalProcess(
|
|
926
|
+
element_name=approval_ele.elementName,
|
|
927
|
+
folder_id=approval_ele.folderId,
|
|
928
|
+
path=approval_ele.path,
|
|
929
|
+
server_name=approval_ele.serverName
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
@cached_property
|
|
933
|
+
def role_strategy(self) -> RoleStrategy:
|
|
934
|
+
"""角色方案元素"""
|
|
935
|
+
if (rs_detail := self.basic_info.rsElementDetail) is None:
|
|
936
|
+
raise ValueError('无角色方案元素信息')
|
|
937
|
+
|
|
938
|
+
return RoleStrategy(
|
|
939
|
+
element_name=rs_detail.elementName,
|
|
940
|
+
folder_id=rs_detail.folderId,
|
|
941
|
+
path=rs_detail.path,
|
|
942
|
+
server_name=rs_detail.serverName
|
|
943
|
+
)
|