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.
Files changed (175) hide show
  1. deepfos/__init__.py +6 -0
  2. deepfos/_version.py +21 -0
  3. deepfos/algo/__init__.py +0 -0
  4. deepfos/algo/graph.py +171 -0
  5. deepfos/algo/segtree.py +31 -0
  6. deepfos/api/V1_1/__init__.py +0 -0
  7. deepfos/api/V1_1/business_model.py +119 -0
  8. deepfos/api/V1_1/dimension.py +599 -0
  9. deepfos/api/V1_1/models/__init__.py +0 -0
  10. deepfos/api/V1_1/models/business_model.py +1033 -0
  11. deepfos/api/V1_1/models/dimension.py +2768 -0
  12. deepfos/api/V1_2/__init__.py +0 -0
  13. deepfos/api/V1_2/dimension.py +285 -0
  14. deepfos/api/V1_2/models/__init__.py +0 -0
  15. deepfos/api/V1_2/models/dimension.py +2923 -0
  16. deepfos/api/__init__.py +0 -0
  17. deepfos/api/account.py +167 -0
  18. deepfos/api/accounting_engines.py +147 -0
  19. deepfos/api/app.py +626 -0
  20. deepfos/api/approval_process.py +198 -0
  21. deepfos/api/base.py +983 -0
  22. deepfos/api/business_model.py +160 -0
  23. deepfos/api/consolidation.py +129 -0
  24. deepfos/api/consolidation_process.py +106 -0
  25. deepfos/api/datatable.py +341 -0
  26. deepfos/api/deep_pipeline.py +61 -0
  27. deepfos/api/deepconnector.py +36 -0
  28. deepfos/api/deepfos_task.py +92 -0
  29. deepfos/api/deepmodel.py +188 -0
  30. deepfos/api/dimension.py +486 -0
  31. deepfos/api/financial_model.py +319 -0
  32. deepfos/api/journal_model.py +119 -0
  33. deepfos/api/journal_template.py +132 -0
  34. deepfos/api/memory_financial_model.py +98 -0
  35. deepfos/api/models/__init__.py +3 -0
  36. deepfos/api/models/account.py +483 -0
  37. deepfos/api/models/accounting_engines.py +756 -0
  38. deepfos/api/models/app.py +1338 -0
  39. deepfos/api/models/approval_process.py +1043 -0
  40. deepfos/api/models/base.py +234 -0
  41. deepfos/api/models/business_model.py +805 -0
  42. deepfos/api/models/consolidation.py +711 -0
  43. deepfos/api/models/consolidation_process.py +248 -0
  44. deepfos/api/models/datatable_mysql.py +427 -0
  45. deepfos/api/models/deep_pipeline.py +55 -0
  46. deepfos/api/models/deepconnector.py +28 -0
  47. deepfos/api/models/deepfos_task.py +386 -0
  48. deepfos/api/models/deepmodel.py +308 -0
  49. deepfos/api/models/dimension.py +1576 -0
  50. deepfos/api/models/financial_model.py +1796 -0
  51. deepfos/api/models/journal_model.py +341 -0
  52. deepfos/api/models/journal_template.py +854 -0
  53. deepfos/api/models/memory_financial_model.py +478 -0
  54. deepfos/api/models/platform.py +178 -0
  55. deepfos/api/models/python.py +221 -0
  56. deepfos/api/models/reconciliation_engine.py +411 -0
  57. deepfos/api/models/reconciliation_report.py +161 -0
  58. deepfos/api/models/role_strategy.py +884 -0
  59. deepfos/api/models/smartlist.py +237 -0
  60. deepfos/api/models/space.py +1137 -0
  61. deepfos/api/models/system.py +1065 -0
  62. deepfos/api/models/variable.py +463 -0
  63. deepfos/api/models/workflow.py +946 -0
  64. deepfos/api/platform.py +199 -0
  65. deepfos/api/python.py +90 -0
  66. deepfos/api/reconciliation_engine.py +181 -0
  67. deepfos/api/reconciliation_report.py +64 -0
  68. deepfos/api/role_strategy.py +234 -0
  69. deepfos/api/smartlist.py +69 -0
  70. deepfos/api/space.py +582 -0
  71. deepfos/api/system.py +372 -0
  72. deepfos/api/variable.py +154 -0
  73. deepfos/api/workflow.py +264 -0
  74. deepfos/boost/__init__.py +6 -0
  75. deepfos/boost/py_jstream.py +89 -0
  76. deepfos/boost/py_pandas.py +20 -0
  77. deepfos/cache.py +121 -0
  78. deepfos/config.py +6 -0
  79. deepfos/core/__init__.py +27 -0
  80. deepfos/core/cube/__init__.py +10 -0
  81. deepfos/core/cube/_base.py +462 -0
  82. deepfos/core/cube/constants.py +21 -0
  83. deepfos/core/cube/cube.py +408 -0
  84. deepfos/core/cube/formula.py +707 -0
  85. deepfos/core/cube/syscube.py +532 -0
  86. deepfos/core/cube/typing.py +7 -0
  87. deepfos/core/cube/utils.py +238 -0
  88. deepfos/core/dimension/__init__.py +11 -0
  89. deepfos/core/dimension/_base.py +506 -0
  90. deepfos/core/dimension/dimcreator.py +184 -0
  91. deepfos/core/dimension/dimension.py +472 -0
  92. deepfos/core/dimension/dimexpr.py +271 -0
  93. deepfos/core/dimension/dimmember.py +155 -0
  94. deepfos/core/dimension/eledimension.py +22 -0
  95. deepfos/core/dimension/filters.py +99 -0
  96. deepfos/core/dimension/sysdimension.py +168 -0
  97. deepfos/core/logictable/__init__.py +5 -0
  98. deepfos/core/logictable/_cache.py +141 -0
  99. deepfos/core/logictable/_operator.py +663 -0
  100. deepfos/core/logictable/nodemixin.py +673 -0
  101. deepfos/core/logictable/sqlcondition.py +609 -0
  102. deepfos/core/logictable/tablemodel.py +497 -0
  103. deepfos/db/__init__.py +36 -0
  104. deepfos/db/cipher.py +660 -0
  105. deepfos/db/clickhouse.py +191 -0
  106. deepfos/db/connector.py +195 -0
  107. deepfos/db/daclickhouse.py +171 -0
  108. deepfos/db/dameng.py +101 -0
  109. deepfos/db/damysql.py +189 -0
  110. deepfos/db/dbkits.py +358 -0
  111. deepfos/db/deepengine.py +99 -0
  112. deepfos/db/deepmodel.py +82 -0
  113. deepfos/db/deepmodel_kingbase.py +83 -0
  114. deepfos/db/edb.py +214 -0
  115. deepfos/db/gauss.py +83 -0
  116. deepfos/db/kingbase.py +83 -0
  117. deepfos/db/mysql.py +184 -0
  118. deepfos/db/oracle.py +131 -0
  119. deepfos/db/postgresql.py +192 -0
  120. deepfos/db/sqlserver.py +99 -0
  121. deepfos/db/utils.py +135 -0
  122. deepfos/element/__init__.py +89 -0
  123. deepfos/element/accounting.py +348 -0
  124. deepfos/element/apvlprocess.py +215 -0
  125. deepfos/element/base.py +398 -0
  126. deepfos/element/bizmodel.py +1269 -0
  127. deepfos/element/datatable.py +2467 -0
  128. deepfos/element/deep_pipeline.py +186 -0
  129. deepfos/element/deepconnector.py +59 -0
  130. deepfos/element/deepmodel.py +1806 -0
  131. deepfos/element/dimension.py +1254 -0
  132. deepfos/element/fact_table.py +427 -0
  133. deepfos/element/finmodel.py +1485 -0
  134. deepfos/element/journal.py +840 -0
  135. deepfos/element/journal_template.py +943 -0
  136. deepfos/element/pyscript.py +412 -0
  137. deepfos/element/reconciliation.py +553 -0
  138. deepfos/element/rolestrategy.py +243 -0
  139. deepfos/element/smartlist.py +457 -0
  140. deepfos/element/variable.py +756 -0
  141. deepfos/element/workflow.py +560 -0
  142. deepfos/exceptions/__init__.py +239 -0
  143. deepfos/exceptions/hook.py +86 -0
  144. deepfos/lazy.py +104 -0
  145. deepfos/lazy_import.py +84 -0
  146. deepfos/lib/__init__.py +0 -0
  147. deepfos/lib/_javaobj.py +366 -0
  148. deepfos/lib/asynchronous.py +879 -0
  149. deepfos/lib/concurrency.py +107 -0
  150. deepfos/lib/constant.py +39 -0
  151. deepfos/lib/decorator.py +310 -0
  152. deepfos/lib/deepchart.py +778 -0
  153. deepfos/lib/deepux.py +477 -0
  154. deepfos/lib/discovery.py +273 -0
  155. deepfos/lib/edb_lexer.py +789 -0
  156. deepfos/lib/eureka.py +156 -0
  157. deepfos/lib/filterparser.py +751 -0
  158. deepfos/lib/httpcli.py +106 -0
  159. deepfos/lib/jsonstreamer.py +80 -0
  160. deepfos/lib/msg.py +394 -0
  161. deepfos/lib/nacos.py +225 -0
  162. deepfos/lib/patch.py +92 -0
  163. deepfos/lib/redis.py +241 -0
  164. deepfos/lib/serutils.py +181 -0
  165. deepfos/lib/stopwatch.py +99 -0
  166. deepfos/lib/subtask.py +572 -0
  167. deepfos/lib/sysutils.py +703 -0
  168. deepfos/lib/utils.py +1003 -0
  169. deepfos/local.py +160 -0
  170. deepfos/options.py +670 -0
  171. deepfos/translation.py +237 -0
  172. deepfos-1.1.60.dist-info/METADATA +33 -0
  173. deepfos-1.1.60.dist-info/RECORD +175 -0
  174. deepfos-1.1.60.dist-info/WHEEL +5 -0
  175. 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
+ )