dsh-crwu-workbench 0.0.10

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 (479) hide show
  1. package/CHANGELOG.md +1516 -0
  2. package/LICENSE +26 -0
  3. package/README.en.md +204 -0
  4. package/README.md +521 -0
  5. package/SECURITY.md +39 -0
  6. package/bin/darwin-arm64/crwu +0 -0
  7. package/bin/darwin-arm64/dws +0 -0
  8. package/bin/darwin-arm64/ossutil +0 -0
  9. package/bin/manifest.json +82 -0
  10. package/bin/win32-x64/crwu.exe +0 -0
  11. package/bin/win32-x64/dws.exe +4 -0
  12. package/bin/win32-x64/ossutil.exe +0 -0
  13. package/common/skills/.synced.json +8 -0
  14. package/common/skills/crwu-dws/SKILL.md +208 -0
  15. package/common/skills/crwu-dws/references/00-/347/233/256/345/275/225/345/277/253/347/205/247schema.md +113 -0
  16. package/common/skills/crwu-dws/references/01-/345/256/241/346/240/270/344/270/213/350/275/275/344/270/216manifest/350/247/204/350/214/203.md +115 -0
  17. package/common/skills/crwu-dws/references/02-/347/274/223/345/255/230/344/270/216/345/205/234/345/272/225/346/237/245/346/211/276/350/247/204/350/214/203.md +119 -0
  18. package/common/skills/crwu-dws/scripts/test_dws_source_contract.py +493 -0
  19. package/common/skills/crwu-h3yun-login/SKILL.md +130 -0
  20. package/common/skills/crwu-h3yun-query/SKILL.md +192 -0
  21. package/config/crwu-workbench.yml +26 -0
  22. package/cordis.patch.yml +46 -0
  23. package/lib/client.js +10346 -0
  24. package/lib/index.js +11584 -0
  25. package/package.json +119 -0
  26. package/scripts/prepare.mjs +59 -0
  27. package/skills/README.md +26 -0
  28. package/skills/crwu/crwu-audit/SKILL.md +152 -0
  29. package/skills/crwu/crwu-audit/references/00-input-and-route-profile.md +232 -0
  30. package/skills/crwu/crwu-audit/references/01-report-id-resolution.md +13 -0
  31. package/skills/crwu/crwu-audit/references/02-scope-classification.md +25 -0
  32. package/skills/crwu/crwu-audit/references/03-asset-classification.md +27 -0
  33. package/skills/crwu/crwu-audit/references/04-business-classification.md +63 -0
  34. package/skills/crwu/crwu-audit/references/05-method-classification.md +66 -0
  35. package/skills/crwu/crwu-audit/references/06-overlay-classification.md +42 -0
  36. package/skills/crwu/crwu-audit/references/07-skill-registry.md +57 -0
  37. package/skills/crwu/crwu-audit/references/08-union-dispatch-rules.md +142 -0
  38. package/skills/crwu/crwu-audit/references/09-review-risk-classification.md +85 -0
  39. package/skills/crwu/crwu-audit/references/10-capability-gap-proposal.md +68 -0
  40. package/skills/crwu/crwu-audit/references/11-html-delivery-spec.md +881 -0
  41. package/skills/crwu/crwu-audit/references/12-leaf-common-contract.md +202 -0
  42. package/skills/crwu/crwu-audit/references/13-dingtalk-result-publish.md +88 -0
  43. package/skills/crwu/crwu-audit/references/14-orchestration-workflow.md +52 -0
  44. package/skills/crwu/crwu-audit/references/99-maintenance.md +62 -0
  45. package/skills/crwu/crwu-audit/scripts/README.md +157 -0
  46. package/skills/crwu/crwu-audit/scripts/audit_delivery.py +2736 -0
  47. package/skills/crwu/crwu-audit/scripts/audit_result.schema.json +1659 -0
  48. package/skills/crwu/crwu-audit/scripts/examples/audit-result.sample.json +739 -0
  49. package/skills/crwu/crwu-audit/scripts/fetch_review_records.py +85 -0
  50. package/skills/crwu/crwu-audit/scripts/media_extract.py +513 -0
  51. package/skills/crwu/crwu-audit/scripts/prepare_materials.py +1252 -0
  52. package/skills/crwu/crwu-audit/scripts/test_audit_delivery.py +1678 -0
  53. package/skills/crwu/crwu-audit/scripts/test_audit_multiaxis_router.py +1339 -0
  54. package/skills/crwu/crwu-audit/scripts/test_fetch_review_records.py +77 -0
  55. package/skills/crwu/crwu-audit/scripts/test_media_extract.py +332 -0
  56. package/skills/crwu/crwu-audit/scripts/test_prepare_materials.py +1081 -0
  57. package/skills/crwu/crwu-audit/template/audit-report.html +435 -0
  58. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/SKILL.md +38 -0
  59. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/00-applicability.md +28 -0
  60. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/01-kb-assembly.md +31 -0
  61. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/02-review-focus.md +21 -0
  62. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/03-common-contract.md +202 -0
  63. package/skills/crwu/crwu-audit-asset-debt/SKILL.md +38 -0
  64. package/skills/crwu/crwu-audit-asset-debt/references/00-applicability.md +40 -0
  65. package/skills/crwu/crwu-audit-asset-debt/references/01-kb-assembly.md +41 -0
  66. package/skills/crwu/crwu-audit-asset-debt/references/02-review-focus.md +54 -0
  67. package/skills/crwu/crwu-audit-asset-debt/references/03-common-contract.md +202 -0
  68. package/skills/crwu/crwu-audit-asset-enterprise-value/SKILL.md +38 -0
  69. package/skills/crwu/crwu-audit-asset-enterprise-value/references/00-applicability.md +31 -0
  70. package/skills/crwu/crwu-audit-asset-enterprise-value/references/01-kb-assembly.md +35 -0
  71. package/skills/crwu/crwu-audit-asset-enterprise-value/references/02-review-focus.md +33 -0
  72. package/skills/crwu/crwu-audit-asset-enterprise-value/references/03-common-contract.md +202 -0
  73. package/skills/crwu/crwu-audit-asset-equipment/SKILL.md +38 -0
  74. package/skills/crwu/crwu-audit-asset-equipment/references/00-applicability.md +40 -0
  75. package/skills/crwu/crwu-audit-asset-equipment/references/01-kb-assembly.md +39 -0
  76. package/skills/crwu/crwu-audit-asset-equipment/references/02-review-focus.md +52 -0
  77. package/skills/crwu/crwu-audit-asset-equipment/references/03-common-contract.md +202 -0
  78. package/skills/crwu/crwu-audit-asset-intangible/SKILL.md +38 -0
  79. package/skills/crwu/crwu-audit-asset-intangible/references/00-applicability.md +36 -0
  80. package/skills/crwu/crwu-audit-asset-intangible/references/01-kb-assembly.md +40 -0
  81. package/skills/crwu/crwu-audit-asset-intangible/references/02-review-focus.md +68 -0
  82. package/skills/crwu/crwu-audit-asset-intangible/references/03-common-contract.md +202 -0
  83. package/skills/crwu/crwu-audit-asset-inventory/SKILL.md +38 -0
  84. package/skills/crwu/crwu-audit-asset-inventory/references/00-applicability.md +28 -0
  85. package/skills/crwu/crwu-audit-asset-inventory/references/01-kb-assembly.md +31 -0
  86. package/skills/crwu/crwu-audit-asset-inventory/references/02-review-focus.md +21 -0
  87. package/skills/crwu/crwu-audit-asset-inventory/references/03-common-contract.md +202 -0
  88. package/skills/crwu/crwu-audit-asset-mining-right/SKILL.md +38 -0
  89. package/skills/crwu/crwu-audit-asset-mining-right/references/00-applicability.md +31 -0
  90. package/skills/crwu/crwu-audit-asset-mining-right/references/01-kb-assembly.md +35 -0
  91. package/skills/crwu/crwu-audit-asset-mining-right/references/02-review-focus.md +33 -0
  92. package/skills/crwu/crwu-audit-asset-mining-right/references/03-common-contract.md +202 -0
  93. package/skills/crwu/crwu-audit-asset-other/SKILL.md +38 -0
  94. package/skills/crwu/crwu-audit-asset-other/references/00-applicability.md +28 -0
  95. package/skills/crwu/crwu-audit-asset-other/references/01-kb-assembly.md +31 -0
  96. package/skills/crwu/crwu-audit-asset-other/references/02-review-focus.md +21 -0
  97. package/skills/crwu/crwu-audit-asset-other/references/03-common-contract.md +202 -0
  98. package/skills/crwu/crwu-audit-asset-portfolio/SKILL.md +38 -0
  99. package/skills/crwu/crwu-audit-asset-portfolio/references/00-applicability.md +28 -0
  100. package/skills/crwu/crwu-audit-asset-portfolio/references/01-kb-assembly.md +31 -0
  101. package/skills/crwu/crwu-audit-asset-portfolio/references/02-review-focus.md +21 -0
  102. package/skills/crwu/crwu-audit-asset-portfolio/references/03-common-contract.md +202 -0
  103. package/skills/crwu/crwu-audit-asset-realestate/SKILL.md +38 -0
  104. package/skills/crwu/crwu-audit-asset-realestate/references/00-applicability.md +43 -0
  105. package/skills/crwu/crwu-audit-asset-realestate/references/01-kb-assembly.md +75 -0
  106. package/skills/crwu/crwu-audit-asset-realestate/references/02-review-focus.md +93 -0
  107. package/skills/crwu/crwu-audit-asset-realestate/references/03-common-contract.md +202 -0
  108. package/skills/crwu/crwu-audit-asset-scrap-materials/SKILL.md +38 -0
  109. package/skills/crwu/crwu-audit-asset-scrap-materials/references/00-applicability.md +28 -0
  110. package/skills/crwu/crwu-audit-asset-scrap-materials/references/01-kb-assembly.md +31 -0
  111. package/skills/crwu/crwu-audit-asset-scrap-materials/references/02-review-focus.md +21 -0
  112. package/skills/crwu/crwu-audit-asset-scrap-materials/references/03-common-contract.md +202 -0
  113. package/skills/crwu/crwu-audit-asset-transport-equipment/SKILL.md +38 -0
  114. package/skills/crwu/crwu-audit-asset-transport-equipment/references/00-applicability.md +31 -0
  115. package/skills/crwu/crwu-audit-asset-transport-equipment/references/01-kb-assembly.md +35 -0
  116. package/skills/crwu/crwu-audit-asset-transport-equipment/references/02-review-focus.md +33 -0
  117. package/skills/crwu/crwu-audit-asset-transport-equipment/references/03-common-contract.md +202 -0
  118. package/skills/crwu/crwu-audit-biz-asset-operation/SKILL.md +38 -0
  119. package/skills/crwu/crwu-audit-biz-asset-operation/references/00-applicability.md +30 -0
  120. package/skills/crwu/crwu-audit-biz-asset-operation/references/01-kb-assembly.md +32 -0
  121. package/skills/crwu/crwu-audit-biz-asset-operation/references/02-review-focus.md +42 -0
  122. package/skills/crwu/crwu-audit-biz-asset-operation/references/03-common-contract.md +202 -0
  123. package/skills/crwu/crwu-audit-biz-consulting-review/SKILL.md +38 -0
  124. package/skills/crwu/crwu-audit-biz-consulting-review/references/00-applicability.md +30 -0
  125. package/skills/crwu/crwu-audit-biz-consulting-review/references/01-kb-assembly.md +32 -0
  126. package/skills/crwu/crwu-audit-biz-consulting-review/references/02-review-focus.md +40 -0
  127. package/skills/crwu/crwu-audit-biz-consulting-review/references/03-common-contract.md +202 -0
  128. package/skills/crwu/crwu-audit-biz-financial-reporting/SKILL.md +38 -0
  129. package/skills/crwu/crwu-audit-biz-financial-reporting/references/00-applicability.md +41 -0
  130. package/skills/crwu/crwu-audit-biz-financial-reporting/references/01-kb-assembly.md +40 -0
  131. package/skills/crwu/crwu-audit-biz-financial-reporting/references/02-review-focus.md +64 -0
  132. package/skills/crwu/crwu-audit-biz-financial-reporting/references/03-common-contract.md +202 -0
  133. package/skills/crwu/crwu-audit-biz-financing-debt/SKILL.md +38 -0
  134. package/skills/crwu/crwu-audit-biz-financing-debt/references/00-applicability.md +31 -0
  135. package/skills/crwu/crwu-audit-biz-financing-debt/references/01-kb-assembly.md +34 -0
  136. package/skills/crwu/crwu-audit-biz-financing-debt/references/02-review-focus.md +47 -0
  137. package/skills/crwu/crwu-audit-biz-financing-debt/references/03-common-contract.md +202 -0
  138. package/skills/crwu/crwu-audit-biz-investment-capital/SKILL.md +38 -0
  139. package/skills/crwu/crwu-audit-biz-investment-capital/references/00-applicability.md +31 -0
  140. package/skills/crwu/crwu-audit-biz-investment-capital/references/01-kb-assembly.md +34 -0
  141. package/skills/crwu/crwu-audit-biz-investment-capital/references/02-review-focus.md +47 -0
  142. package/skills/crwu/crwu-audit-biz-investment-capital/references/03-common-contract.md +202 -0
  143. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/SKILL.md +38 -0
  144. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/00-applicability.md +30 -0
  145. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/01-kb-assembly.md +32 -0
  146. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/02-review-focus.md +40 -0
  147. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/03-common-contract.md +202 -0
  148. package/skills/crwu/crwu-audit-biz-tax-history/SKILL.md +38 -0
  149. package/skills/crwu/crwu-audit-biz-tax-history/references/00-applicability.md +29 -0
  150. package/skills/crwu/crwu-audit-biz-tax-history/references/01-kb-assembly.md +30 -0
  151. package/skills/crwu/crwu-audit-biz-tax-history/references/02-review-focus.md +33 -0
  152. package/skills/crwu/crwu-audit-biz-tax-history/references/03-common-contract.md +202 -0
  153. package/skills/crwu/crwu-audit-biz-transaction-disposal/SKILL.md +38 -0
  154. package/skills/crwu/crwu-audit-biz-transaction-disposal/references/00-applicability.md +31 -0
  155. package/skills/crwu/crwu-audit-biz-transaction-disposal/references/01-kb-assembly.md +34 -0
  156. package/skills/crwu/crwu-audit-biz-transaction-disposal/references/02-review-focus.md +47 -0
  157. package/skills/crwu/crwu-audit-biz-transaction-disposal/references/03-common-contract.md +202 -0
  158. package/skills/crwu/crwu-audit-datacheck/SKILL.md +59 -0
  159. package/skills/crwu/crwu-audit-datacheck/references/00-KB/350/243/205/351/205/215/350/241/250.md +25 -0
  160. package/skills/crwu/crwu-audit-datacheck/references/01-runtime-and-check-catalog.md +139 -0
  161. package/skills/crwu/crwu-audit-datacheck/scripts/recalc_check.py +521 -0
  162. package/skills/crwu/crwu-audit-datacheck/scripts/test_recalc_check.py +419 -0
  163. package/skills/crwu/crwu-audit-datacheck/scripts/test_skill_self_containment.py +44 -0
  164. package/skills/crwu/crwu-audit-external-data/SKILL.md +73 -0
  165. package/skills/crwu/crwu-audit-external-data/references/00-KB/350/243/205/351/205/215/350/241/250.md +38 -0
  166. package/skills/crwu/crwu-audit-external-data/references/01-connector-access.md +116 -0
  167. package/skills/crwu/crwu-audit-external-data/references/02-verification-workflow.md +78 -0
  168. package/skills/crwu/crwu-audit-external-data/scripts/connector_probe.py +374 -0
  169. package/skills/crwu/crwu-audit-external-data/scripts/test_connector_probe.py +264 -0
  170. package/skills/crwu/crwu-audit-output-filter/SKILL.md +34 -0
  171. package/skills/crwu/crwu-audit-output-filter/references/00-applicability.md +7 -0
  172. package/skills/crwu/crwu-audit-output-filter/references/01-kb-assembly.md +11 -0
  173. package/skills/crwu/crwu-audit-output-filter/references/02-review-focus.md +25 -0
  174. package/skills/crwu/crwu-audit-public-general-standards/SKILL.md +46 -0
  175. package/skills/crwu/crwu-audit-public-general-standards/references/00-applicability.md +41 -0
  176. package/skills/crwu/crwu-audit-public-general-standards/references/01-kb-assembly.md +66 -0
  177. package/skills/crwu/crwu-audit-public-general-standards/references/02-review-focus.md +111 -0
  178. package/skills/crwu/crwu-audit-public-general-standards/references/03-execution-contract.md +89 -0
  179. package/skills/crwu/crwu-dev-audit-optimize/SKILL.md +98 -0
  180. package/skills/crwu/crwu-dev-audit-optimize/references/00-/344/274/230/345/214/226/350/247/204/350/214/203/344/270/216/346/226/207/344/273/266/350/220/275/347/202/271.md +132 -0
  181. package/skills/crwu/crwu-dev-audit-optimize/references/01-/345/217/215/351/246/210/345/256/232/344/275/215/344/270/216/347/224/273/345/203/217/346/265/201/347/250/213.md +108 -0
  182. package/skills/crwu/crwu-dev-audit-optimize/references/02-/346/226/271/346/241/210/346/250/241/346/235/277/344/270/216/347/241/256/350/256/244/351/227/250/347/246/201.md +92 -0
  183. package/skills/crwu/crwu-dev-audit-optimize/references/03-AI/344/272/272/345/267/245/345/267/256/350/267/235/345/210/206/346/236/220/346/265/201/347/250/213.md +95 -0
  184. package/skills/crwu/crwu-dev-audit-optimize/scripts/examples/gap-analysis.sample.json +116 -0
  185. package/skills/crwu/crwu-dev-audit-optimize/scripts/gap_analysis.schema.json +33 -0
  186. package/skills/crwu/crwu-dev-audit-optimize/scripts/gap_analysis_delivery.py +475 -0
  187. package/skills/crwu/crwu-dev-audit-optimize/scripts/test_gap_analysis_delivery.py +855 -0
  188. package/skills/crwu/crwu-dev-audit-optimize/template/NOTICE.echarts.txt +7 -0
  189. package/skills/crwu/crwu-dev-audit-optimize/template/echarts.min.js +45 -0
  190. package/skills/crwu/crwu-dev-audit-optimize/template/gap-analysis-report.html +71 -0
  191. package/skills/crwu/crwu-dev-audit-skill-maintainer/SKILL.md +74 -0
  192. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/00-responsibility-and-modes.md +62 -0
  193. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/01-kb-source-discovery.md +89 -0
  194. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/02-child-skill-contract.md +99 -0
  195. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/03-review-item-execution-contract.md +51 -0
  196. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/04-registry-and-mapping-update.md +73 -0
  197. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/05-validation-and-delivery.md +127 -0
  198. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/06-live-routing-reconciliation.md +136 -0
  199. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/07-kb-skill-map.md +411 -0
  200. package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/README.md +200 -0
  201. package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/check_audit_skill_mappings.py +2477 -0
  202. package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/kb_tool.py +577 -0
  203. package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/test_audit_skill_maintainer.py +5097 -0
  204. package/skills/dws/LICENSE +178 -0
  205. package/skills/dws/NOTICE +17 -0
  206. package/skills/dws/dingtalk-aisearch/SKILL.md +83 -0
  207. package/skills/dws/dingtalk-aisearch/references/aisearch.md +249 -0
  208. package/skills/dws/dingtalk-aisearch/references/intent-guide.md +15 -0
  209. package/skills/dws/dingtalk-aisearch/references/lite-recipes.md +31 -0
  210. package/skills/dws/dingtalk-aitable/SKILL.md +139 -0
  211. package/skills/dws/dingtalk-aitable/references/06-data-analytics.md +11 -0
  212. package/skills/dws/dingtalk-aitable/references/aitable/aitable-advperm.md +251 -0
  213. package/skills/dws/dingtalk-aitable/references/aitable/aitable-attachment.md +49 -0
  214. package/skills/dws/dingtalk-aitable/references/aitable/aitable-best-practices.md +48 -0
  215. package/skills/dws/dingtalk-aitable/references/aitable/aitable-cell-value.md +346 -0
  216. package/skills/dws/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md +46 -0
  217. package/skills/dws/dingtalk-aitable/references/aitable/aitable-data-analysis-sop.md +131 -0
  218. package/skills/dws/dingtalk-aitable/references/aitable/aitable-datasource.md +272 -0
  219. package/skills/dws/dingtalk-aitable/references/aitable/aitable-error-recovery.md +128 -0
  220. package/skills/dws/dingtalk-aitable/references/aitable/aitable-export-import.md +113 -0
  221. package/skills/dws/dingtalk-aitable/references/aitable/aitable-field-properties.md +238 -0
  222. package/skills/dws/dingtalk-aitable/references/aitable/aitable-field.md +186 -0
  223. package/skills/dws/dingtalk-aitable/references/aitable/aitable-filter-sort.md +135 -0
  224. package/skills/dws/dingtalk-aitable/references/aitable/aitable-form.md +130 -0
  225. package/skills/dws/dingtalk-aitable/references/aitable/aitable-formula-guide.md +224 -0
  226. package/skills/dws/dingtalk-aitable/references/aitable/aitable-primary-doc.md +58 -0
  227. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-create.md +52 -0
  228. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-delete.md +20 -0
  229. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-history.md +97 -0
  230. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-name-key.md +47 -0
  231. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-query.md +120 -0
  232. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-share.md +54 -0
  233. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-stats.md +59 -0
  234. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-update.md +66 -0
  235. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-upsert.md +89 -0
  236. package/skills/dws/dingtalk-aitable/references/aitable/aitable-view-config.md +140 -0
  237. package/skills/dws/dingtalk-aitable/references/aitable/aitable-view-extras.md +195 -0
  238. package/skills/dws/dingtalk-aitable/references/aitable/aitable-view-types.md +95 -0
  239. package/skills/dws/dingtalk-aitable/references/aitable/aitable-workflow.md +384 -0
  240. package/skills/dws/dingtalk-aitable/references/aitable-record-ops.md +80 -0
  241. package/skills/dws/dingtalk-aitable/references/aitable-section.md +30 -0
  242. package/skills/dws/dingtalk-aitable/references/aitable.md +59 -0
  243. package/skills/dws/dingtalk-aitable/references/field-rules.md +17 -0
  244. package/skills/dws/dingtalk-aitable/references/intent-guide.md +17 -0
  245. package/skills/dws/dingtalk-aitable/references/recipes/conventions.md +44 -0
  246. package/skills/dws/dingtalk-aitable/references/url-patterns.md +144 -0
  247. package/skills/dws/dingtalk-aitable/scripts/aitable_export_via_task.py +210 -0
  248. package/skills/dws/dingtalk-aitable/scripts/aitable_import_via_task.py +173 -0
  249. package/skills/dws/dingtalk-aitable/scripts/bulk_add_fields.py +273 -0
  250. package/skills/dws/dingtalk-aitable/scripts/import_records.py +333 -0
  251. package/skills/dws/dingtalk-aitable/scripts/upload_attachment.py +190 -0
  252. package/skills/dws/dingtalk-calendar/SKILL.md +137 -0
  253. package/skills/dws/dingtalk-calendar/references/03-meeting.md +56 -0
  254. package/skills/dws/dingtalk-calendar/references/calendar.md +760 -0
  255. package/skills/dws/dingtalk-calendar/references/intent-guide.md +9 -0
  256. package/skills/dws/dingtalk-calendar/references/lite-recipes.md +25 -0
  257. package/skills/dws/dingtalk-calendar/scripts/calendar_free_slot_finder.py +199 -0
  258. package/skills/dws/dingtalk-calendar/scripts/calendar_schedule_meeting.py +233 -0
  259. package/skills/dws/dingtalk-calendar/scripts/calendar_today_agenda.py +139 -0
  260. package/skills/dws/dingtalk-chat/SKILL.md +127 -0
  261. package/skills/dws/dingtalk-chat/references/01-messaging.md +103 -0
  262. package/skills/dws/dingtalk-chat/references/card/callback.md +10 -0
  263. package/skills/dws/dingtalk-chat/references/card/create.md +27 -0
  264. package/skills/dws/dingtalk-chat/references/card/schema.md +14 -0
  265. package/skills/dws/dingtalk-chat/references/card/update.md +13 -0
  266. package/skills/dws/dingtalk-chat/references/chat/chat-bot.md +187 -0
  267. package/skills/dws/dingtalk-chat/references/chat/chat-conversation.md +165 -0
  268. package/skills/dws/dingtalk-chat/references/chat/group-admin.md +144 -0
  269. package/skills/dws/dingtalk-chat/references/chat/group-discovery.md +121 -0
  270. package/skills/dws/dingtalk-chat/references/chat/message-actions.md +132 -0
  271. package/skills/dws/dingtalk-chat/references/chat/message-media.md +76 -0
  272. package/skills/dws/dingtalk-chat/references/chat/message-query.md +135 -0
  273. package/skills/dws/dingtalk-chat/references/chat/thread.md +46 -0
  274. package/skills/dws/dingtalk-chat/references/chat-emoji-list.md +216 -0
  275. package/skills/dws/dingtalk-chat/references/chat.md +140 -0
  276. package/skills/dws/dingtalk-chat/references/contracts.md +70 -0
  277. package/skills/dws/dingtalk-chat/references/intent-guide.md +56 -0
  278. package/skills/dws/dingtalk-contact/SKILL.md +122 -0
  279. package/skills/dws/dingtalk-contact/references/08-directory.md +51 -0
  280. package/skills/dws/dingtalk-contact/references/contact.md +595 -0
  281. package/skills/dws/dingtalk-contact/references/intent-guide.md +14 -0
  282. package/skills/dws/dingtalk-contact/references/lite-recipes.md +30 -0
  283. package/skills/dws/dingtalk-contact/references/recipes/conventions.md +44 -0
  284. package/skills/dws/dingtalk-contact/scripts/contact_dept_members.py +130 -0
  285. package/skills/dws/dingtalk-doc/SKILL.md +112 -0
  286. package/skills/dws/dingtalk-doc/references/04-document.md +57 -0
  287. package/skills/dws/dingtalk-doc/references/contracts.md +40 -0
  288. package/skills/dws/dingtalk-doc/references/doc/doc-block.md +39 -0
  289. package/skills/dws/dingtalk-doc/references/doc/doc-comment.md +29 -0
  290. package/skills/dws/dingtalk-doc/references/doc/doc-create.md +54 -0
  291. package/skills/dws/dingtalk-doc/references/doc/doc-export.md +30 -0
  292. package/skills/dws/dingtalk-doc/references/doc/doc-import.md +28 -0
  293. package/skills/dws/dingtalk-doc/references/doc/doc-info.md +12 -0
  294. package/skills/dws/dingtalk-doc/references/doc/doc-media.md +46 -0
  295. package/skills/dws/dingtalk-doc/references/doc/doc-read.md +56 -0
  296. package/skills/dws/dingtalk-doc/references/doc/doc-update.md +87 -0
  297. package/skills/dws/dingtalk-doc/references/doc/format/doc-jsonml-cookbook.md +469 -0
  298. package/skills/dws/dingtalk-doc/references/doc/format/doc-jsonml-schema.json +526 -0
  299. package/skills/dws/dingtalk-doc/references/doc/format/doc-jsonml-schema.md +711 -0
  300. package/skills/dws/dingtalk-doc/references/doc/style/doc-create-workflow.md +414 -0
  301. package/skills/dws/dingtalk-doc/references/doc/style/doc-style-guideline.md +404 -0
  302. package/skills/dws/dingtalk-doc/references/doc/style/doc-update-workflow.md +316 -0
  303. package/skills/dws/dingtalk-doc/references/doc.md +82 -0
  304. package/skills/dws/dingtalk-doc/references/intent-guide.md +20 -0
  305. package/skills/dws/dingtalk-drive/SKILL.md +128 -0
  306. package/skills/dws/dingtalk-drive/references/drive.md +126 -0
  307. package/skills/dws/dingtalk-drive/references/folder-sync.md +40 -0
  308. package/skills/dws/dingtalk-drive/references/intent-guide.md +22 -0
  309. package/skills/dws/dingtalk-drive/references/lite-recipes.md +39 -0
  310. package/skills/dws/dingtalk-event/SKILL.md +106 -0
  311. package/skills/dws/dingtalk-event/references/event-im-keys.md +54 -0
  312. package/skills/dws/dingtalk-event/references/event-im-lifecycle.md +35 -0
  313. package/skills/dws/dingtalk-event/references/event-im-operations.md +52 -0
  314. package/skills/dws/dingtalk-event/references/event-im-output.md +39 -0
  315. package/skills/dws/dingtalk-event/references/event-im.md +58 -0
  316. package/skills/dws/dingtalk-event/references/event-oa.md +155 -0
  317. package/skills/dws/dingtalk-event/references/event-todo.md +50 -0
  318. package/skills/dws/dingtalk-event/references/event-voip.md +69 -0
  319. package/skills/dws/dingtalk-mail/SKILL.md +113 -0
  320. package/skills/dws/dingtalk-mail/references/09-mail.md +45 -0
  321. package/skills/dws/dingtalk-mail/references/intent-guide.md +8 -0
  322. package/skills/dws/dingtalk-mail/references/lite-recipes.md +45 -0
  323. package/skills/dws/dingtalk-mail/references/mail.md +1889 -0
  324. package/skills/dws/dingtalk-mail/references/recipes/conventions.md +44 -0
  325. package/skills/dws/dingtalk-mail/scripts/mail_send_with_cc.py +122 -0
  326. package/skills/dws/dingtalk-mail/scripts/mail_unread_summary.py +114 -0
  327. package/skills/dws/dingtalk-minutes/SKILL.md +116 -0
  328. package/skills/dws/dingtalk-minutes/references/07-minutes.md +149 -0
  329. package/skills/dws/dingtalk-minutes/references/10-minutes-speaker-match.md +32 -0
  330. package/skills/dws/dingtalk-minutes/references/11-minutes-speaker-correct.md +40 -0
  331. package/skills/dws/dingtalk-minutes/references/intent-guide.md +79 -0
  332. package/skills/dws/dingtalk-minutes/references/lite-recipes.md +25 -0
  333. package/skills/dws/dingtalk-minutes/references/minutes.md +210 -0
  334. package/skills/dws/dingtalk-minutes/references/recipes/conventions.md +33 -0
  335. package/skills/dws/dingtalk-minutes/scripts/minutes_recent_summary.py +196 -0
  336. package/skills/dws/dingtalk-minutes/scripts/test_minutes_scripts.py +222 -0
  337. package/skills/dws/dingtalk-misc/SKILL.md +55 -0
  338. package/skills/dws/dingtalk-misc/references/05-reporting.md +29 -0
  339. package/skills/dws/dingtalk-misc/references/agoal.md +302 -0
  340. package/skills/dws/dingtalk-misc/references/attendance-intent-guide.md +7 -0
  341. package/skills/dws/dingtalk-misc/references/attendance-report.md +109 -0
  342. package/skills/dws/dingtalk-misc/references/attendance-schedule.md +115 -0
  343. package/skills/dws/dingtalk-misc/references/attendance-vacation.md +129 -0
  344. package/skills/dws/dingtalk-misc/references/attendance.md +149 -0
  345. package/skills/dws/dingtalk-misc/references/dev/app.md +42 -0
  346. package/skills/dws/dingtalk-misc/references/dev/connect.md +108 -0
  347. package/skills/dws/dingtalk-misc/references/dev/credentials.md +26 -0
  348. package/skills/dws/dingtalk-misc/references/dev/dev-index.md +13 -0
  349. package/skills/dws/dingtalk-misc/references/dev/event.md +47 -0
  350. package/skills/dws/dingtalk-misc/references/dev/mcp.md +106 -0
  351. package/skills/dws/dingtalk-misc/references/dev/member.md +19 -0
  352. package/skills/dws/dingtalk-misc/references/dev/permission.md +56 -0
  353. package/skills/dws/dingtalk-misc/references/dev/recipes.md +58 -0
  354. package/skills/dws/dingtalk-misc/references/dev/robot.md +74 -0
  355. package/skills/dws/dingtalk-misc/references/dev/security.md +21 -0
  356. package/skills/dws/dingtalk-misc/references/dev/version.md +99 -0
  357. package/skills/dws/dingtalk-misc/references/dev/webapp.md +22 -0
  358. package/skills/dws/dingtalk-misc/references/devapp.md +148 -0
  359. package/skills/dws/dingtalk-misc/references/devdoc-intent-guide.md +7 -0
  360. package/skills/dws/dingtalk-misc/references/devdoc.md +42 -0
  361. package/skills/dws/dingtalk-misc/references/ding.md +184 -0
  362. package/skills/dws/dingtalk-misc/references/hrbrain.md +67 -0
  363. package/skills/dws/dingtalk-misc/references/live.md +23 -0
  364. package/skills/dws/dingtalk-misc/references/markdown.md +167 -0
  365. package/skills/dws/dingtalk-misc/references/oa/oa-form-components.md +345 -0
  366. package/skills/dws/dingtalk-misc/references/oa/oa-process-nodes.md +374 -0
  367. package/skills/dws/dingtalk-misc/references/oa.md +1014 -0
  368. package/skills/dws/dingtalk-misc/references/openapi-explorer.md +47 -0
  369. package/skills/dws/dingtalk-misc/references/pat.md +52 -0
  370. package/skills/dws/dingtalk-misc/references/profile.md +41 -0
  371. package/skills/dws/dingtalk-misc/references/recruit.md +89 -0
  372. package/skills/dws/dingtalk-misc/references/report-conventions.md +44 -0
  373. package/skills/dws/dingtalk-misc/references/report-intent-guide.md +11 -0
  374. package/skills/dws/dingtalk-misc/references/report-lite-recipes.md +25 -0
  375. package/skills/dws/dingtalk-misc/references/report.md +109 -0
  376. package/skills/dws/dingtalk-misc/references/sheet/sheet-batch-operations.md +104 -0
  377. package/skills/dws/dingtalk-misc/references/sheet/sheet-chart.md +69 -0
  378. package/skills/dws/dingtalk-misc/references/sheet/sheet-comment.md +166 -0
  379. package/skills/dws/dingtalk-misc/references/sheet/sheet-conditional-format.md +59 -0
  380. package/skills/dws/dingtalk-misc/references/sheet/sheet-dimension-operations.md +60 -0
  381. package/skills/dws/dingtalk-misc/references/sheet/sheet-dropdown.md +103 -0
  382. package/skills/dws/dingtalk-misc/references/sheet/sheet-export.md +135 -0
  383. package/skills/dws/dingtalk-misc/references/sheet/sheet-filter-view.md +51 -0
  384. package/skills/dws/dingtalk-misc/references/sheet/sheet-filter.md +181 -0
  385. package/skills/dws/dingtalk-misc/references/sheet/sheet-formula.md +192 -0
  386. package/skills/dws/dingtalk-misc/references/sheet/sheet-import.md +76 -0
  387. package/skills/dws/dingtalk-misc/references/sheet/sheet-media-image.md +263 -0
  388. package/skills/dws/dingtalk-misc/references/sheet/sheet-pivot-table.md +292 -0
  389. package/skills/dws/dingtalk-misc/references/sheet/sheet-range-operations.md +161 -0
  390. package/skills/dws/dingtalk-misc/references/sheet/sheet-read-data.md +60 -0
  391. package/skills/dws/dingtalk-misc/references/sheet/sheet-revision-changeset.md +46 -0
  392. package/skills/dws/dingtalk-misc/references/sheet/sheet-search-replace.md +120 -0
  393. package/skills/dws/dingtalk-misc/references/sheet/sheet-style-format.md +71 -0
  394. package/skills/dws/dingtalk-misc/references/sheet/sheet-version.md +84 -0
  395. package/skills/dws/dingtalk-misc/references/sheet/sheet-workbook.md +59 -0
  396. package/skills/dws/dingtalk-misc/references/sheet/sheet-write-data.md +93 -0
  397. package/skills/dws/dingtalk-misc/references/sheet-conventions.md +44 -0
  398. package/skills/dws/dingtalk-misc/references/sheet-intent-guide.md +12 -0
  399. package/skills/dws/dingtalk-misc/references/sheet.md +166 -0
  400. package/skills/dws/dingtalk-misc/references/skill.md +98 -0
  401. package/skills/dws/dingtalk-misc/references/unsupported-scripts.md +49 -0
  402. package/skills/dws/dingtalk-misc/references/url-patterns.md +144 -0
  403. package/skills/dws/dingtalk-misc/references/whiteboard/compose.md +200 -0
  404. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/01-overview.md +57 -0
  405. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/02-query.md +288 -0
  406. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/03-update.md +249 -0
  407. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/04-text-style.md +395 -0
  408. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/05-shape-frame-group-connector.md +186 -0
  409. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/06-vector-icon-path.md +313 -0
  410. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/07-examples-errors-write-support.md +270 -0
  411. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/08-catalogs.md +61 -0
  412. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1.md +54 -0
  413. package/skills/dws/dingtalk-misc/references/whiteboard/replace.md +41 -0
  414. package/skills/dws/dingtalk-misc/references/whiteboard/vector.md +51 -0
  415. package/skills/dws/dingtalk-misc/references/whiteboard.md +115 -0
  416. package/skills/dws/dingtalk-misc/scripts/aiapp_create_and_poll.py +138 -0
  417. package/skills/dws/dingtalk-misc/scripts/attendance_my_record.py +91 -0
  418. package/skills/dws/dingtalk-misc/scripts/attendance_report_checkin.py +440 -0
  419. package/skills/dws/dingtalk-misc/scripts/attendance_report_common.py +1698 -0
  420. package/skills/dws/dingtalk-misc/scripts/attendance_report_daily.py +558 -0
  421. package/skills/dws/dingtalk-misc/scripts/attendance_report_detail.py +809 -0
  422. package/skills/dws/dingtalk-misc/scripts/attendance_report_monthly.py +758 -0
  423. package/skills/dws/dingtalk-misc/scripts/attendance_report_record.py +947 -0
  424. package/skills/dws/dingtalk-misc/scripts/attendance_schedule_export.py +344 -0
  425. package/skills/dws/dingtalk-misc/scripts/attendance_schedule_import.py +498 -0
  426. package/skills/dws/dingtalk-misc/scripts/attendance_team_shift.py +89 -0
  427. package/skills/dws/dingtalk-misc/scripts/attendance_vacation_balance.py +617 -0
  428. package/skills/dws/dingtalk-misc/scripts/finance_daily_cashflow.py +70 -0
  429. package/skills/dws/dingtalk-misc/scripts/finance_expense_flow.py +138 -0
  430. package/skills/dws/dingtalk-misc/scripts/oa_batch_approve.py +158 -0
  431. package/skills/dws/dingtalk-misc/scripts/oa_pending_review.py +132 -0
  432. package/skills/dws/dingtalk-misc/scripts/report_received_today.py +315 -0
  433. package/skills/dws/dingtalk-misc/scripts/yida_custom_page_update.py +401 -0
  434. package/skills/dws/dingtalk-misc/scripts/yida_form_builder.py +526 -0
  435. package/skills/dws/dingtalk-misc/scripts/yida_form_fields.py +315 -0
  436. package/skills/dws/dingtalk-misc/scripts/yida_form_inspector.py +311 -0
  437. package/skills/dws/dingtalk-misc/scripts/yida_form_update.py +185 -0
  438. package/skills/dws/dingtalk-misc/scripts/yida_jsx_pipeline.py +1588 -0
  439. package/skills/dws/dingtalk-misc/scripts/yida_page_compiler.py +1441 -0
  440. package/skills/dws/dingtalk-misc/scripts/yida_page_generate.py +514 -0
  441. package/skills/dws/dingtalk-misc/scripts/yida_page_schema.py +519 -0
  442. package/skills/dws/dingtalk-misc/scripts/yida_page_self_check.py +398 -0
  443. package/skills/dws/dingtalk-misc/scripts/yida_process_flow.py +1478 -0
  444. package/skills/dws/dingtalk-misc/scripts/yida_process_update.py +589 -0
  445. package/skills/dws/dingtalk-misc/scripts/yida_report_builder.py +494 -0
  446. package/skills/dws/dingtalk-misc/scripts/yida_report_charts.py +1434 -0
  447. package/skills/dws/dingtalk-misc/scripts/yida_report_update.py +244 -0
  448. package/skills/dws/dingtalk-misc/scripts/yida_schema_common.py +340 -0
  449. package/skills/dws/dingtalk-shared/SKILL.md +83 -0
  450. package/skills/dws/dingtalk-shared/references/capability-limits.md +31 -0
  451. package/skills/dws/dingtalk-shared/references/channel-login.md +26 -0
  452. package/skills/dws/dingtalk-shared/references/confirmation.md +59 -0
  453. package/skills/dws/dingtalk-shared/references/error-codes.md +155 -0
  454. package/skills/dws/dingtalk-shared/references/global-reference.md +122 -0
  455. package/skills/dws/dingtalk-shared/references/intent-guide.md +537 -0
  456. package/skills/dws/dingtalk-shared/references/recipes/conventions.md +45 -0
  457. package/skills/dws/dingtalk-shared/references/recipes/lite-catalog.md +275 -0
  458. package/skills/dws/dingtalk-shared/references/recipes/meta.md +67 -0
  459. package/skills/dws/dingtalk-shared/references/routing.md +52 -0
  460. package/skills/dws/dingtalk-shared/references/runtime-contract.md +11 -0
  461. package/skills/dws/dingtalk-shared/references/schema-usage.md +88 -0
  462. package/skills/dws/dingtalk-shared/references/url-patterns.md +144 -0
  463. package/skills/dws/dingtalk-shared/references/workflow-routing.md +45 -0
  464. package/skills/dws/dingtalk-todo/SKILL.md +119 -0
  465. package/skills/dws/dingtalk-todo/references/02-task.md +70 -0
  466. package/skills/dws/dingtalk-todo/references/intent-guide.md +15 -0
  467. package/skills/dws/dingtalk-todo/references/lite-recipes.md +68 -0
  468. package/skills/dws/dingtalk-todo/references/recipes/conventions.md +44 -0
  469. package/skills/dws/dingtalk-todo/references/todo.md +517 -0
  470. package/skills/dws/dingtalk-todo/scripts/todo_batch_create.py +354 -0
  471. package/skills/dws/dingtalk-todo/scripts/todo_daily_summary.py +153 -0
  472. package/skills/dws/dingtalk-todo/scripts/todo_overdue_check.py +101 -0
  473. package/skills/dws/dingtalk-wiki/SKILL.md +117 -0
  474. package/skills/dws/dingtalk-wiki/references/intent-guide.md +14 -0
  475. package/skills/dws/dingtalk-wiki/references/lite-recipes.md +44 -0
  476. package/skills/dws/dingtalk-wiki/references/wiki-members.md +32 -0
  477. package/skills/dws/dingtalk-wiki/references/wiki-node-ops.md +46 -0
  478. package/skills/dws/dingtalk-wiki/references/wiki.md +65 -0
  479. package/skills/dws/provenance.json +27 -0
@@ -0,0 +1,1478 @@
1
+ #!/usr/bin/env python3
2
+ """Shared Yida process-flow schema builder.
3
+
4
+ The schema consumed by integration automation and design process update is the
5
+ same shape:
6
+
7
+ {"props": {"processCode": "TPROC-xxx", ...}, "nodes": [...]}
8
+
9
+ This module accepts either that ready-to-save shape or the flatter node objects
10
+ described in references/yida-process-node.md, then normalizes them into
11
+ the save payload used by `dws yida design process update --content`.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import copy
16
+ from typing import Any
17
+
18
+
19
+ FLOW_PROP_KEYS = ("allowWithdraw", "allowCollaboration", "allowTemporaryStorage")
20
+ FLOW_TOP_LEVEL_KEYS = ("flowConfig", "formulaRules", "approvalSummary", "nodeI18nKeyMap")
21
+
22
+ NODE_CORE_KEYS = {
23
+ "type",
24
+ "componentName",
25
+ "nodeType",
26
+ "tool",
27
+ "toolName",
28
+ "kind",
29
+ "nodeId",
30
+ "name",
31
+ "description",
32
+ "title",
33
+ "approvalType",
34
+ "nextId",
35
+ "prevId",
36
+ "props",
37
+ "childNodes",
38
+ }
39
+
40
+ TYPE_ALIASES = {
41
+ "create_trigger_node": "trigger",
42
+ "trigger_node": "trigger",
43
+ "trigger": "trigger",
44
+ "create_timer_trigger_node": "trigger",
45
+ "timer_trigger_node": "trigger",
46
+ "timerTrigger": "trigger",
47
+ "create_data_create_node": "dataCreate",
48
+ "data_create_node": "dataCreate",
49
+ "AddDataNode": "dataCreate",
50
+ "addData": "dataCreate",
51
+ "dataCreate": "dataCreate",
52
+ "create_data_update_node": "dataUpdate",
53
+ "data_update_node": "dataUpdate",
54
+ "UpdateDataNode": "dataUpdate",
55
+ "updateData": "dataUpdate",
56
+ "dataUpdate": "dataUpdate",
57
+ "create_data_retrieve_node": "dataRetrieve",
58
+ "data_retrieve_node": "dataRetrieve",
59
+ "GetSingleDataNode": "dataRetrieve",
60
+ "GetBatchDataNode": "dataRetrieve",
61
+ "getSingleData": "dataRetrieve",
62
+ "getBatchData": "dataRetrieve",
63
+ "dataRetrieve": "dataRetrieve",
64
+ "DeleteDataNode": "dataDelete",
65
+ "deleteData": "dataDelete",
66
+ "dataDelete": "dataDelete",
67
+ "create_connector_node": "connector",
68
+ "connector_node": "connector",
69
+ "ConnectorNode": "connector",
70
+ "connector": "connector",
71
+ "create_send_message_node": "sendMessage",
72
+ "send_message_node": "sendMessage",
73
+ "SendMessageNode": "sendMessage",
74
+ "sendMessage": "sendMessage",
75
+ "create_send_email_node": "sendEmail",
76
+ "send_email_node": "sendEmail",
77
+ "SendEmailNode": "sendEmail",
78
+ "sendEmail": "sendEmail",
79
+ "create_send_card_node": "sendCard",
80
+ "send_card_node": "sendCard",
81
+ "SendCardNode": "sendCard",
82
+ "CardNode": "sendCard",
83
+ "sendCard": "sendCard",
84
+ "create_update_card_node": "updateCard",
85
+ "update_card_node": "updateCard",
86
+ "UpdateCardNode": "updateCard",
87
+ "CardUpdateNode": "updateCard",
88
+ "updateCard": "updateCard",
89
+ "create_route_node": "route",
90
+ "route_node": "route",
91
+ "ConditionContainer": "route",
92
+ "route": "route",
93
+ "ConditionNode": "condition",
94
+ "ParallelNode": "condition",
95
+ "condition": "condition",
96
+ "create_foreach_node": "forEach",
97
+ "foreach_node": "forEach",
98
+ "CycleContainer": "forEach",
99
+ "forEach": "forEach",
100
+ "foreach": "forEach",
101
+ "create_initiate_approval_node": "initiateApproval",
102
+ "initiate_approval_node": "initiateApproval",
103
+ "InitiateApprovalNode": "initiateApproval",
104
+ "subProcess": "initiateApproval",
105
+ "initiateApproval": "initiateApproval",
106
+ "create_approver_node": "approver",
107
+ "approver_node": "approver",
108
+ "ApprovalNode": "approval",
109
+ "MultiApprovalNode": "approval",
110
+ "approver": "approver",
111
+ "create_executor_node": "executor",
112
+ "executor_node": "executor",
113
+ "OperatorNode": "operator",
114
+ "executor": "executor",
115
+ "create_carbon_copy_node": "carbonCopy",
116
+ "carbon_copy_node": "carbonCopy",
117
+ "CarbonNode": "carbon",
118
+ "carbonCopy": "carbonCopy",
119
+ "GroovyNode": "CodeExecutor",
120
+ "groovy": "CodeExecutor",
121
+ "JavaScriptNode": "CodeExecutor",
122
+ "javascript": "CodeExecutor",
123
+ "AINode": "AIExecutor",
124
+ "ai": "AIExecutor",
125
+ "create_finish_node": "finish",
126
+ "finish_node": "finish",
127
+ "EndNode": "finish",
128
+ "finish": "finish",
129
+ }
130
+
131
+ DEFAULT_NODE_NAMES = {
132
+ "apply": "发起",
133
+ "approval": "审批人",
134
+ "operator": "执行人",
135
+ "carbon": "抄送人",
136
+ "trigger": "表单事件触发",
137
+ "dataCreate": "新增数据",
138
+ "dataUpdate": "更新数据",
139
+ "dataRetrieve": "获取数据",
140
+ "dataDelete": "删除数据",
141
+ "connector": "连接器",
142
+ "sendMessage": "消息通知",
143
+ "sendEmail": "发送邮件",
144
+ "sendCard": "发送卡片",
145
+ "updateCard": "更新卡片",
146
+ "route": "ConditionContainer",
147
+ "condition": "条件",
148
+ "forEach": "循环容器",
149
+ "foreach": "循环容器",
150
+ "initiateApproval": "发起审批",
151
+ "CodeExecutor": "脚本",
152
+ "AIExecutor": "AI 节点",
153
+ "approver": "审批人",
154
+ "executor": "执行人",
155
+ "carbonCopy": "抄送人",
156
+ "finish": "结束",
157
+ }
158
+
159
+ VIEW_COMPONENT_BY_TYPE = {
160
+ "apply": "ApplyNode",
161
+ "approval": "ApprovalNode",
162
+ "operator": "OperatorNode",
163
+ "carbon": "CarbonNode",
164
+ "connector": "ConnectorNode",
165
+ "dataCreate": "AddDataNode",
166
+ "dataUpdate": "UpdateDataNode",
167
+ "dataRetrieve": "GetSingleDataNode",
168
+ "dataDelete": "DeleteDataNode",
169
+ "sendMessage": "SendMessageNode",
170
+ "sendEmail": "SendEmailNode",
171
+ "sendCard": "CardNode",
172
+ "updateCard": "CardUpdateNode",
173
+ "route": "ConditionContainer",
174
+ "condition": "ConditionNode",
175
+ "initiateApproval": "InitiateApprovalNode",
176
+ "approver": "ApprovalNode",
177
+ "executor": "OperatorNode",
178
+ "carbonCopy": "CarbonNode",
179
+ "forEach": "CycleContainer",
180
+ "foreach": "CycleContainer",
181
+ "AIExecutor": "AINode",
182
+ "CodeExecutor": "JavaScriptNode",
183
+ "finish": "EndNode",
184
+ }
185
+
186
+ VIEW_COMPONENT_RULE_KEY = {
187
+ "ConnectorNode": "connectorRules",
188
+ "AddDataNode": "addDataRules",
189
+ "UpdateDataNode": "updateDataRules",
190
+ "DeleteDataNode": "deleteData",
191
+ "GetSingleDataNode": "getData",
192
+ "GetBatchDataNode": "getData",
193
+ "SendMessageNode": "sendMessageRules",
194
+ "SendEmailNode": "sendEmailRules",
195
+ "CardNode": "cardRules",
196
+ "CardUpdateNode": "cardUpdateRules",
197
+ "InitiateApprovalNode": "initiateApprovalRules",
198
+ "CycleContainer": "cycleContainerRules",
199
+ "AINode": "workFlowRules",
200
+ "GroovyNode": "groovy",
201
+ "JavaScriptNode": "JavaScript",
202
+ }
203
+
204
+ TRIGGER_INPUT_KEYS = {
205
+ "formUuid",
206
+ "formEventType",
207
+ "formEventField",
208
+ "triggerFormEventRecursively",
209
+ "conditions",
210
+ "startDate",
211
+ "repeat",
212
+ "end",
213
+ }
214
+
215
+ UNRESOLVED_CARD_BIZ_ID_VALUES = {
216
+ "__masterdata_form_inst_id",
217
+ }
218
+
219
+
220
+ class FlowSchemaError(ValueError):
221
+ """Raised when a flow spec cannot be normalized safely."""
222
+
223
+
224
+ def i18n_name(value: Any, default: str) -> dict[str, str]:
225
+ if isinstance(value, dict):
226
+ result = copy.deepcopy(value)
227
+ result.setdefault("zh_CN", result.get("name") or default)
228
+ result.setdefault("en_US", "")
229
+ result.setdefault("type", "i18n")
230
+ return result
231
+ text = str(value or default)
232
+ return {"zh_CN": text, "en_US": "", "type": "i18n"}
233
+
234
+
235
+ def normalize_next_id(value: Any) -> list[str]:
236
+ if value is None:
237
+ return []
238
+ if isinstance(value, str):
239
+ return [value] if value else []
240
+ if isinstance(value, list):
241
+ return [str(item) for item in value if item not in (None, "")]
242
+ raise FlowSchemaError(f"nextId must be string or list, got {type(value).__name__}")
243
+
244
+
245
+ def normalize_node_type(node: dict[str, Any]) -> str:
246
+ raw = (
247
+ node.get("componentName")
248
+ or node.get("nodeType")
249
+ or node.get("tool")
250
+ or node.get("toolName")
251
+ or node.get("kind")
252
+ or node.get("type")
253
+ )
254
+ if not raw:
255
+ raise FlowSchemaError(f"node {node.get('nodeId') or '<unknown>'} missing type")
256
+ raw = str(raw)
257
+ return TYPE_ALIASES.get(raw, raw)
258
+
259
+
260
+ def _normalize_trigger_props(props: dict[str, Any]) -> dict[str, Any]:
261
+ result = copy.deepcopy(props)
262
+ inputs = result.get("inputs")
263
+ if inputs is None:
264
+ inputs = {}
265
+ if not isinstance(inputs, dict):
266
+ raise FlowSchemaError("trigger props.inputs must be object")
267
+
268
+ for key in list(TRIGGER_INPUT_KEYS):
269
+ if key in result:
270
+ inputs[key] = result.pop(key)
271
+
272
+ if "triggerType" not in result:
273
+ result["triggerType"] = "TimerEvent" if ("repeat" in inputs or "startDate" in inputs) else "FormEvent"
274
+ result["inputs"] = inputs
275
+ return result
276
+
277
+
278
+ def _script_outputs_schema(outputs: Any) -> list[dict[str, Any]]:
279
+ if not isinstance(outputs, list):
280
+ return []
281
+ result = []
282
+ for item in outputs:
283
+ if not isinstance(item, dict):
284
+ continue
285
+ result.append({
286
+ "description": item.get("desc") or item.get("description") or "",
287
+ "name": item.get("name"),
288
+ "type": item.get("type"),
289
+ "valueType": item.get("valueType"),
290
+ })
291
+ return result
292
+
293
+
294
+ def _normalize_process_payload(node_type: str, props: dict[str, Any]) -> tuple[str, dict[str, Any]]:
295
+ result = copy.deepcopy(props)
296
+
297
+ if node_type in {"approval", "approver", "operator", "executor"} and isinstance(result.get("approver"), dict):
298
+ approver = result["approver"]
299
+ if approver.get("approvalType") and not result.get("approvalType"):
300
+ result["approvalType"] = copy.deepcopy(approver["approvalType"])
301
+ if approver.get("approvals") is not None and "approvals" not in result:
302
+ result["approvals"] = copy.deepcopy(approver.get("approvals"))
303
+ if isinstance(approver.get("approverRules"), dict) and "approverRules" not in result:
304
+ result["approverRules"] = copy.deepcopy(approver["approverRules"])
305
+
306
+ if node_type == "dataRetrieve" and isinstance(result.get("getData"), dict):
307
+ return node_type, copy.deepcopy(result["getData"])
308
+ if node_type == "dataUpdate" and isinstance(result.get("updateDataRules"), dict):
309
+ return node_type, copy.deepcopy(result["updateDataRules"])
310
+ if node_type == "dataDelete" and isinstance(result.get("deleteData"), dict):
311
+ return node_type, copy.deepcopy(result["deleteData"])
312
+ if node_type == "sendMessage" and isinstance(result.get("sendMessageRules"), dict):
313
+ payload = copy.deepcopy(result["sendMessageRules"])
314
+ payload.pop("description", None)
315
+ return node_type, payload
316
+ if node_type == "sendEmail" and isinstance(result.get("sendEmailRules"), dict):
317
+ return node_type, copy.deepcopy(result["sendEmailRules"])
318
+ if node_type == "sendCard":
319
+ if isinstance(result.get("sendCardRules"), dict):
320
+ return node_type, copy.deepcopy(result["sendCardRules"])
321
+ if isinstance(result.get("cardRules"), dict):
322
+ return node_type, copy.deepcopy(result["cardRules"])
323
+ if node_type == "updateCard":
324
+ if isinstance(result.get("updateCardRules"), dict):
325
+ return node_type, copy.deepcopy(result["updateCardRules"])
326
+ if isinstance(result.get("cardUpdateRules"), dict):
327
+ return node_type, copy.deepcopy(result["cardUpdateRules"])
328
+ if node_type == "initiateApproval" and isinstance(result.get("initiateApprovalRules"), dict):
329
+ return node_type, copy.deepcopy(result["initiateApprovalRules"])
330
+ if node_type == "forEach":
331
+ if isinstance(result.get("cycleContainerRules"), dict):
332
+ return "foreach", copy.deepcopy(result["cycleContainerRules"])
333
+ if isinstance(result.get("cycleRules"), dict):
334
+ return "foreach", copy.deepcopy(result["cycleRules"])
335
+ if isinstance(result.get("foreachRules"), dict):
336
+ return "foreach", copy.deepcopy(result["foreachRules"])
337
+ if node_type == "AIExecutor" and isinstance(result.get("workFlowRules"), dict):
338
+ workflow = result["workFlowRules"]
339
+ return node_type, {
340
+ "type": "aiFlow",
341
+ "action": {"flowId": workflow.get("flowId")},
342
+ "outputs": copy.deepcopy(workflow.get("outputs") or []),
343
+ "yidaFieldIdList": copy.deepcopy(workflow.get("yidaFieldIdList") or []),
344
+ }
345
+ if node_type == "CodeExecutor":
346
+ script = result.get("JavaScript") if isinstance(result.get("JavaScript"), dict) else None
347
+ script_type = "JavaScript"
348
+ if script is None and isinstance(result.get("groovy"), dict):
349
+ script = result.get("groovy")
350
+ script_type = "groovy"
351
+ if script is not None:
352
+ return node_type, {
353
+ "inputs": copy.deepcopy(script.get("inputs")),
354
+ "action": copy.deepcopy(script.get("action")),
355
+ "scriptType": script.get("scriptType") or script_type,
356
+ "outputsSchema": _script_outputs_schema(script.get("outputs")),
357
+ }
358
+
359
+ return node_type, result
360
+
361
+
362
+ def _contains_originator_keyword(value: Any) -> bool:
363
+ text = _i18n_text(value, "").lower()
364
+ return any(keyword in text for keyword in ("申请人", "发起人", "applicant", "originator"))
365
+
366
+
367
+ def _actioner_targets_originator(value: Any) -> bool:
368
+ if isinstance(value, str):
369
+ return value.lower() in {"originator", "$originator", "applicant", "$applicant"} or value in {"申请人", "发起人"}
370
+ if not isinstance(value, dict):
371
+ return False
372
+
373
+ rule_type = str(value.get("type") or value.get("approvalType") or "").lower()
374
+ if rule_type in {"originator", "ext_target_approval_originator"}:
375
+ return True
376
+
377
+ for key in ("value", "id", "userId", "fieldId", "fieldName", "label", "name", "description"):
378
+ if _actioner_targets_originator(value.get(key)):
379
+ return True
380
+ return False
381
+
382
+
383
+ def _is_originator_operator_node(node_type: str, node: dict[str, Any], props: dict[str, Any]) -> bool:
384
+ if node_type not in {"operator", "executor"}:
385
+ return False
386
+ if _actioner_targets_originator(props.get("actionerRule")):
387
+ return True
388
+ if _actioner_targets_originator(props.get("executor")) or _actioner_targets_originator(props.get("approver")):
389
+ return True
390
+ if _contains_originator_keyword(node.get("name")) or _contains_originator_keyword(node.get("description")):
391
+ return True
392
+ if _contains_originator_keyword(props.get("name")) or _contains_originator_keyword(props.get("description")):
393
+ return True
394
+ return False
395
+
396
+
397
+ def _operator_action_to_approval_action(action: Any) -> dict[str, Any] | None:
398
+ if isinstance(action, dict):
399
+ normalized = copy.deepcopy(action)
400
+ raw_action = str(normalized.get("action") or "").lower()
401
+ if raw_action in {"submit", "confirm"}:
402
+ normalized["action"] = "agree"
403
+ normalized.setdefault("hidden", False)
404
+ normalized.setdefault("name", i18n_name(normalized.get("name") or normalized.get("text"), "确认"))
405
+ normalized.setdefault("text", copy.deepcopy(normalized["name"]))
406
+ normalized.setdefault("alias", copy.deepcopy(normalized["name"]))
407
+ return normalized
408
+
409
+ raw = str(action or "").lower()
410
+ if raw in {"submit", "confirm", "agree"}:
411
+ return {
412
+ "hidden": False,
413
+ "name": {"en_US": "Confirm", "zh_CN": "确认", "type": "i18n"},
414
+ "text": {"en_US": "Confirm", "zh_CN": "确认", "type": "i18n"},
415
+ "action": "agree",
416
+ "alias": {"en_US": "Confirm", "zh_CN": "确认", "type": "i18n"},
417
+ }
418
+ if raw in {"transfer", "forward"}:
419
+ return {
420
+ "hidden": True,
421
+ "name": {"en_US": "Forward", "zh_CN": "转交", "type": "i18n"},
422
+ "text": {"en_US": "Forward", "zh_CN": "转交", "type": "i18n"},
423
+ "action": "forward",
424
+ "alias": {"en_US": "Forward", "zh_CN": "转交", "type": "i18n"},
425
+ }
426
+ return None
427
+
428
+
429
+ def _originator_approval_props_from_operator(props: dict[str, Any]) -> dict[str, Any]:
430
+ result = copy.deepcopy(props)
431
+ result.pop("actionerRule", None)
432
+ result.pop("finishRule", None)
433
+ result.pop("executor", None)
434
+ result.pop("approver", None)
435
+
436
+ result["approvals"] = [["originator"]]
437
+ approver_rules = copy.deepcopy(result.get("approverRules") if isinstance(result.get("approverRules"), dict) else {})
438
+ approver_rules.update({
439
+ "type": "ext_target_approval_originator",
440
+ "mode": "ApprovalNode_rules_only",
441
+ "approverList": [{"type": "ext_target_approval"}],
442
+ "multiApproverType": approver_rules.get("multiApproverType", "all"),
443
+ "conditionalMode": approver_rules.get("conditionalMode", "conditional"),
444
+ "approvals": [["originator"]],
445
+ "description": approver_rules.get("description") or "发起人本人",
446
+ })
447
+ result["approverRules"] = approver_rules
448
+
449
+ raw_actions = result.get("actions")
450
+ converted_actions = [
451
+ converted
452
+ for converted in (_operator_action_to_approval_action(item) for item in raw_actions or [])
453
+ if converted is not None
454
+ ]
455
+ if not converted_actions:
456
+ converted_actions = [
457
+ _operator_action_to_approval_action("submit"),
458
+ _operator_action_to_approval_action("transfer"),
459
+ ]
460
+ result["actions"] = [item for item in converted_actions if item is not None]
461
+ result.setdefault("appendActions", [])
462
+ result.setdefault("openDigitalSign", False)
463
+ result.setdefault("noActionersType", "stopProcess")
464
+ return result
465
+
466
+
467
+ def normalize_node(node: dict[str, Any]) -> dict[str, Any]:
468
+ if not isinstance(node, dict):
469
+ raise FlowSchemaError(f"node must be object, got {type(node).__name__}")
470
+
471
+ node_type = normalize_node_type(node)
472
+ node_id = node.get("nodeId")
473
+ if not isinstance(node_id, str) or not node_id:
474
+ raise FlowSchemaError(f"{node_type} node missing nodeId")
475
+
476
+ props = copy.deepcopy(node.get("props") or {})
477
+ if not isinstance(props, dict):
478
+ raise FlowSchemaError(f"node {node_id} props must be object")
479
+
480
+ for key, value in node.items():
481
+ if key == "type" and (
482
+ node.get("componentName")
483
+ or node.get("nodeType")
484
+ or node.get("tool")
485
+ or node.get("toolName")
486
+ or node.get("kind")
487
+ ):
488
+ props[key] = copy.deepcopy(value)
489
+ continue
490
+ if key not in NODE_CORE_KEYS:
491
+ props[key] = copy.deepcopy(value)
492
+
493
+ if node_type == "trigger":
494
+ props = _normalize_trigger_props(props)
495
+ node_type, props = _normalize_process_payload(node_type, props)
496
+ approval_type = copy.deepcopy(node.get("approvalType")) if "approvalType" in node else copy.deepcopy(props.get("approvalType"))
497
+ if _is_originator_operator_node(node_type, node, props):
498
+ node_type = "approval"
499
+ approval_type = "ext_target_approval_originator"
500
+ props = _originator_approval_props_from_operator(props)
501
+
502
+ child_nodes = node.get("childNodes") or []
503
+ if not isinstance(child_nodes, list):
504
+ raise FlowSchemaError(f"node {node_id} childNodes must be list")
505
+
506
+ normalized = {
507
+ "name": i18n_name(node.get("name"), DEFAULT_NODE_NAMES.get(node_type, node_type)),
508
+ "type": node_type,
509
+ "nodeId": node_id,
510
+ "nextId": normalize_next_id(node.get("nextId")),
511
+ "props": props,
512
+ "childNodes": [normalize_node(child) for child in child_nodes],
513
+ }
514
+ if "prevId" in node:
515
+ normalized["prevId"] = node["prevId"]
516
+ if "description" in node:
517
+ normalized["description"] = copy.deepcopy(node.get("description"))
518
+ if "title" in node:
519
+ normalized["title"] = copy.deepcopy(node.get("title"))
520
+ if approval_type is not None:
521
+ normalized["approvalType"] = approval_type
522
+ return normalized
523
+
524
+
525
+ def _walk_nodes(nodes: list[dict[str, Any]]) -> list[dict[str, Any]]:
526
+ result: list[dict[str, Any]] = []
527
+ for node in nodes:
528
+ result.append(node)
529
+ result.extend(_walk_nodes(node.get("childNodes") or []))
530
+ return result
531
+
532
+
533
+ def _node_label(node: dict[str, Any]) -> str:
534
+ return f"{node.get('nodeId') or '<unknown>'}({ _i18n_text(node.get('name'), str(node.get('type') or 'node')) })"
535
+
536
+
537
+ def _non_empty_assignments(value: Any) -> bool:
538
+ return isinstance(value, list) and any(isinstance(item, dict) for item in value)
539
+
540
+
541
+ def _rule_list_has_entries(value: Any) -> bool:
542
+ if not isinstance(value, dict):
543
+ return False
544
+ rules = value.get("rules")
545
+ if isinstance(rules, list):
546
+ return any(isinstance(item, dict) for item in rules)
547
+ if isinstance(rules, dict):
548
+ return _rule_list_has_entries(rules)
549
+ return False
550
+
551
+
552
+ def _validate_assignment_columns(node: dict[str, Any], assignments: Any) -> None:
553
+ if not isinstance(assignments, list):
554
+ return
555
+ for index, assignment in enumerate(assignments, start=1):
556
+ if not isinstance(assignment, dict):
557
+ raise FlowSchemaError(f"{_node_label(node)} assignment #{index} must be object")
558
+ if not assignment.get("column"):
559
+ raise FlowSchemaError(f"{_node_label(node)} assignment #{index} missing column")
560
+ if not assignment.get("valueType"):
561
+ raise FlowSchemaError(f"{_node_label(node)} assignment #{index} missing valueType")
562
+
563
+
564
+ def _validate_designer_runtime_node(node: dict[str, Any]) -> None:
565
+ node_type = str(node.get("type") or "")
566
+ props = node.get("props")
567
+ if not isinstance(props, dict):
568
+ return
569
+
570
+ if node_type == "dataCreate":
571
+ if not props.get("formUuid"):
572
+ raise FlowSchemaError(f"{_node_label(node)} AddDataNode missing target formUuid")
573
+ assignments = props.get("assignments")
574
+ has_rules = _rule_list_has_entries(props.get("rules"))
575
+ add_data_rules = props.get("addDataRules") if isinstance(props.get("addDataRules"), dict) else {}
576
+ has_add_rules = _rule_list_has_entries(add_data_rules.get("rules"))
577
+ if not (_non_empty_assignments(assignments) or has_rules or has_add_rules):
578
+ raise FlowSchemaError(f"{_node_label(node)} AddDataNode missing field assignment rules")
579
+ _validate_assignment_columns(node, assignments)
580
+
581
+ if node_type == "dataUpdate":
582
+ if not props.get("sourceId"):
583
+ raise FlowSchemaError(f"{_node_label(node)} UpdateDataNode missing sourceId")
584
+ assignments = props.get("assignments")
585
+ update_rules = props.get("updateDataRules") if isinstance(props.get("updateDataRules"), dict) else {}
586
+ has_update_rules = _rule_list_has_entries(update_rules.get("rules"))
587
+ if not (_non_empty_assignments(assignments) or has_update_rules):
588
+ raise FlowSchemaError(f"{_node_label(node)} UpdateDataNode missing field assignment rules")
589
+ _validate_assignment_columns(node, assignments)
590
+
591
+ if node_type == "dataRetrieve":
592
+ if not props.get("sourceId"):
593
+ raise FlowSchemaError(f"{_node_label(node)} GetDataNode missing sourceId")
594
+
595
+ if node_type == "sendCard":
596
+ if not props.get("cardAppId"):
597
+ raise FlowSchemaError(f"{_node_label(node)} CardNode missing cardAppId")
598
+ if not props.get("cardPageCode"):
599
+ raise FlowSchemaError(f"{_node_label(node)} CardNode missing cardPageCode")
600
+ if props.get("sendType") == "update":
601
+ biz_id = props.get("bizId") if isinstance(props.get("bizId"), dict) else {}
602
+ value = str(biz_id.get("value") or "")
603
+ if not value:
604
+ raise FlowSchemaError(f"{_node_label(node)} CardNode sendType=update missing bizId.value")
605
+ if value in UNRESOLVED_CARD_BIZ_ID_VALUES:
606
+ raise FlowSchemaError(
607
+ f"{_node_label(node)} CardNode bizId.value={value!r} is not designer-visible; "
608
+ "choose a real variable from the designer dropdown, such as a current form fieldId"
609
+ )
610
+
611
+
612
+ def validate_flow_schema(flow: dict[str, Any]) -> None:
613
+ if not isinstance(flow, dict):
614
+ raise FlowSchemaError("flow schema must be object")
615
+ props = flow.get("props")
616
+ nodes = flow.get("nodes")
617
+ if not isinstance(props, dict):
618
+ raise FlowSchemaError("flow schema missing props object")
619
+ if not props.get("processCode"):
620
+ raise FlowSchemaError("flow schema missing props.processCode")
621
+ if not isinstance(nodes, list) or not nodes:
622
+ raise FlowSchemaError("flow schema nodes must be non-empty array")
623
+
624
+ seen: set[str] = set()
625
+ for node in _walk_nodes(nodes):
626
+ node_id = node.get("nodeId")
627
+ if node_id in seen:
628
+ raise FlowSchemaError(f"duplicate nodeId: {node_id}")
629
+ seen.add(node_id)
630
+ _validate_designer_runtime_node(node)
631
+
632
+
633
+ def build_automation_flow(
634
+ process_code: str,
635
+ nodes: list[dict[str, Any]],
636
+ *,
637
+ allow_withdraw: bool = True,
638
+ allow_collaboration: bool = True,
639
+ allow_temporary_storage: bool = True,
640
+ ) -> dict[str, Any]:
641
+ if not process_code:
642
+ raise FlowSchemaError("processCode is required")
643
+ if not isinstance(nodes, list) or not nodes:
644
+ raise FlowSchemaError("nodes must be non-empty array")
645
+
646
+ flow = {
647
+ "props": {
648
+ "processCode": process_code,
649
+ "allowWithdraw": allow_withdraw,
650
+ "allowCollaboration": allow_collaboration,
651
+ "allowTemporaryStorage": allow_temporary_storage,
652
+ },
653
+ "nodes": [normalize_node(node) for node in nodes],
654
+ }
655
+ validate_flow_schema(flow)
656
+ return flow
657
+
658
+
659
+ def build_flow_schema(spec: Any, *, process_code: str | None = None) -> dict[str, Any]:
660
+ if isinstance(spec, list):
661
+ if not process_code:
662
+ raise FlowSchemaError("processCode is required when spec is a node array")
663
+ return build_automation_flow(process_code, spec)
664
+
665
+ if not isinstance(spec, dict):
666
+ raise FlowSchemaError("flow spec must be object or node array")
667
+
668
+ if "props" in spec and "nodes" in spec:
669
+ props = copy.deepcopy(spec["props"])
670
+ if not isinstance(props, dict):
671
+ raise FlowSchemaError("props must be object")
672
+ if process_code:
673
+ props["processCode"] = process_code
674
+ if "bindingForm" in spec and not props.get("bindingForm"):
675
+ props["bindingForm"] = copy.deepcopy(spec["bindingForm"])
676
+ nodes = spec["nodes"]
677
+ if not isinstance(nodes, list):
678
+ raise FlowSchemaError("nodes must be array")
679
+ flow = {"props": props, "nodes": [normalize_node(node) for node in nodes]}
680
+ for key in FLOW_PROP_KEYS:
681
+ flow["props"].setdefault(key, True)
682
+ for key in FLOW_TOP_LEVEL_KEYS:
683
+ if key in spec:
684
+ flow[key] = copy.deepcopy(spec[key])
685
+ validate_flow_schema(flow)
686
+ return flow
687
+
688
+ nodes = spec.get("nodes")
689
+ if not isinstance(nodes, list):
690
+ raise FlowSchemaError("flow spec missing nodes array")
691
+
692
+ code = process_code or spec.get("processCode")
693
+ flow = build_automation_flow(
694
+ str(code or ""),
695
+ nodes,
696
+ allow_withdraw=bool(spec.get("allowWithdraw", True)),
697
+ allow_collaboration=bool(spec.get("allowCollaboration", True)),
698
+ allow_temporary_storage=bool(spec.get("allowTemporaryStorage", True)),
699
+ )
700
+ if "bindingForm" in spec:
701
+ flow["props"]["bindingForm"] = copy.deepcopy(spec["bindingForm"])
702
+ return flow
703
+
704
+
705
+ def _i18n_text(value: Any, default: str = "") -> str:
706
+ if isinstance(value, dict):
707
+ return str(value.get("zh_CN") or value.get("en_US") or value.get("value") or default)
708
+ return str(value or default)
709
+
710
+
711
+ def _default_component_props() -> dict[str, Any]:
712
+ return {
713
+ "defaultDataSource": {},
714
+ "relateAppType": "",
715
+ "relateOrderEnable": False,
716
+ "relateOrderConfig": [],
717
+ }
718
+
719
+
720
+ def _rule_for_assignment(assignment: dict[str, Any], fields_by_id: dict[str, dict[str, Any]]) -> dict[str, Any]:
721
+ column = str(assignment.get("column") or "")
722
+ field = copy.deepcopy(fields_by_id.get(column) or {})
723
+ rule = {
724
+ "ruleId": f"rule-{column}" if column else "rule-unknown",
725
+ "name": column,
726
+ "label": field.get("label") or column,
727
+ "componentName": field.get("componentName"),
728
+ "componentOption": field.get("componentOption", "[]"),
729
+ "componentProps": copy.deepcopy(field.get("componentProps") or _default_component_props()),
730
+ "required": bool(field.get("required", False)),
731
+ "valueType": assignment.get("valueType", "literal"),
732
+ "value": assignment.get("value"),
733
+ }
734
+ for key in ("__display", "__source"):
735
+ if key in assignment:
736
+ rule[key] = copy.deepcopy(assignment[key])
737
+ return rule
738
+
739
+
740
+ def _component_option_map(child_list: list[dict[str, Any]]) -> dict[str, Any]:
741
+ return {
742
+ str(field.get("fieldId") or field.get("name") or ""): copy.deepcopy(field.get("componentOption", "[]"))
743
+ for field in child_list
744
+ if field.get("fieldId") or field.get("name")
745
+ }
746
+
747
+
748
+ def _rules_for_assignments(
749
+ assignments: list[dict[str, Any]],
750
+ child_list: list[dict[str, Any]],
751
+ existing_rules: Any = None,
752
+ ) -> dict[str, Any]:
753
+ rules = copy.deepcopy(existing_rules or {})
754
+ component_option_map = _component_option_map(child_list)
755
+ if assignments and not rules.get("rules"):
756
+ fields_by_id = {str(field.get("fieldId") or field.get("name")): field for field in child_list}
757
+ rules = {
758
+ "ruleId": "rule-root",
759
+ "childList": copy.deepcopy(child_list),
760
+ "componentOptionMap": copy.deepcopy(component_option_map),
761
+ "rules": [_rule_for_assignment(item, fields_by_id) for item in assignments],
762
+ }
763
+ else:
764
+ rules.setdefault("ruleId", "rule-root")
765
+ rules.setdefault("childList", copy.deepcopy(child_list))
766
+ rules.setdefault("componentOptionMap", copy.deepcopy(component_option_map))
767
+ rules.setdefault("rules", [])
768
+ return rules
769
+
770
+
771
+ def _approval_option_lookup(props: dict[str, Any]) -> dict[str, dict[str, Any]]:
772
+ result: dict[str, dict[str, Any]] = {}
773
+ for key in ("approvalUsers", "approvalOptions", "approverOptions"):
774
+ options = props.get(key)
775
+ if not isinstance(options, list):
776
+ continue
777
+ for item in options:
778
+ if not isinstance(item, dict):
779
+ continue
780
+ user_id = item.get("id") or item.get("userId") or item.get("value")
781
+ if isinstance(user_id, str) and user_id:
782
+ normalized = copy.deepcopy(item)
783
+ normalized.setdefault("id", user_id)
784
+ normalized.setdefault("label", item.get("name") or item.get("userName") or user_id)
785
+ normalized.setdefault("type", "employee")
786
+ normalized.setdefault("roleType", "DINGTALK")
787
+ result[user_id] = normalized
788
+ return result
789
+
790
+
791
+ def _approval_value_for_view(value: Any, options_by_id: dict[str, dict[str, Any]]) -> Any:
792
+ if isinstance(value, dict):
793
+ item = copy.deepcopy(value)
794
+ user_id = item.get("id") or item.get("userId") or item.get("value")
795
+ if isinstance(user_id, str) and user_id:
796
+ item.setdefault("id", user_id)
797
+ item.setdefault("label", item.get("name") or item.get("userName") or item.get("id") or "")
798
+ item.setdefault("type", "employee")
799
+ item.setdefault("roleType", "DINGTALK")
800
+ return item
801
+ if isinstance(value, str):
802
+ if value in options_by_id:
803
+ return copy.deepcopy(options_by_id[value])
804
+ return {
805
+ "id": value,
806
+ "label": value,
807
+ "type": "employee",
808
+ "roleType": "DINGTALK",
809
+ }
810
+ return value
811
+
812
+
813
+ def _approver_rules_for_view(node: dict[str, Any], props: dict[str, Any]) -> dict[str, Any]:
814
+ raw_rules = props.get("approverRules") if isinstance(props.get("approverRules"), dict) else {}
815
+ result = copy.deepcopy(raw_rules)
816
+ result.setdefault("type", node.get("approvalType") or props.get("approvalType") or "ext_target_approval")
817
+ result.setdefault("mode", "ApprovalNode_rules_only")
818
+ if result.get("type") == "ext_target_approval_originator":
819
+ approver_list = result.get("approverList")
820
+ if isinstance(approver_list, list) and approver_list:
821
+ normalized_list = []
822
+ for item in approver_list:
823
+ if isinstance(item, dict):
824
+ normalized = copy.deepcopy(item)
825
+ if normalized.get("type") == "ext_target_approval_originator":
826
+ normalized["type"] = "ext_target_approval"
827
+ normalized_list.append(normalized)
828
+ else:
829
+ normalized_list.append(item)
830
+ result["approverList"] = normalized_list
831
+ else:
832
+ result["approverList"] = [{"type": "ext_target_approval"}]
833
+ result.setdefault("description", "发起人本人")
834
+ else:
835
+ result.setdefault("approverList", [{"type": result["type"]}])
836
+ result.setdefault("multiApproverType", "all")
837
+ result.setdefault("conditionalMode", props.get("conditionalMode", "conditional"))
838
+
839
+ approvals = result.get("approvals", props.get("approvals", []))
840
+ if isinstance(approvals, list):
841
+ options_by_id = _approval_option_lookup(props)
842
+ result["approvals"] = [_approval_value_for_view(item, options_by_id) for item in approvals]
843
+ labels = [
844
+ str(item.get("label") or item.get("id") or "")
845
+ for item in result["approvals"]
846
+ if isinstance(item, dict)
847
+ ]
848
+ if labels and not result.get("description"):
849
+ result["description"] = "、".join(label for label in labels if label)
850
+ return result
851
+
852
+
853
+ def _operator_actions_for_view(props: dict[str, Any]) -> dict[str, Any]:
854
+ normal_actions = []
855
+ for action in props.get("actions") or []:
856
+ if isinstance(action, dict):
857
+ normalized = copy.deepcopy(action)
858
+ normalized.setdefault("hidden", False)
859
+ normalized.setdefault("name", i18n_name(normalized.get("name") or normalized.get("text"), "提交"))
860
+ normalized.setdefault("text", copy.deepcopy(normalized["name"]))
861
+ normalized.setdefault("alias", copy.deepcopy(normalized["name"]))
862
+ normal_actions.append(normalized)
863
+ continue
864
+
865
+ raw = str(action or "").lower()
866
+ if raw in {"submit", "agree", "confirm"}:
867
+ normal_actions.append({
868
+ "hidden": False,
869
+ "name": {"en_US": "Submit", "zh_CN": "提交", "type": "i18n"},
870
+ "text": {"en_US": "Submit", "zh_CN": "提交", "type": "i18n"},
871
+ "action": "submit",
872
+ "alias": {"en_US": "Submit", "zh_CN": "提交", "type": "i18n"},
873
+ })
874
+ elif raw in {"transfer", "forward"}:
875
+ normal_actions.append({
876
+ "hidden": True,
877
+ "name": {"en_US": "Forward", "zh_CN": "转交", "type": "i18n"},
878
+ "text": {"en_US": "Forward", "zh_CN": "转交", "type": "i18n"},
879
+ "action": "forward",
880
+ "alias": {"en_US": "Forward", "zh_CN": "转交", "type": "i18n"},
881
+ })
882
+
883
+ if not normal_actions:
884
+ normal_actions.append({
885
+ "hidden": False,
886
+ "name": {"en_US": "Submit", "zh_CN": "提交", "type": "i18n"},
887
+ "text": {"en_US": "Submit", "zh_CN": "提交", "type": "i18n"},
888
+ "action": "submit",
889
+ "alias": {"en_US": "Submit", "zh_CN": "提交", "type": "i18n"},
890
+ })
891
+
892
+ return {
893
+ "normalActions": normal_actions,
894
+ "appendActions": copy.deepcopy(props.get("appendActions") or []),
895
+ }
896
+
897
+
898
+ def _operator_rules_for_view(node: dict[str, Any], props: dict[str, Any]) -> dict[str, Any]:
899
+ actioner_rule = props.get("actionerRule") if isinstance(props.get("actionerRule"), dict) else {}
900
+ if actioner_rule and not props.get("approvals") and not props.get("approverRules"):
901
+ rule_type = str(actioner_rule.get("type") or "")
902
+ value = str(actioner_rule.get("value") or "")
903
+ if rule_type == "static" and value:
904
+ props = copy.deepcopy(props)
905
+ props["approvals"] = [item.strip() for item in value.split(",") if item.strip()]
906
+ props.setdefault("approvalType", "ext_target_approval")
907
+ return _approver_rules_for_view(node, props)
908
+
909
+
910
+ def _message_recipient_description(props: dict[str, Any]) -> str:
911
+ parts: list[str] = []
912
+
913
+ for item in props.get("toUsers") or []:
914
+ if not isinstance(item, dict):
915
+ continue
916
+ label = item.get("userName") or item.get("name") or item.get("label") or item.get("userId")
917
+ if label:
918
+ parts.append(str(label))
919
+
920
+ for item in props.get("toRoles") or []:
921
+ if not isinstance(item, dict):
922
+ continue
923
+ label = item.get("roleName") or item.get("name") or item.get("label") or item.get("roleCode")
924
+ if label:
925
+ parts.append(str(label))
926
+
927
+ field_labels = {
928
+ "PROCESS_PARTICIPANTS": "流程参与人",
929
+ "PROCESS_APPROVERS": "流程审批人",
930
+ "PROCESS_CARBONS": "流程抄送人",
931
+ "FORM_INST_MODIFIER": "表单修改人",
932
+ }
933
+ for item in props.get("userFields") or []:
934
+ label = field_labels.get(str(item), str(item))
935
+ if label:
936
+ parts.append(label)
937
+
938
+ for key, label in (
939
+ ("toVars", "变量接收人"),
940
+ ("toGroups", "群组"),
941
+ ("toGroupVars", "变量群组"),
942
+ ("robotUrls", "机器人"),
943
+ ):
944
+ values = props.get(key) or []
945
+ if values:
946
+ parts.append(label)
947
+
948
+ seen: set[str] = set()
949
+ result: list[str] = []
950
+ for part in parts:
951
+ if part not in seen:
952
+ seen.add(part)
953
+ result.append(part)
954
+ return "、".join(result)
955
+
956
+
957
+ def _send_message_rules_for_view(node: dict[str, Any], props: dict[str, Any]) -> tuple[dict[str, Any], str]:
958
+ result = copy.deepcopy(props)
959
+ description = result.get("description")
960
+ if isinstance(description, dict):
961
+ description = _i18n_text(description)
962
+ if not description and node.get("description") not in (None, ""):
963
+ description = _i18n_text(node.get("description"))
964
+ if not description:
965
+ description = _message_recipient_description(result)
966
+ if description:
967
+ result["description"] = description
968
+ return result, str(description or "")
969
+
970
+
971
+ def _condition_description(condition_group: Any) -> str:
972
+ if not isinstance(condition_group, dict):
973
+ return ""
974
+ rules = condition_group.get("rules")
975
+ if not isinstance(rules, list):
976
+ return ""
977
+ parts = []
978
+ for rule in rules:
979
+ if not isinstance(rule, dict):
980
+ continue
981
+ name = str(rule.get("name") or rule.get("id") or "")
982
+ op = str(rule.get("op") or rule.get("opCode") or "")
983
+ value = rule.get("ruleValue", rule.get("value", ""))
984
+ if isinstance(value, list):
985
+ value_text = "、".join(str(item.get("label") or item.get("name") or item.get("id") or item) if isinstance(item, dict) else str(item) for item in value)
986
+ else:
987
+ value_text = str(value)
988
+ if name and op:
989
+ parts.append(f"{name}{op}{value_text}")
990
+ return ",".join(parts)
991
+
992
+
993
+ def _condition_rules_for_view(props: dict[str, Any]) -> dict[str, Any]:
994
+ raw_conditions = props.get("conditions") if isinstance(props.get("conditions"), dict) else {}
995
+ result = copy.deepcopy(raw_conditions)
996
+ result.setdefault("condition", "AND")
997
+ result.setdefault("conditionCode", "&&")
998
+ result.setdefault("rules", [])
999
+ result["calculate"] = props.get("calculate") or raw_conditions.get("calculate") or "condition"
1000
+ result["conditions"] = copy.deepcopy(raw_conditions)
1001
+ result["isDefault"] = bool(props.get("isDefault", raw_conditions.get("isDefault", False)))
1002
+ if "priority" in props:
1003
+ result["priority"] = props["priority"]
1004
+ elif "priority" in raw_conditions:
1005
+ result["priority"] = raw_conditions["priority"]
1006
+ description = props.get("description") or raw_conditions.get("description") or _condition_description(raw_conditions)
1007
+ if not description and result.get("calculate") == "all" and not result.get("rules"):
1008
+ description = "全部数据"
1009
+ if description:
1010
+ result["description"] = description
1011
+ return result
1012
+
1013
+
1014
+ def _build_add_data_rules(
1015
+ node_props: dict[str, Any],
1016
+ *,
1017
+ fields: list[dict[str, Any]] | None = None,
1018
+ form_title: str = "",
1019
+ ) -> dict[str, Any]:
1020
+ raw_add_rules = node_props.get("addDataRules") if isinstance(node_props.get("addDataRules"), dict) else {}
1021
+ child_list = copy.deepcopy(fields or raw_add_rules.get("inputs", {}).get("childList") or [])
1022
+ component_option_map = _component_option_map(child_list)
1023
+
1024
+ assignments = copy.deepcopy(node_props.get("assignments") or raw_add_rules.get("assignments") or [])
1025
+ rules = _rules_for_assignments(assignments, child_list, raw_add_rules.get("rules") or node_props.get("rules"))
1026
+
1027
+ inputs = copy.deepcopy(raw_add_rules.get("inputs") or node_props.get("inputs") or {})
1028
+ inputs.setdefault("childList", copy.deepcopy(child_list))
1029
+ inputs.setdefault("componentOptionMap", component_option_map)
1030
+
1031
+ description = raw_add_rules.get("description")
1032
+ if isinstance(description, dict):
1033
+ description = _i18n_text(description)
1034
+ if not description and form_title:
1035
+ description = f"在 [{form_title}] 中新增数据"
1036
+ if not description:
1037
+ description = node_props.get("description")
1038
+ if isinstance(description, dict):
1039
+ description = _i18n_text(description)
1040
+
1041
+ return {
1042
+ "formUuid": node_props.get("formUuid", raw_add_rules.get("formUuid", "")),
1043
+ "appType": node_props.get("appType", raw_add_rules.get("appType", "")),
1044
+ "subFormUuid": node_props.get("subFormUuid", raw_add_rules.get("subFormUuid", "")),
1045
+ "insertType": (
1046
+ node_props.get("insertType")
1047
+ or raw_add_rules.get("insertType")
1048
+ or node_props.get("originalType")
1049
+ or raw_add_rules.get("originalType")
1050
+ or "form"
1051
+ ),
1052
+ "originalType": node_props.get("originalType", raw_add_rules.get("originalType", "form")),
1053
+ "type": node_props.get("type", raw_add_rules.get("type", "single")),
1054
+ "sourceId": node_props.get("sourceId", raw_add_rules.get("sourceId", "")),
1055
+ "assignments": copy.deepcopy(raw_add_rules.get("assignments") or []),
1056
+ "inputs": inputs,
1057
+ "rules": rules,
1058
+ "description": description or None,
1059
+ }
1060
+
1061
+
1062
+ def _normalize_none_operation(value: Any) -> str:
1063
+ raw = str(value or "").strip()
1064
+ lowered = raw.lower()
1065
+ if lowered in {"add", "新增一条数据"} or raw == "ADD":
1066
+ return "add"
1067
+ if lowered in {"ignored", "ignore", "skip", "跳过当前节点"} or raw == "IGNORED":
1068
+ return "ignored"
1069
+ return lowered or "ignored"
1070
+
1071
+
1072
+ def _condition_rule_text(rule: Any) -> str:
1073
+ if not isinstance(rule, dict):
1074
+ return ""
1075
+ name = str(rule.get("name") or rule.get("label") or rule.get("id") or rule.get("column") or "")
1076
+ op = str(rule.get("op") or rule.get("opCode") or "")
1077
+ value = rule.get("ruleValue") if rule.get("ruleValue") not in (None, "") else rule.get("value")
1078
+ value_text = str(value or "")
1079
+ if name and op and value_text:
1080
+ return f"{name}{op}{value_text}"
1081
+ if name and op:
1082
+ return f"{name}{op}"
1083
+ return name or value_text
1084
+
1085
+
1086
+ def _condition_description_for_get_data(condition: Any) -> str:
1087
+ if not isinstance(condition, dict):
1088
+ return ""
1089
+ rules = condition.get("rules")
1090
+ if not isinstance(rules, list):
1091
+ return ""
1092
+ parts = [_condition_rule_text(rule) for rule in rules]
1093
+ return ",".join(part for part in parts if part)
1094
+
1095
+
1096
+ def _build_get_data_rules(
1097
+ node_props: dict[str, Any],
1098
+ *,
1099
+ form_title: str = "",
1100
+ ) -> tuple[dict[str, Any], str]:
1101
+ raw_get_data = node_props.get("getData") if isinstance(node_props.get("getData"), dict) else {}
1102
+ get_data = copy.deepcopy(raw_get_data or node_props)
1103
+ get_data.setdefault("type", node_props.get("type") or "single")
1104
+ get_data.setdefault("sourceId", node_props.get("sourceId") or "")
1105
+ get_data.setdefault("appType", node_props.get("appType") or "")
1106
+ get_data.setdefault("originalType", node_props.get("originalType") or "form")
1107
+ get_data.setdefault("subSourceId", node_props.get("subSourceId") or "")
1108
+ get_data.setdefault("filterType", node_props.get("filterType") or "condition")
1109
+ get_data.setdefault("quantity", str(node_props.get("quantity") or "1"))
1110
+ get_data.setdefault("assignments", copy.deepcopy(node_props.get("assignments") or []))
1111
+ get_data.setdefault("sort", copy.deepcopy(node_props.get("sort") or {"type": "none", "column": ""}))
1112
+
1113
+ description = get_data.get("description") or node_props.get("description")
1114
+ if isinstance(description, dict):
1115
+ description = _i18n_text(description)
1116
+ if not description:
1117
+ condition_text = _condition_description_for_get_data(get_data.get("condition"))
1118
+ if condition_text:
1119
+ description = condition_text
1120
+ elif form_title:
1121
+ quantity = get_data.get("quantity") or "1"
1122
+ description = f"在 [{form_title}] 中查询 {quantity} 条数据"
1123
+
1124
+ if description:
1125
+ get_data["description"] = description
1126
+ condition = get_data.get("condition")
1127
+ if isinstance(condition, dict):
1128
+ condition.setdefault("description", description)
1129
+ condition.setdefault("conditions", copy.deepcopy(condition))
1130
+ return get_data, str(description or "")
1131
+
1132
+
1133
+ def _find_data_source_form(
1134
+ node_props: dict[str, Any],
1135
+ *,
1136
+ nodes_by_id: dict[str, dict[str, Any]] | None = None,
1137
+ ) -> tuple[str, str]:
1138
+ app_type = str(node_props.get("appType") or "")
1139
+ direct_form = node_props.get("formUuid") or node_props.get("sourceFormUuid")
1140
+ if isinstance(direct_form, str) and direct_form:
1141
+ return app_type, direct_form
1142
+
1143
+ source_id = str(node_props.get("sourceId") or "")
1144
+ if source_id.startswith("FORM-"):
1145
+ return app_type, source_id
1146
+
1147
+ source_node = (nodes_by_id or {}).get(source_id)
1148
+ if isinstance(source_node, dict):
1149
+ props = source_node.get("props") if isinstance(source_node.get("props"), dict) else {}
1150
+ source_app = str(props.get("appType") or app_type)
1151
+ source_form = props.get("formUuid") or props.get("sourceId")
1152
+ if isinstance(source_form, str) and source_form.startswith("FORM-"):
1153
+ return source_app, source_form
1154
+ return app_type, ""
1155
+
1156
+
1157
+ def _build_update_data_rules(
1158
+ node_props: dict[str, Any],
1159
+ *,
1160
+ fields: list[dict[str, Any]] | None = None,
1161
+ form_title: str = "",
1162
+ ) -> tuple[dict[str, Any], str]:
1163
+ raw_update_rules = node_props.get("updateDataRules") if isinstance(node_props.get("updateDataRules"), dict) else {}
1164
+ rules = copy.deepcopy(raw_update_rules or node_props)
1165
+ child_list = copy.deepcopy(fields or raw_update_rules.get("inputs", {}).get("childList") or [])
1166
+ component_option_map = _component_option_map(child_list)
1167
+ assignments = copy.deepcopy(node_props.get("assignments") or raw_update_rules.get("assignments") or [])
1168
+
1169
+ rules.setdefault("type", node_props.get("type") or "node")
1170
+ rules.setdefault("sourceId", node_props.get("sourceId") or "")
1171
+ rules.setdefault("subSourceId", node_props.get("subSourceId") or "")
1172
+ rules.setdefault("condition", copy.deepcopy(node_props.get("condition") or {}))
1173
+ rules.setdefault("subCondition", copy.deepcopy(node_props.get("subCondition") or {}))
1174
+ rules["assignments"] = assignments
1175
+ display_rules = _rules_for_assignments(assignments, child_list, raw_update_rules.get("rules") or node_props.get("rules"))
1176
+ rules["rules"] = display_rules
1177
+ rules["dataRules"] = copy.deepcopy(
1178
+ raw_update_rules.get("dataRules")
1179
+ or node_props.get("dataRules")
1180
+ or display_rules
1181
+ )
1182
+ inputs = copy.deepcopy(raw_update_rules.get("inputs") or node_props.get("inputs") or {})
1183
+ inputs.setdefault("childList", copy.deepcopy(child_list))
1184
+ inputs.setdefault("componentOptionMap", component_option_map)
1185
+ rules["inputs"] = inputs
1186
+ rules.setdefault("rulesFilter", copy.deepcopy(node_props.get("rulesFilter") or []))
1187
+ rules.setdefault("tableRulesFilter", copy.deepcopy(node_props.get("tableRulesFilter") or []))
1188
+ rules["noneOperation"] = _normalize_none_operation(
1189
+ raw_update_rules.get("noneOperation") or node_props.get("noneOperation")
1190
+ )
1191
+
1192
+ description = rules.get("description") or node_props.get("description")
1193
+ if isinstance(description, dict):
1194
+ description = _i18n_text(description)
1195
+ if not description and form_title:
1196
+ description = f"更新 [{form_title}] 中的数据"
1197
+ if description:
1198
+ rules["description"] = description
1199
+ return rules, str(description or "")
1200
+
1201
+
1202
+ def _view_component_name_for_node(node: dict[str, Any]) -> str:
1203
+ node_type = str(node.get("type") or "")
1204
+ props = node.get("props") if isinstance(node.get("props"), dict) else {}
1205
+ if node_type == "dataRetrieve":
1206
+ get_data = props.get("getData") if isinstance(props.get("getData"), dict) else props
1207
+ retrieve_type = str(get_data.get("type") or "").lower()
1208
+ return "GetBatchDataNode" if retrieve_type in {"batch", "multiple", "multi"} else "GetSingleDataNode"
1209
+ if node_type == "CodeExecutor":
1210
+ if "groovy" in props or str(props.get("scriptType") or "").lower() == "groovy":
1211
+ return "GroovyNode"
1212
+ return "JavaScriptNode"
1213
+ return VIEW_COMPONENT_BY_TYPE.get(node_type, str(node.get("componentName") or node_type))
1214
+
1215
+
1216
+ def _view_props_for_node(
1217
+ node: dict[str, Any],
1218
+ *,
1219
+ data_create_metadata: dict[tuple[str, str], dict[str, Any]] | None = None,
1220
+ nodes_by_id: dict[str, dict[str, Any]] | None = None,
1221
+ ) -> dict[str, Any]:
1222
+ node_type = str(node.get("type") or "")
1223
+ component_name = _view_component_name_for_node(node)
1224
+ props = copy.deepcopy(node.get("props") or {})
1225
+ name = copy.deepcopy(node.get("name") or i18n_name(None, DEFAULT_NODE_NAMES.get(node_type, node_type)))
1226
+ if node.get("description") not in (None, ""):
1227
+ props.setdefault("description", copy.deepcopy(node["description"]))
1228
+
1229
+ view_props: dict[str, Any] = {
1230
+ "nodeName": component_name,
1231
+ "name": name,
1232
+ }
1233
+ if node.get("description") not in (None, ""):
1234
+ view_props["description"] = copy.deepcopy(node["description"])
1235
+
1236
+ if component_name == "AddDataNode":
1237
+ target_key = (str(props.get("appType") or ""), str(props.get("formUuid") or ""))
1238
+ metadata = (data_create_metadata or {}).get(target_key) or {}
1239
+ add_data_rules = _build_add_data_rules(
1240
+ props,
1241
+ fields=metadata.get("fields") or [],
1242
+ form_title=str(metadata.get("title") or ""),
1243
+ )
1244
+ view_props["addDataRules"] = add_data_rules
1245
+ elif component_name in {"GetSingleDataNode", "GetBatchDataNode"}:
1246
+ target_app, target_form = _find_data_source_form(props, nodes_by_id=nodes_by_id)
1247
+ metadata = (data_create_metadata or {}).get((target_app, target_form)) or {}
1248
+ get_data, description = _build_get_data_rules(
1249
+ props,
1250
+ form_title=str(metadata.get("title") or ""),
1251
+ )
1252
+ view_props["getData"] = get_data
1253
+ if description:
1254
+ view_props.setdefault("description", description)
1255
+ elif component_name == "UpdateDataNode":
1256
+ target_app, target_form = _find_data_source_form(props, nodes_by_id=nodes_by_id)
1257
+ metadata = (data_create_metadata or {}).get((target_app, target_form)) or {}
1258
+ update_data_rules, description = _build_update_data_rules(
1259
+ props,
1260
+ fields=metadata.get("fields") or [],
1261
+ form_title=str(metadata.get("title") or ""),
1262
+ )
1263
+ view_props["updateDataRules"] = update_data_rules
1264
+ if description:
1265
+ view_props.setdefault("description", description)
1266
+ elif component_name == "ApprovalNode":
1267
+ view_props["approverRules"] = _approver_rules_for_view(node, props)
1268
+ if props.get("actions") or props.get("appendActions"):
1269
+ view_props["actions"] = {
1270
+ "normalActions": copy.deepcopy(props.get("actions") or []),
1271
+ "appendActions": copy.deepcopy(props.get("appendActions") or []),
1272
+ }
1273
+ elif component_name == "OperatorNode":
1274
+ view_props["approverRules"] = _operator_rules_for_view(node, props)
1275
+ view_props["actions"] = _operator_actions_for_view(props)
1276
+ elif component_name == "SendMessageNode":
1277
+ send_message_rules, description = _send_message_rules_for_view(node, props)
1278
+ view_props["sendMessageRules"] = send_message_rules
1279
+ if description:
1280
+ view_props.setdefault("description", description)
1281
+ elif node_type == "condition":
1282
+ for key, value in props.items():
1283
+ view_props.setdefault(key, copy.deepcopy(value))
1284
+ view_props["conditions"] = _condition_rules_for_view(props)
1285
+ elif node_type == "route":
1286
+ for key, value in props.items():
1287
+ view_props.setdefault(key, copy.deepcopy(value))
1288
+ else:
1289
+ rule_key = VIEW_COMPONENT_RULE_KEY.get(component_name)
1290
+ if rule_key:
1291
+ view_props[rule_key] = copy.deepcopy(props.get(rule_key) or props)
1292
+
1293
+ view_props["title"] = copy.deepcopy(node.get("title") or name)
1294
+ return view_props
1295
+
1296
+
1297
+ def _view_child_for_node(
1298
+ node: dict[str, Any],
1299
+ *,
1300
+ data_create_metadata: dict[tuple[str, str], dict[str, Any]] | None = None,
1301
+ nodes_by_id: dict[str, dict[str, Any]] | None = None,
1302
+ ) -> dict[str, Any]:
1303
+ child = {
1304
+ "componentName": _view_component_name_for_node(node),
1305
+ "id": node.get("nodeId"),
1306
+ "props": _view_props_for_node(node, data_create_metadata=data_create_metadata, nodes_by_id=nodes_by_id),
1307
+ }
1308
+ nested = node.get("childNodes") or []
1309
+ if isinstance(nested, list) and nested:
1310
+ child["children"] = [
1311
+ _view_child_for_node(item, data_create_metadata=data_create_metadata, nodes_by_id=nodes_by_id)
1312
+ for item in nested
1313
+ ]
1314
+ return child
1315
+
1316
+
1317
+ def build_process_view_schema(
1318
+ flow: dict[str, Any],
1319
+ *,
1320
+ data_create_metadata: dict[tuple[str, str], dict[str, Any]] | None = None,
1321
+ ) -> dict[str, Any]:
1322
+ """Build the designer viewJson that matches a normalized process schema.
1323
+
1324
+ The backend processJson can execute dataCreate nodes with plain
1325
+ ``props.assignments``. The Yida designer, however, renders and re-saves
1326
+ AddDataNode from ``props.addDataRules.rules.rules`` plus target field
1327
+ metadata. This helper keeps both surfaces aligned.
1328
+ """
1329
+ validate_flow_schema(flow)
1330
+ nodes_by_id = {str(node.get("nodeId")): node for node in _walk_nodes(flow.get("nodes") or [])}
1331
+ children = []
1332
+ for node in flow.get("nodes") or []:
1333
+ children.append(_view_child_for_node(node, data_create_metadata=data_create_metadata, nodes_by_id=nodes_by_id))
1334
+
1335
+ return {
1336
+ "schema": {
1337
+ "componentName": "CanvasEngine",
1338
+ "id": "canvas",
1339
+ "props": {},
1340
+ "children": children,
1341
+ },
1342
+ "bindingForm": flow.get("props", {}).get("bindingForm"),
1343
+ "formulaRules": flow.get("formulaRules", []),
1344
+ "globalSetting": {
1345
+ "enableSignature": False,
1346
+ "stopAssociationRulesIfFailed": flow.get("props", {}).get("stopAssociationRulesIfFailed", False),
1347
+ "nodeMerge": False,
1348
+ "originatorMerge": False,
1349
+ "allNodeMerge": False,
1350
+ "behaviorList": [],
1351
+ "needOpenDigitalSignNodes": [],
1352
+ "approvalSummary": flow.get("approvalSummary", []),
1353
+ "noRecordRecall": flow.get("props", {}).get("noRecordRecall", False),
1354
+ "untimedRule": flow.get("props", {}).get("untimedRule", []),
1355
+ },
1356
+ }
1357
+
1358
+
1359
+ def _sync_view_top_level_defaults(view: dict[str, Any], flow: dict[str, Any]) -> None:
1360
+ props = flow.get("props") if isinstance(flow.get("props"), dict) else {}
1361
+ binding_form = props.get("bindingForm")
1362
+ if binding_form:
1363
+ view["bindingForm"] = binding_form
1364
+ view.setdefault("formulaRules", copy.deepcopy(flow.get("formulaRules") or []))
1365
+ view.setdefault("flowConfig", copy.deepcopy(flow.get("flowConfig") or {}))
1366
+
1367
+ global_setting = view.setdefault("globalSetting", {})
1368
+ if not isinstance(global_setting, dict):
1369
+ global_setting = {}
1370
+ view["globalSetting"] = global_setting
1371
+ global_setting.setdefault("enableSignature", False)
1372
+ global_setting.setdefault("stopAssociationRulesIfFailed", props.get("stopAssociationRulesIfFailed", False))
1373
+ global_setting.setdefault("nodeMerge", False)
1374
+ global_setting.setdefault("originatorMerge", False)
1375
+ global_setting.setdefault("allNodeMerge", False)
1376
+ global_setting.setdefault("behaviorList", [])
1377
+ global_setting.setdefault("needOpenDigitalSignNodes", [])
1378
+ global_setting.setdefault("approvalSummary", flow.get("approvalSummary", []))
1379
+ global_setting.setdefault("noRecordRecall", props.get("noRecordRecall", False))
1380
+ global_setting.setdefault("untimedRule", props.get("untimedRule", []))
1381
+
1382
+
1383
+ def _iter_view_children(children: list[dict[str, Any]]):
1384
+ for child in children:
1385
+ yield child
1386
+ nested = child.get("children") or []
1387
+ if isinstance(nested, list):
1388
+ yield from _iter_view_children(nested)
1389
+
1390
+
1391
+ def merge_process_view_schema(
1392
+ base_view: dict[str, Any],
1393
+ flow: dict[str, Any],
1394
+ *,
1395
+ data_create_metadata: dict[tuple[str, str], dict[str, Any]] | None = None,
1396
+ ) -> dict[str, Any]:
1397
+ """Patch a designer viewJson with normalized flow-node configuration.
1398
+
1399
+ Prefer this over building a view from scratch when a source process version
1400
+ already has a valid designer skeleton. It preserves designer-specific node
1401
+ ids and props, while refreshing advanced component settings such as
1402
+ AddDataNode's UI-visible addDataRules.
1403
+ """
1404
+ validate_flow_schema(flow)
1405
+ view = copy.deepcopy(base_view)
1406
+ schema = view.setdefault("schema", {})
1407
+ children = schema.setdefault("children", [])
1408
+ nodes_by_id = {str(node.get("nodeId")): node for node in _walk_nodes(flow.get("nodes") or [])}
1409
+ seen: set[str] = set()
1410
+
1411
+ def sync_children(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
1412
+ synced: list[dict[str, Any]] = []
1413
+ for child in items:
1414
+ node_id = str(child.get("id") or "")
1415
+ node = nodes_by_id.get(node_id)
1416
+ if node:
1417
+ fresh_child = _view_child_for_node(
1418
+ node,
1419
+ data_create_metadata=data_create_metadata,
1420
+ nodes_by_id=nodes_by_id,
1421
+ )
1422
+ component_name = fresh_child["componentName"]
1423
+ if (
1424
+ child.get("componentName") == component_name
1425
+ and str(node.get("type") or "") in {"apply", "finish"}
1426
+ ):
1427
+ seen.add(node_id)
1428
+ synced.append(child)
1429
+ continue
1430
+ child["componentName"] = component_name
1431
+ child["props"] = fresh_child["props"]
1432
+ if "children" in fresh_child:
1433
+ child["children"] = fresh_child["children"]
1434
+ else:
1435
+ child.pop("children", None)
1436
+ seen.add(node_id)
1437
+ synced.append(child)
1438
+ continue
1439
+ nested = child.get("children")
1440
+ if isinstance(nested, list):
1441
+ child["children"] = sync_children(nested)
1442
+ synced.append(child)
1443
+ return synced
1444
+
1445
+ schema["children"] = sync_children(children)
1446
+ children = schema.setdefault("children", [])
1447
+
1448
+ for child in _iter_view_children(children):
1449
+ node_id = str(child.get("id") or "")
1450
+ node = nodes_by_id.get(node_id)
1451
+ if not node:
1452
+ continue
1453
+ seen.add(node_id)
1454
+
1455
+ existing_top_level = {str(child.get("id") or ""): child for child in children if child.get("id")}
1456
+ ordered_children: list[dict[str, Any]] = []
1457
+ for child in children:
1458
+ node_id = str(child.get("id") or "")
1459
+ if child.get("componentName") == "ApplyNode" and node_id not in nodes_by_id:
1460
+ ordered_children.append(child)
1461
+
1462
+ for node in flow.get("nodes") or []:
1463
+ node_id = str(node.get("nodeId") or "")
1464
+ if str(node.get("type") or "") == "apply" and not existing_top_level.get(node_id):
1465
+ continue
1466
+ child = existing_top_level.get(node_id)
1467
+ if not child:
1468
+ child = _view_child_for_node(
1469
+ node,
1470
+ data_create_metadata=data_create_metadata,
1471
+ nodes_by_id=nodes_by_id,
1472
+ )
1473
+ ordered_children.append(child)
1474
+
1475
+ if ordered_children:
1476
+ schema["children"] = ordered_children
1477
+ _sync_view_top_level_defaults(view, flow)
1478
+ return view