deepfos 1.1.65__tar.gz → 1.1.67__tar.gz

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 (191) hide show
  1. {deepfos-1.1.65 → deepfos-1.1.67}/CHANGELOG.md +19 -0
  2. {deepfos-1.1.65 → deepfos-1.1.67}/PKG-INFO +1 -1
  3. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/_version.py +3 -3
  4. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/deepconnector.py +5 -3
  5. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/deepmodel.py +126 -123
  6. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/dimension.py +2 -2
  7. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/finmodel.py +23 -4
  8. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/journal.py +19 -8
  9. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos.egg-info/PKG-INFO +1 -1
  10. {deepfos-1.1.65 → deepfos-1.1.67}/.gitattributes +0 -0
  11. {deepfos-1.1.65 → deepfos-1.1.67}/.gitee/ISSUE_GUIDELINES.md +0 -0
  12. {deepfos-1.1.65 → deepfos-1.1.67}/.gitee/ISSUE_TEMPLATE.md +0 -0
  13. {deepfos-1.1.65 → deepfos-1.1.67}/.gitignore +0 -0
  14. {deepfos-1.1.65 → deepfos-1.1.67}/MANIFEST.in +0 -0
  15. {deepfos-1.1.65 → deepfos-1.1.67}/README.md +0 -0
  16. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/__init__.py +0 -0
  17. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/algo/__init__.py +0 -0
  18. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/algo/graph.py +0 -0
  19. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/V1_1/__init__.py +0 -0
  20. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/V1_1/business_model.py +0 -0
  21. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/V1_1/dimension.py +0 -0
  22. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/V1_1/models/__init__.py +0 -0
  23. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/V1_1/models/business_model.py +0 -0
  24. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/V1_1/models/dimension.py +0 -0
  25. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/V1_2/__init__.py +0 -0
  26. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/V1_2/dimension.py +0 -0
  27. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/V1_2/models/__init__.py +0 -0
  28. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/V1_2/models/dimension.py +0 -0
  29. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/__init__.py +0 -0
  30. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/account.py +0 -0
  31. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/accounting_engines.py +0 -0
  32. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/app.py +0 -0
  33. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/approval_process.py +0 -0
  34. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/base.py +0 -0
  35. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/business_model.py +0 -0
  36. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/consolidation.py +0 -0
  37. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/consolidation_process.py +0 -0
  38. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/datatable.py +0 -0
  39. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/deep_pipeline.py +0 -0
  40. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/deepconnector.py +0 -0
  41. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/deepfos_task.py +0 -0
  42. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/deepmodel.py +0 -0
  43. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/dimension.py +0 -0
  44. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/financial_model.py +0 -0
  45. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/journal_model.py +0 -0
  46. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/journal_template.py +0 -0
  47. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/memory_financial_model.py +0 -0
  48. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/__init__.py +0 -0
  49. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/account.py +0 -0
  50. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/accounting_engines.py +0 -0
  51. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/app.py +0 -0
  52. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/approval_process.py +0 -0
  53. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/base.py +0 -0
  54. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/business_model.py +0 -0
  55. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/consolidation.py +0 -0
  56. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/consolidation_process.py +0 -0
  57. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/datatable_mysql.py +0 -0
  58. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/deep_pipeline.py +0 -0
  59. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/deepconnector.py +0 -0
  60. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/deepfos_task.py +0 -0
  61. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/deepmodel.py +0 -0
  62. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/dimension.py +0 -0
  63. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/financial_model.py +0 -0
  64. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/journal_model.py +0 -0
  65. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/journal_template.py +0 -0
  66. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/memory_financial_model.py +0 -0
  67. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/platform.py +0 -0
  68. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/python.py +0 -0
  69. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/reconciliation_engine.py +0 -0
  70. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/reconciliation_report.py +0 -0
  71. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/role_strategy.py +0 -0
  72. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/smartlist.py +0 -0
  73. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/space.py +0 -0
  74. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/system.py +0 -0
  75. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/variable.py +0 -0
  76. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/models/workflow.py +0 -0
  77. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/platform.py +0 -0
  78. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/python.py +0 -0
  79. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/reconciliation_engine.py +0 -0
  80. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/reconciliation_report.py +0 -0
  81. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/role_strategy.py +0 -0
  82. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/smartlist.py +0 -0
  83. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/space.py +0 -0
  84. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/system.py +0 -0
  85. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/variable.py +0 -0
  86. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/api/workflow.py +0 -0
  87. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/boost/__init__.py +0 -0
  88. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/boost/jstream.c +0 -0
  89. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/boost/jstream.pyx +0 -0
  90. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/boost/pandas.c +0 -0
  91. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/boost/pandas.pyx +0 -0
  92. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/boost/py_jstream.py +0 -0
  93. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/boost/py_pandas.py +0 -0
  94. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/cache.py +0 -0
  95. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/config.py +0 -0
  96. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/__init__.py +0 -0
  97. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/cube/__init__.py +0 -0
  98. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/cube/_base.py +0 -0
  99. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/cube/constants.py +0 -0
  100. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/cube/cube.py +0 -0
  101. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/cube/formula.py +0 -0
  102. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/cube/syscube.py +0 -0
  103. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/cube/typing.py +0 -0
  104. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/cube/utils.py +0 -0
  105. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/dimension/__init__.py +0 -0
  106. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/dimension/_base.py +0 -0
  107. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/dimension/dimcreator.py +0 -0
  108. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/dimension/dimension.py +0 -0
  109. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/dimension/dimexpr.py +0 -0
  110. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/dimension/dimmember.py +0 -0
  111. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/dimension/eledimension.py +0 -0
  112. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/dimension/filters.py +0 -0
  113. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/dimension/sysdimension.py +0 -0
  114. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/logictable/__init__.py +0 -0
  115. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/logictable/_cache.py +0 -0
  116. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/logictable/_operator.py +0 -0
  117. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/logictable/nodemixin.py +0 -0
  118. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/logictable/sqlcondition.py +0 -0
  119. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/core/logictable/tablemodel.py +0 -0
  120. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/__init__.py +0 -0
  121. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/cipher.py +0 -0
  122. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/clickhouse.py +0 -0
  123. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/connector.py +0 -0
  124. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/daclickhouse.py +0 -0
  125. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/dameng.py +0 -0
  126. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/damysql.py +0 -0
  127. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/dbkits.py +0 -0
  128. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/deepengine.py +0 -0
  129. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/deepmodel.py +0 -0
  130. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/deepmodel_kingbase.py +0 -0
  131. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/edb.py +0 -0
  132. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/gauss.py +0 -0
  133. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/kingbase.py +0 -0
  134. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/mysql.py +0 -0
  135. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/oracle.py +0 -0
  136. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/postgresql.py +0 -0
  137. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/sqlserver.py +0 -0
  138. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/db/utils.py +0 -0
  139. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/__init__.py +0 -0
  140. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/accounting.py +0 -0
  141. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/apvlprocess.py +0 -0
  142. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/base.py +0 -0
  143. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/bizmodel.py +0 -0
  144. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/datatable.py +0 -0
  145. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/deep_pipeline.py +0 -0
  146. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/fact_table.py +0 -0
  147. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/journal_template.py +0 -0
  148. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/pyscript.py +0 -0
  149. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/reconciliation.py +0 -0
  150. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/rolestrategy.py +0 -0
  151. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/smartlist.py +0 -0
  152. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/variable.py +0 -0
  153. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/element/workflow.py +0 -0
  154. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/exceptions/__init__.py +0 -0
  155. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/exceptions/hook.py +0 -0
  156. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lazy.py +0 -0
  157. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/__init__.py +0 -0
  158. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/_javaobj.py +0 -0
  159. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/asynchronous.py +0 -0
  160. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/concurrency.py +0 -0
  161. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/constant.py +0 -0
  162. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/decorator.py +0 -0
  163. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/deepchart.py +0 -0
  164. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/deepux.py +0 -0
  165. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/discovery.py +0 -0
  166. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/edb_lexer.py +0 -0
  167. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/eureka.py +0 -0
  168. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/filterparser.py +0 -0
  169. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/httpcli.py +0 -0
  170. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/jsonstreamer.py +0 -0
  171. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/msg.py +0 -0
  172. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/nacos.py +0 -0
  173. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/patch.py +0 -0
  174. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/redis.py +0 -0
  175. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/serutils.py +0 -0
  176. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/stopwatch.py +0 -0
  177. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/subtask.py +0 -0
  178. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/sysutils.py +0 -0
  179. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/lib/utils.py +0 -0
  180. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/local.py +0 -0
  181. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/options.py +0 -0
  182. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos/translation.py +0 -0
  183. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos.egg-info/SOURCES.txt +0 -0
  184. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos.egg-info/dependency_links.txt +0 -0
  185. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos.egg-info/not-zip-safe +0 -0
  186. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos.egg-info/requires.txt +0 -0
  187. {deepfos-1.1.65 → deepfos-1.1.67}/deepfos.egg-info/top_level.txt +0 -0
  188. {deepfos-1.1.65 → deepfos-1.1.67}/requirements.txt +0 -0
  189. {deepfos-1.1.65 → deepfos-1.1.67}/setup.cfg +0 -0
  190. {deepfos-1.1.65 → deepfos-1.1.67}/setup.py +0 -0
  191. {deepfos-1.1.65 → deepfos-1.1.67}/versioneer.py +0 -0
@@ -1,3 +1,22 @@
1
+ ## [1.1.67] - 2025-07-22
2
+
3
+ ### 新增
4
+
5
+ * 财务模型元素查询支持指定透视成员列表
6
+ * 凭证模型数据更新接受指定头行操作类型
7
+
8
+ ### 更新
9
+
10
+ * 兼容维度load_dataframe依据共享成员列分数据时共享成员列非bool列的处理
11
+
12
+
13
+ ## [1.1.66] - 2025-07-10
14
+
15
+ ### 更新
16
+
17
+ * 修复连接器连接信息密码超过16位时的aes解密逻辑
18
+
19
+
1
20
  ## [1.1.65] - 2025-07-09
2
21
 
3
22
  ### 更新
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: deepfos
3
- Version: 1.1.65
3
+ Version: 1.1.67
4
4
  Summary: Collecions of useful and handy tools for deepfos platform
5
5
  Home-page: http://py.deepfos.com
6
6
  Author: deepfos-python-team
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-07-09T10:58:56+0000",
11
+ "date": "2025-07-22T03:24:23+0000",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "7891ba3eb0b577f318140d5ea3b613c82bda9e68",
15
- "version": "1.1.65"
14
+ "full-revisionid": "61559f9a44520b8bcfced0acc40f356ed3c1e6d7",
15
+ "version": "1.1.67"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -13,9 +13,11 @@ __all__ = ['AsyncDeepConnector', 'DeepConnector', 'ConnectionInfo']
13
13
 
14
14
 
15
15
  def decrypt(secret, cipher_text, encoding='utf8'):
16
- pwd_padded = AES(secret).decrypt(
17
- base64.b16decode(cipher_text)
18
- )
16
+ pwd_padded = b''
17
+ for i in range(len(cipher_text))[::32]:
18
+ pwd_padded += AES(secret).decrypt(
19
+ base64.b16decode(cipher_text[i: i + 32])
20
+ )
19
21
  if pwd_padded[-1] in range(1, 17):
20
22
  pad_length = pwd_padded[-1]
21
23
  pad_char = chr(pwd_padded[-1])
@@ -7,7 +7,7 @@ import json
7
7
  import uuid
8
8
  from contextlib import asynccontextmanager, contextmanager
9
9
  from contextvars import ContextVar
10
- from itertools import count
10
+ from itertools import count, chain
11
11
  from typing import (
12
12
  List, TYPE_CHECKING, Any, Dict, Union, NamedTuple,
13
13
  Iterable, Optional, Literal
@@ -37,7 +37,7 @@ from deepfos.lib import serutils
37
37
  from deepfos.lib.asynchronous import future_property, evloop
38
38
  from deepfos.lib.decorator import flagmethod, cached_property, lru_cache
39
39
  from deepfos.lib.utils import (
40
- AliasGenerator, to_version_tuple, ChunkAlert, split_dataframe_alert
40
+ AliasGenerator, to_version_tuple,
41
41
  )
42
42
 
43
43
  __all__ = ['AsyncDeepModel', 'DeepModel', 'to_fields', 'QueryWithArgs']
@@ -793,8 +793,7 @@ class AsyncDeepModel(ElementBase[DeepModelAPI]):
793
793
  self,
794
794
  direct_access: bool = True,
795
795
  pg_dsn: str = None,
796
- before_chunk: ChunkAlert = None,
797
- after_chunk: ChunkAlert = None,
796
+ **kwargs
798
797
  ):
799
798
  self._txn_ = ContextVar('QLTXN')
800
799
  self.appmodule = f"app{OPTION.api.header['app']}"
@@ -805,8 +804,6 @@ class AsyncDeepModel(ElementBase[DeepModelAPI]):
805
804
  self.alias = AliasGenerator()
806
805
  self.pg_dsn = pg_dsn
807
806
  self._globals = None
808
- self.before_chunk = before_chunk
809
- self.after_chunk = after_chunk
810
807
  self._clients = threading.local()
811
808
 
812
809
  @future_property
@@ -1099,6 +1096,10 @@ class AsyncDeepModel(ElementBase[DeepModelAPI]):
1099
1096
  会自动用作所有string形式ql的参数
1100
1097
 
1101
1098
  """
1099
+ qls_with_args = self._collect_execute_qls(qls, kwargs)
1100
+ return await self._maybe_exec_qls(qls_with_args)
1101
+
1102
+ def _collect_execute_qls(self, qls, kwargs):
1102
1103
  self._ensure_client()
1103
1104
  if isinstance(qls, str):
1104
1105
  qls_with_args = [QueryWithArgs(
@@ -1126,8 +1127,7 @@ class AsyncDeepModel(ElementBase[DeepModelAPI]):
1126
1127
  ))
1127
1128
  else:
1128
1129
  raise TypeError(f'qls参数中出现类型非法成员:{type(ql)}')
1129
-
1130
- return await self._maybe_exec_qls(qls_with_args)
1130
+ return qls_with_args
1131
1131
 
1132
1132
  execute.__doc__ = execute.__doc__ + DOC_ARGS_KWARGS
1133
1133
 
@@ -1312,30 +1312,17 @@ class AsyncDeepModel(ElementBase[DeepModelAPI]):
1312
1312
  tgt_main_field[name] = MainField(tgt_bkey, is_multi, link_props)
1313
1313
  return field_info, tgt_main_field
1314
1314
 
1315
- def _collect_qls(
1316
- self,
1317
- data: pd.DataFrame,
1318
- ql: str,
1319
- chunksize: int,
1320
- qls: List[QueryWithArgs]
1321
- ):
1315
+ def _ql_payload(self, data: pd.DataFrame, ql: str,):
1322
1316
  self._ensure_client()
1323
-
1324
- for part, alert in split_dataframe_alert(
1325
- data, chunksize, self.before_chunk, self.after_chunk
1326
- ):
1327
- with alert:
1328
- kw_name = self.alias.get(BATCH_INSERT_KW)
1329
- qls.append(QueryWithArgs(
1330
- commands=ql.replace(
1331
- f'${BATCH_INSERT_KW}', f'${kw_name}'
1332
- ),
1333
- kwargs={kw_name: part.to_json(
1334
- orient='records', double_precision=15,
1335
- force_ascii=False, default_handler=str
1336
- )},
1337
- globals=self._globals
1338
- ))
1317
+ kw_name = self.alias.get(BATCH_INSERT_KW)
1318
+ return QueryWithArgs(
1319
+ commands=ql.replace(f'${BATCH_INSERT_KW}', f'${kw_name}'),
1320
+ kwargs={kw_name: data.to_json(
1321
+ orient='records', double_precision=15,
1322
+ force_ascii=False, default_handler=str
1323
+ )},
1324
+ globals=self._globals,
1325
+ )
1339
1326
 
1340
1327
  @staticmethod
1341
1328
  def _split_self_link(data, relation, structure, bkey):
@@ -1391,6 +1378,87 @@ class AsyncDeepModel(ElementBase[DeepModelAPI]):
1391
1378
  data = data.join(link.to_frame(name), on=bkey)
1392
1379
  return data
1393
1380
 
1381
+ async def _collect_bulk_qls(
1382
+ self,
1383
+ object_name: str,
1384
+ data: pd.DataFrame,
1385
+ relation: Dict[str, pd.DataFrame] = None,
1386
+ chunk_size: int = 500,
1387
+ enable_upsert: bool = False,
1388
+ update_fields: Iterable[str] = None,
1389
+ exclusive_fields: Iterable[str] = None,
1390
+ match_fields: Iterable[str] = None,
1391
+ insert: bool = True
1392
+ ) -> List[List[QueryWithArgs]]:
1393
+ if object_name in self.objects:
1394
+ obj = self.objects[object_name]
1395
+ else:
1396
+ raise ObjectNotExist(
1397
+ f'DeepModel对象[{object_name}]在当前应用不存在,无法插入数据'
1398
+ )
1399
+ if obj.external:
1400
+ raise ExternalObjectReadOnly('外部对象只可读')
1401
+
1402
+ structure = ObjectStructure(name=obj.name, structure=obj.fields.values())
1403
+ self._valid_data(data, object_name, relation, structure, check_required=insert)
1404
+
1405
+ relation = relation or {}
1406
+ bkey = await self._get_bkey(obj)
1407
+ if bkey not in data.columns:
1408
+ raise RequiredFieldUnfilled(f'缺少业务主键[{bkey}]')
1409
+
1410
+ # data拼接relation df
1411
+ data = self._merge_relation(data, relation, structure, bkey)
1412
+ # 从data中分离出self-link更新信息
1413
+ data, self_link_dfs = self._split_self_link(data, relation, structure, bkey)
1414
+ field_info, tgt_main_field = await self._collect_bulk_field_info(
1415
+ object_name, structure, data, relation
1416
+ )
1417
+ field_names = set(map(lambda f: f.name, field_info))
1418
+ if insert:
1419
+ if enable_upsert:
1420
+ self._valid_upsert(obj, field_names, bkey, exclusive_fields, update_fields)
1421
+
1422
+ exclusive_fields = set(exclusive_fields or {bkey}) & set(field_names)
1423
+ update_fields = set(update_fields or (field_names - {bkey})) & set(field_names)
1424
+ if enable_upsert and update_fields:
1425
+ bulk_ql = bulk_upsert_by_fields(
1426
+ object_name, field_info, tgt_main_field,
1427
+ exclusive_fields, update_fields
1428
+ )
1429
+ else:
1430
+ bulk_ql = bulk_insert_by_fields(object_name, field_info, tgt_main_field)
1431
+ else:
1432
+ if missing := (set(match_fields or [bkey]) - set(field_names)):
1433
+ raise ValueError(f"match fields: {missing} 不在提供的数据中")
1434
+
1435
+ match_fields = set(match_fields or [bkey]) & set(field_names)
1436
+ if to_upd := (field_names - match_fields):
1437
+ bulk_ql = bulk_update_by_fields(
1438
+ object_name, field_info, tgt_main_field,
1439
+ match_fields, to_upd
1440
+ )
1441
+ else:
1442
+ bulk_ql = None
1443
+ qls = []
1444
+ self._ensure_client()
1445
+ for i in range(0, len(data), chunk_size):
1446
+ part = structure.fit(data.iloc[i: i + chunk_size])
1447
+ ql_chunk = []
1448
+ # Ignore bulk_ql when only update multi links
1449
+ if bulk_ql is not None:
1450
+ ql_chunk = [self._ql_payload(part, bulk_ql)]
1451
+ for update_field, (update_df, main_field) in self_link_dfs.items():
1452
+ field = structure.fields[update_field]
1453
+ update_ql = bulk_update_by_fields(
1454
+ object_name, [field], {update_field: main_field},
1455
+ [bkey], [update_field]
1456
+ )
1457
+ update_part = update_df.iloc[i: i + chunk_size]
1458
+ ql_chunk.append(self._ql_payload(update_part, update_ql))
1459
+ qls.append(ql_chunk)
1460
+ return qls
1461
+
1394
1462
  @txn_support
1395
1463
  async def insert_df(
1396
1464
  self,
@@ -1401,6 +1469,7 @@ class AsyncDeepModel(ElementBase[DeepModelAPI]):
1401
1469
  enable_upsert: bool = False,
1402
1470
  update_fields: Iterable[str] = None,
1403
1471
  exclusive_fields: Iterable[str] = None,
1472
+ commit_per_chunk: bool = False,
1404
1473
  ) -> None:
1405
1474
  """以事务执行基于DataFrame字段信息的批量插入数据
1406
1475
 
@@ -1421,6 +1490,9 @@ class AsyncDeepModel(ElementBase[DeepModelAPI]):
1421
1490
  exclusive_fields: upsert句式下update的exclusive fields列表,
1422
1491
  涉及的fields需出现在data或relation中,
1423
1492
  默认为业务主键
1493
+ commit_per_chunk: 每次插入后是否提交事务,
1494
+ 默认为False,即所有数据插入后再提交事务
1495
+ 该参数仅在非start transaction上下文中生效
1424
1496
 
1425
1497
  Notes:
1426
1498
 
@@ -1493,54 +1565,16 @@ class AsyncDeepModel(ElementBase[DeepModelAPI]):
1493
1565
  logger.info("data为空,无DML执行")
1494
1566
  return
1495
1567
 
1496
- if object_name in self.objects:
1497
- obj = self.objects[object_name]
1498
- else:
1499
- raise ObjectNotExist(
1500
- f'DeepModel对象[{object_name}]在当前应用不存在,无法插入数据'
1501
- )
1502
- if obj.external:
1503
- raise ExternalObjectReadOnly('外部对象只可读')
1504
-
1505
- structure = ObjectStructure(name=obj.name, structure=obj.fields.values())
1506
-
1507
- self._valid_data(data, object_name, relation, structure)
1508
-
1509
- relation = relation or {}
1510
- bkey = await self._get_bkey(obj)
1511
- # data拼接relation df
1512
- data = self._merge_relation(data, relation, structure, bkey)
1513
- # 从data中分离出self-link更新信息
1514
- data, self_link_dfs = self._split_self_link(data, relation, structure, bkey)
1515
- field_info, tgt_main_field = await self._collect_bulk_field_info(
1516
- object_name, structure, data, relation
1568
+ qls = await self._collect_bulk_qls(
1569
+ object_name, data, relation, chunksize,
1570
+ enable_upsert, update_fields, exclusive_fields,
1571
+ insert=True
1517
1572
  )
1518
- field_names = set(map(lambda f: f.name, field_info))
1519
- if enable_upsert:
1520
- self._valid_upsert(obj, field_names, bkey, exclusive_fields, update_fields)
1521
-
1522
- exclusive_fields = set(exclusive_fields or {bkey}) & set(field_names)
1523
- update_fields = set(update_fields or (field_names - {bkey})) & set(field_names)
1524
- if enable_upsert and update_fields:
1525
- insert_ql = bulk_upsert_by_fields(
1526
- object_name, field_info, tgt_main_field,
1527
- exclusive_fields, update_fields
1528
- )
1573
+ if commit_per_chunk:
1574
+ for ql_chunk in qls:
1575
+ await self.execute(ql_chunk)
1529
1576
  else:
1530
- insert_ql = bulk_insert_by_fields(object_name, field_info, tgt_main_field)
1531
-
1532
- qls = []
1533
- self._collect_qls(structure.fit(data), insert_ql, chunksize, qls)
1534
- if self_link_dfs:
1535
- for update_field, (update_df, main_field) in self_link_dfs.items():
1536
- field = structure.fields[update_field]
1537
- update_ql = bulk_update_by_fields(
1538
- object_name, [field], {update_field: main_field},
1539
- [bkey], [update_field]
1540
- )
1541
- self._collect_qls(update_df, update_ql, chunksize, qls)
1542
-
1543
- await self.execute(qls)
1577
+ await self.execute(list(chain(*qls)))
1544
1578
 
1545
1579
  async def get_object(
1546
1580
  self,
@@ -1693,6 +1727,7 @@ class AsyncDeepModel(ElementBase[DeepModelAPI]):
1693
1727
  relation: Dict[str, pd.DataFrame] = None,
1694
1728
  chunksize: int = 500,
1695
1729
  match_fields: Iterable[str] = None,
1730
+ commit_per_chunk: bool = False,
1696
1731
  ) -> None:
1697
1732
  """以事务执行基于DataFrame字段信息的批量更新数据
1698
1733
 
@@ -1709,57 +1744,23 @@ class AsyncDeepModel(ElementBase[DeepModelAPI]):
1709
1744
  若有link property,则以property名为列名,提供在除source和target的列中
1710
1745
  chunksize: 单次最大行数
1711
1746
  match_fields: update的匹配列表,涉及的fields需出现在data或relation中,默认为业务主键
1747
+ commit_per_chunk: 每次插入后是否提交事务,
1748
+ 默认为False,即所有数据插入后再提交事务
1749
+ 该参数仅在非start transaction上下文中生效
1712
1750
  """
1713
1751
  if data.empty:
1714
1752
  logger.info("data为空,无DML执行")
1715
1753
  return
1716
1754
 
1717
- if object_name in self.objects:
1718
- obj = self.objects[object_name]
1719
- else:
1720
- raise ObjectNotExist(
1721
- f'DeepModel对象[{object_name}]在当前应用不存在,无法更新数据'
1722
- )
1723
- if obj.external:
1724
- raise ExternalObjectReadOnly('外部对象只可读')
1725
-
1726
- structure = ObjectStructure(name=obj.name, structure=obj.fields.values())
1727
- self._valid_data(data, object_name, relation, structure, check_required=False)
1728
- relation = relation or {}
1729
- bkey = await self._get_bkey(obj)
1730
- if bkey not in data.columns:
1731
- raise RequiredFieldUnfilled(f'缺少业务主键[{bkey}]')
1732
- # data拼接relation df
1733
- data = self._merge_relation(data, relation, structure, bkey)
1734
- # 从data中分离出self-link更新信息
1735
- data, self_link_dfs = self._split_self_link(data, relation, structure, bkey)
1736
- field_info, tgt_main_field = await self._collect_bulk_field_info(
1737
- object_name, structure, data, relation
1738
- )
1739
- field_names = set(map(lambda f: f.name, field_info))
1740
-
1741
- if missing := (set(match_fields or [bkey]) - set(field_names)):
1742
- raise ValueError(f"match fields: {missing} 不在提供的数据中")
1743
-
1744
- match_fields = set(match_fields or [bkey]) & set(field_names)
1745
- update_ql = bulk_update_by_fields(
1746
- object_name, field_info, tgt_main_field,
1747
- match_fields, field_names - match_fields
1755
+ qls = await self._collect_bulk_qls(
1756
+ object_name, data, relation, chunksize,
1757
+ match_fields=match_fields, insert=False
1748
1758
  )
1749
- qls = []
1750
- self._collect_qls(structure.fit(data), update_ql, chunksize, qls)
1751
- if self_link_dfs:
1752
- for update_field, (update_df, main_field) in self_link_dfs.items():
1753
- field = structure.fields[update_field]
1754
- update_ql = bulk_update_by_fields(
1755
- object_name, [field],
1756
- {update_field: main_field},
1757
- [bkey],
1758
- [update_field]
1759
- )
1760
- self._collect_qls(update_df, update_ql, chunksize, qls)
1761
-
1762
- await self.execute(qls)
1759
+ if commit_per_chunk:
1760
+ for ql_chunk in qls:
1761
+ await self.execute(ql_chunk)
1762
+ else:
1763
+ await self.execute(list(chain(*qls)))
1763
1764
 
1764
1765
  @asynccontextmanager
1765
1766
  async def start_transaction(self, flatten: bool = False):
@@ -1799,7 +1800,7 @@ class AsyncDeepModel(ElementBase[DeepModelAPI]):
1799
1800
 
1800
1801
  Important:
1801
1802
 
1802
- 仅 :func:`insert_df` :func:`execute` 方法支持在事务中执行
1803
+ 仅 :func:`insert_df` :func:`update_df` :func:`execute` 方法支持在事务中执行
1803
1804
 
1804
1805
  """
1805
1806
  try:
@@ -1913,6 +1914,7 @@ class DeepModel(AsyncDeepModel, metaclass=SyncMeta):
1913
1914
  enable_upsert: bool = False,
1914
1915
  update_fields: Iterable[str] = None,
1915
1916
  exclusive_fields: Iterable[str] = None,
1917
+ commit_per_chunk: bool = False,
1916
1918
  ) -> None:
1917
1919
  ...
1918
1920
 
@@ -1934,6 +1936,7 @@ class DeepModel(AsyncDeepModel, metaclass=SyncMeta):
1934
1936
  relation: Dict[str, pd.DataFrame] = None,
1935
1937
  chunksize: int = 500,
1936
1938
  match_fields: Iterable[str] = None,
1939
+ commit_per_chunk: bool = False,
1937
1940
  ) -> None:
1938
1941
  ...
1939
1942
 
@@ -956,7 +956,7 @@ class AsyncDimension(ElementBase[DimensionAPI]):
956
956
 
957
957
  # -----------------------------------------------------------------------------
958
958
  # has shared member
959
- df_shared: pd.DataFrame = df[df[shared_mbr_col]]
959
+ df_shared: pd.DataFrame = df[df[shared_mbr_col].astype(bool)]
960
960
  if self._strict:
961
961
  existed_mbrs = self._member_memo
962
962
 
@@ -989,7 +989,7 @@ class AsyncDimension(ElementBase[DimensionAPI]):
989
989
  # 在df中丢弃系统中已存在的共享节点
990
990
  df_shared_remain = df_shared[~df_shared.index.isin(dup_sharedmbrs)]
991
991
 
992
- df_not_shared = df[~df[shared_mbr_col]]
992
+ df_not_shared = df[~df[shared_mbr_col].astype(bool)]
993
993
  existed_idx = df_not_shared[DFLT_NAME_COLUMN].isin(existed_mbrs)
994
994
  return df_not_shared.loc[existed_idx], \
995
995
  pd.concat([df_not_shared.loc[~existed_idx], df_shared_remain])
@@ -390,6 +390,7 @@ class AsyncFinancialCube(ElementBase[FinancialModelAPI]):
390
390
  verify_access: bool = False,
391
391
  include_ignored: bool = False,
392
392
  normalize_view: bool = False,
393
+ pivot_members: List[str] = None,
393
394
  ) -> Union[pd.DataFrame, Tuple[pd.DataFrame, Dict[str, str]]]:
394
395
  """
395
396
  根据维度表达式以及pov获取cube数据
@@ -403,6 +404,7 @@ class AsyncFinancialCube(ElementBase[FinancialModelAPI]):
403
404
  verify_access: 是否带权限查询
404
405
  include_ignored: 包含多版本实体维时,是否在结果中包含无效数据(即i列为1的数据)
405
406
  normalize_view: 是否把大小写View统一成"View"
407
+ pivot_members: 如有透视维度,可指定透视成员列表,在透视列成员不存在时补全列
406
408
 
407
409
  .. admonition:: 示例
408
410
 
@@ -432,7 +434,9 @@ class AsyncFinancialCube(ElementBase[FinancialModelAPI]):
432
434
 
433
435
  """
434
436
  pov = self._resolve_pov_as_dict(pov, validate_expr)
435
- expression, full_pov = self._split_expr(expression, pov, validate_expr=validate_expr)
437
+ expression, full_pov = self._split_expr(
438
+ expression, pov, validate_expr=validate_expr
439
+ )
436
440
  pov_expr = dict_to_expr(full_pov)
437
441
 
438
442
  if not expression: # only pov
@@ -469,10 +473,17 @@ class AsyncFinancialCube(ElementBase[FinancialModelAPI]):
469
473
 
470
474
  if pivot_dim is not None:
471
475
  pivot_col = self._get_column_from_dim(pivot_dim)
472
-
476
+ has_mbrs = isinstance(pivot_members, list)
477
+ if has_mbrs and any(not isinstance(c, str) for c in pivot_members):
478
+ raise ValueError(
479
+ f"Pivot members must be a list of string, got: {pivot_members}"
480
+ )
473
481
  if pivot_col in full_pov:
474
482
  val = full_pov.pop(pivot_col)
475
- data = data.rename(columns={DFLT_DATA_COLUMN: val})
483
+ if not has_mbrs or val in pivot_members:
484
+ data = data.rename(columns={DFLT_DATA_COLUMN: val})
485
+ else:
486
+ data = data.drop(columns=[DFLT_DATA_COLUMN])
476
487
  elif pivot_col not in data.columns:
477
488
  raise ValueError(
478
489
  f"Pivot dimension: {pivot_dim} does not "
@@ -488,7 +499,14 @@ class AsyncFinancialCube(ElementBase[FinancialModelAPI]):
488
499
  columns=pivot_col, aggfunc='first', fill_value=None
489
500
  ).reset_index(drop=drop_index)
490
501
  data.columns.name = None
491
-
502
+ if has_mbrs:
503
+ data = data.drop(columns=filter(
504
+ lambda c: c not in pivot_members and c not in index,
505
+ data.columns
506
+ ))
507
+ if has_mbrs:
508
+ mbr_assigns = {m: None for m in pivot_members if m not in data.columns}
509
+ data = data.assign(**mbr_assigns)
492
510
  if not compact:
493
511
  return data.assign(**full_pov)
494
512
  return data, full_pov
@@ -1573,6 +1591,7 @@ class FinancialCube(AsyncFinancialCube, metaclass=SyncMeta):
1573
1591
  verify_access: bool = False,
1574
1592
  include_ignored: bool = False,
1575
1593
  normalize_view: bool = False,
1594
+ pivot_members: List[str] = None,
1576
1595
  ) -> Union[pd.DataFrame, Tuple[pd.DataFrame, Dict[str, str]]]:
1577
1596
  ...
1578
1597
 
@@ -122,8 +122,7 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
122
122
  line_df: pd.DataFrame,
123
123
  callback: Union[Dict, CallbackInfo] = None,
124
124
  relation_field: str = 'journal_id',
125
- id_col: str = None,
126
- operate_type: Literal['EDIT', 'ADD'] = 'ADD',
125
+ id_col: str = MAIN_ID,
127
126
  enable_create: bool = True,
128
127
  enable_default_value: bool = True,
129
128
  enable_repeat_check: bool = True,
@@ -131,9 +130,10 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
131
130
  enable_valid_range: bool = True,
132
131
  enable_all_errors: bool = True,
133
132
  enable_need_one_line: bool = True,
133
+ header_operate: Literal['EDIT', 'ADD', 'DELETE_ADD'] = 'ADD',
134
+ line_operate: Literal['EDIT', 'ADD', 'DELETE', 'DELETE_ADD'] = 'ADD',
134
135
  ):
135
136
  errors = set()
136
- is_editing = operate_type == 'EDIT'
137
137
 
138
138
  if head_df.empty:
139
139
  errors.add('凭证头表数据DataFrame不能为空')
@@ -143,6 +143,7 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
143
143
  errors.add('凭证头行表的关联字段relation_field不能为空')
144
144
 
145
145
  self._maybe_raise_errors(errors)
146
+ is_editing = not (header_operate == 'ADD' and line_operate == 'ADD')
146
147
 
147
148
  if is_editing:
148
149
  head_required_fields = [relation_field, '_type', id_col]
@@ -201,7 +202,7 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
201
202
  index=head_data.index
202
203
  )
203
204
  )
204
- head_data = head_data.assign(operateType=operate_type)
205
+ head_data = head_data.assign(operateType=header_operate)
205
206
 
206
207
  # NB: replace twice in case of infer None to nan happened
207
208
  head_df = head_df.replace({None: np.nan})
@@ -227,7 +228,7 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
227
228
  self._maybe_raise_errors(errors)
228
229
 
229
230
  line_data = line_df[line_required_fields]
230
- line_data = line_data.assign(operateType=operate_type)
231
+ line_data = line_data.assign(operateType=line_operate)
231
232
 
232
233
  # generate lineMainId
233
234
  if is_editing:
@@ -301,7 +302,7 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
301
302
  enable_need_one_line: bool = True,
302
303
  sync: bool = True
303
304
  ) -> CommonResultDTO:
304
- """凭证模型数据保存
305
+ """凭证模型数据新增
305
306
 
306
307
  Args:
307
308
  head_df: 凭证头表的数据(字段名与凭证模型上头表的字段名对应)
@@ -471,6 +472,8 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
471
472
  enable_valid_range: bool = True,
472
473
  enable_all_errors: bool = True,
473
474
  enable_need_one_line: bool = True,
475
+ header_operate: Literal['EDIT', 'DELETE_ADD'] = 'EDIT',
476
+ line_operate: Literal['EDIT', 'ADD', 'DELETE', 'DELETE_ADD'] = 'EDIT',
474
477
  ) -> CommonResultDTO:
475
478
  """凭证模型数据更新
476
479
 
@@ -491,6 +494,10 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
491
494
  enable_valid_range: 是否启用有效性范围的校验,默认为True
492
495
  enable_all_errors: 是否启用一次性校验所有规则和数据,默认为True
493
496
  enable_need_one_line: 是否启用凭证行表至少需要一条数据的校验,默认为True
497
+ header_operate: 凭证头表的操作类型,默认为EDIT
498
+ line_operate: 凭证行表的操作类型,默认为EDIT
499
+ 头表EDIT, 行表可以ADD/EDIT/DELETE
500
+ 头表DELETE_ADD, 头表update,行表全删全增,行表只能为DELETE_ADD
494
501
 
495
502
  Returns:
496
503
  接口返回信息(CommonResultDTO的success为true 表示成功,如false 则错误在errors集合里)
@@ -500,14 +507,16 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
500
507
  batch = self._gen_batch_payload(
501
508
  head_df=head_df, line_df=line_df,
502
509
  callback=callback, relation_field=relation_field,
503
- id_col=MAIN_ID, operate_type='EDIT',
510
+ id_col=MAIN_ID,
504
511
  enable_create=enable_create,
505
512
  enable_default_value=enable_default_value,
506
513
  enable_repeat_check=enable_repeat_check,
507
514
  enable_required=enable_required,
508
515
  enable_valid_range=enable_valid_range,
509
516
  enable_all_errors=enable_all_errors,
510
- enable_need_one_line=enable_need_one_line
517
+ enable_need_one_line=enable_need_one_line,
518
+ header_operate=header_operate,
519
+ line_operate=line_operate,
511
520
  )
512
521
  resp = await self.async_api.journal_model_data.update(batch)
513
522
 
@@ -814,6 +823,8 @@ class JournalModel(AsyncJournalModel, metaclass=SyncMeta):
814
823
  enable_valid_range: bool = True,
815
824
  enable_all_errors: bool = True,
816
825
  enable_need_one_line: bool = True,
826
+ header_operate: Literal['EDIT', 'DELETE_ADD'] = 'EDIT',
827
+ line_operate: Literal['EDIT', 'ADD', 'DELETE', 'DELETE_ADD'] = 'EDIT',
817
828
  ) -> CommonResultDTO:
818
829
  ...
819
830
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: deepfos
3
- Version: 1.1.65
3
+ Version: 1.1.67
4
4
  Summary: Collecions of useful and handy tools for deepfos platform
5
5
  Home-page: http://py.deepfos.com
6
6
  Author: deepfos-python-team
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes