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,947 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 考勤记录报表导出脚本 — 补卡/出差/外出/请假
4
+
5
+ 属于考勤报表导出体系,和 attendance_report_detail.py / attendance_report_monthly.py 平级。
6
+ Agent 负责意图判断和人员获取,本脚本自包含:数据查询 → 解析 → Excel 生成。
7
+
8
+ 数据链路:
9
+ 1. dws attendance approve list --users <ids> --types <type> --start --end
10
+ → 获取审批单摘要(含 originId = processInstanceId)
11
+ 2. dws oa approval detail --instance-id <originId>
12
+ → 获取审批单完整表单字段(extValue / detailList)
13
+ 3. 解析 formValueVOS 中的 DDHolidayField / extValue → 按天拆分行
14
+ 4. write_excel 输出
15
+
16
+ 用法:
17
+ python attendance_report_record.py --type leave --users <userId1,userId2> --start 2026-04-01 --end 2026-04-30
18
+ python attendance_report_record.py --type trip --users <userId1,userId2> --start 2026-04-01 --end 2026-04-30
19
+ python attendance_report_record.py --type out --users <userId1> --start 2026-05-01 --end 2026-05-31
20
+ python attendance_report_record.py --type patch --users <userId1> --start 2026-05-01 --end 2026-05-31
21
+
22
+ 支持类型: leave(请假), trip(出差), out(外出), patch(补卡)
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import argparse
28
+ import json
29
+ import sys
30
+ import os
31
+ from datetime import datetime
32
+ from typing import Any
33
+
34
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
35
+ from attendance_report_common import (
36
+ run_dws,
37
+ write_excel,
38
+ resolve_user_names,
39
+ resolve_user_info,
40
+ UserInfo,
41
+ log,
42
+ warn,
43
+ error,
44
+ DwsCallError,
45
+ DATE_FMT,
46
+ )
47
+
48
+ # ─────────────────────────────────────────────────────────────────────────────
49
+ # 常量
50
+ # ─────────────────────────────────────────────────────────────────────────────
51
+
52
+ SUPPORTED_TYPES = ("leave", "trip", "out", "patch")
53
+
54
+ COLUMNS: dict[str, list[str]] = {
55
+ "leave": ["姓名", "考勤组", "部门", "工号", "职位", "假期类型", "请假时间",
56
+ "请假时长(小时)", "请假时长(天)", "关联审批单", "审批单状态"],
57
+ "trip": ["姓名", "考勤组", "部门", "工号", "职位", "出差时间",
58
+ "出差时长", "出差单位", "关联审批单", "审批单状态"],
59
+ "out": ["姓名", "考勤组", "部门", "工号", "职位", "外出申请时间",
60
+ "外出时长(小时)", "外出时长(天)", "关联审批单", "审批单状态"],
61
+ "patch": ["姓名", "考勤组", "部门", "工号", "职位", "考勤日期", "考勤时间",
62
+ "原打卡时间", "原考勤状态", "补卡时间", "补卡结果", "关联审批单", "审批单状态"],
63
+ }
64
+
65
+ SHEET_NAMES: dict[str, str] = {
66
+ "leave": "请假记录",
67
+ "trip": "出差记录",
68
+ "out": "外出记录",
69
+ "patch": "补卡记录",
70
+ }
71
+
72
+ STATUS_MAP: dict[str, dict[str, str]] = {
73
+ "COMPLETED": {"agree": "审批通过", "refuse": "已拒绝"},
74
+ "RUNNING": {"": "审批中"},
75
+ "TERMINATED": {"": "已撤销"},
76
+ }
77
+
78
+ APPROVE_LIST_BATCH_SIZE = 50 # attendance approve list 单次最多用户数
79
+
80
+ # 审批详情页 URL 模板
81
+ # 内层:aflow 审批详情页
82
+ _AFLOW_URL_TEMPLATE = (
83
+ "https://aflow.dingtalk.com/dingtalk/mobile/homepage.htm"
84
+ "?corpid={corp_id}&dd_share=false&showmenu=true&back=native"
85
+ "#/approval?procInstId={instance_id}"
86
+ )
87
+ # 外层:dingtalk schema 协议,在钉钉客户端侧边面板打开
88
+ _DINGTALK_SCHEMA_TEMPLATE = (
89
+ "dingtalk://dingtalkclient/action/openapp"
90
+ "?corpid={corp_id}&container_type=slide_panel&app_id=-4"
91
+ "&&redirect_url={encoded_url}"
92
+ )
93
+
94
+
95
+ class HyperlinkCell:
96
+ """标记单元格为超链接:Excel 中显示 label 文本,点击跳转到 url。"""
97
+
98
+ __slots__ = ("label", "url")
99
+
100
+ def __init__(self, label: str, url: str):
101
+ self.label = label
102
+ self.url = url
103
+
104
+ def __str__(self) -> str:
105
+ return self.label
106
+
107
+
108
+ def build_approve_url(corp_id: str, instance_id: str) -> str:
109
+ """
110
+ 构建审批单跳转链接(dingtalk:// schema)。
111
+
112
+ 结构:外层 dingtalk schema 打开钉钉侧边面板,内部 redirect 到 aflow 审批详情页。
113
+ """
114
+ from urllib.parse import quote
115
+
116
+ inner_url = _AFLOW_URL_TEMPLATE.format(corp_id=corp_id, instance_id=instance_id)
117
+ encoded_url = quote(inner_url, safe="")
118
+ return _DINGTALK_SCHEMA_TEMPLATE.format(corp_id=corp_id, encoded_url=encoded_url)
119
+
120
+
121
+ def build_approve_cell(corp_id: str, instance_id: str, title: str = "") -> HyperlinkCell | str:
122
+ """
123
+ 构建"关联审批单"列的单元格值。
124
+
125
+ 如果有 corp_id 和 instance_id,返回 HyperlinkCell(Excel 中为可点击链接)。
126
+ 否则返回纯文本。
127
+ """
128
+ if not instance_id:
129
+ return ""
130
+ label = title or instance_id
131
+ if not corp_id:
132
+ return label
133
+ url = build_approve_url(corp_id, instance_id)
134
+ return HyperlinkCell(label=label, url=url)
135
+
136
+
137
+ # ─────────────────────────────────────────────────────────────────────────────
138
+ # 工具函数
139
+ # ─────────────────────────────────────────────────────────────────────────────
140
+
141
+ def normalize_am_pm(text: str) -> str:
142
+ """将时间文本中的 AM/PM 替换为 上午/下午。"""
143
+ return text.replace(" PM", " 下午").replace(" AM", " 上午")
144
+
145
+
146
+ def format_status(status: str, result: str) -> str:
147
+ """将 status + processInstanceResult 转为中文状态。"""
148
+ status_upper = (status or "").upper()
149
+ result_lower = (result or "").lower()
150
+ group = STATUS_MAP.get(status_upper, {})
151
+ return group.get(result_lower, group.get("", f"{status}/{result}"))
152
+
153
+
154
+ def ms_to_datetime(ms: int | float | None) -> datetime | None:
155
+ """毫秒时间戳转 datetime。"""
156
+ if not ms:
157
+ return None
158
+ try:
159
+ return datetime.fromtimestamp(int(ms) / 1000)
160
+ except (OSError, ValueError, OverflowError):
161
+ return None
162
+
163
+
164
+ def ms_to_time_str(ms: int | float | None) -> str:
165
+ """毫秒时间戳转 HH:MM。"""
166
+ dt = ms_to_datetime(ms)
167
+ return dt.strftime("%H:%M") if dt else ""
168
+
169
+
170
+ def ms_to_date_str(ms: int | float | None) -> str:
171
+ """毫秒时间戳转 YYYY-MM-DD。"""
172
+ dt = ms_to_datetime(ms)
173
+ return dt.strftime(DATE_FMT) if dt else ""
174
+
175
+
176
+ def format_day_type(detail: dict) -> str:
177
+ """从 detailList 单条判断日历类型。"""
178
+ day_type = detail.get("dayType", "")
179
+ is_rest = detail.get("isRest", False)
180
+ if day_type == "workDay" or (not is_rest and not day_type):
181
+ return "工作日"
182
+ if day_type == "restDay" or is_rest:
183
+ return "休息日"
184
+ if day_type == "holiday":
185
+ return "节假日"
186
+ return day_type or ("休息日" if is_rest else "工作日")
187
+
188
+
189
+ def format_class_time(detail: dict) -> str:
190
+ """从 detailList 单条提取上下班时间。"""
191
+ class_info = detail.get("classInfo", {})
192
+ sections = class_info.get("sections", []) if class_info else []
193
+ if not sections:
194
+ return "未排班"
195
+ section = sections[0]
196
+ start_time = ms_to_time_str(section.get("startTime"))
197
+ end_time = ms_to_time_str(section.get("endTime"))
198
+ if start_time and end_time:
199
+ return f"{start_time} ~ {end_time}"
200
+ return "未排班"
201
+
202
+
203
+ # ─────────────────────────────────────────────────────────────────────────────
204
+ # 数据查询
205
+ # ─────────────────────────────────────────────────────────────────────────────
206
+
207
+ # 钉钉接口把"外出"和"出差"都归类到 trip(bizType=2),out 类型查不到数据。
208
+ # 脚本通过 tagName 区分:tagName="出差" → trip,tagName="外出" → out。
209
+ _API_TYPE_MAP: dict[str, str] = {
210
+ "leave": "leave",
211
+ "trip": "trip",
212
+ "out": "trip", # 外出也用 trip 查询,再按 tagName 过滤
213
+ "patch": "patch",
214
+ }
215
+
216
+ _TAG_FILTER: dict[str, str | None] = {
217
+ "leave": None,
218
+ "trip": "出差",
219
+ "out": "外出",
220
+ "patch": None,
221
+ }
222
+
223
+
224
+ def fetch_approve_list(user_ids: list[str], record_type: str, start: str, end: str) -> list[dict]:
225
+ """
226
+ 分批调用 dws attendance approve list 获取审批单摘要。
227
+
228
+ 返回列表中每条包含: userId, tagName, duration, durationUnit, beginTime, endTime, originId。
229
+ 对于 out 类型,实际用 trip 查询接口,再按 tagName="外出" 过滤;
230
+ 对于 trip 类型,按 tagName="出差" 过滤(排除外出记录)。
231
+ """
232
+ api_type = _API_TYPE_MAP.get(record_type, record_type)
233
+ tag_filter = _TAG_FILTER.get(record_type)
234
+
235
+ all_records: list[dict] = []
236
+ for i in range(0, len(user_ids), APPROVE_LIST_BATCH_SIZE):
237
+ batch = user_ids[i:i + APPROVE_LIST_BATCH_SIZE]
238
+ users_str = ",".join(batch)
239
+ try:
240
+ result = run_dws([
241
+ "attendance", "approve", "list",
242
+ "--users", users_str,
243
+ "--types", api_type,
244
+ "--start", start,
245
+ "--end", end,
246
+ ])
247
+ records: list[dict] = []
248
+ if isinstance(result, list):
249
+ records = result
250
+ elif isinstance(result, dict):
251
+ records = result.get("approveList", result.get("list", []))
252
+ if not isinstance(records, list):
253
+ records = []
254
+ # 按 tagName 过滤
255
+ if tag_filter:
256
+ records = [r for r in records if r.get("tagName") == tag_filter]
257
+ all_records.extend(records)
258
+ except DwsCallError as e:
259
+ warn(f"查询审批列表失败(batch {i // APPROVE_LIST_BATCH_SIZE + 1}): {e}")
260
+ return all_records
261
+
262
+
263
+ def fetch_detail(instance_id: str) -> dict | None:
264
+ """调用 dws oa approval detail 获取审批单完整详情。"""
265
+ try:
266
+ result = run_dws([
267
+ "oa", "approval", "detail",
268
+ "--instance-id", instance_id,
269
+ ])
270
+ return result if isinstance(result, dict) else None
271
+ except DwsCallError as e:
272
+ warn(f"获取审批详情失败({instance_id[:20]}...): {e}")
273
+ return None
274
+
275
+
276
+ # ─────────────────────────────────────────────────────────────────────────────
277
+ # 解析器
278
+ # ─────────────────────────────────────────────────────────────────────────────
279
+
280
+ def find_holiday_field(form_values: list[dict]) -> dict | None:
281
+ """从 formValueVOS 中查找 DDHolidayField 组件。"""
282
+ for fv in form_values:
283
+ if fv.get("componentType") == "DDHolidayField":
284
+ return fv
285
+ return None
286
+
287
+
288
+ def parse_ext_value(field_data: dict) -> dict:
289
+ """解析字段的 extValue JSON 字符串。"""
290
+ ext_str = field_data.get("extValue") or ""
291
+ if not ext_str:
292
+ return {}
293
+ try:
294
+ return json.loads(ext_str)
295
+ except (json.JSONDecodeError, TypeError):
296
+ return {}
297
+
298
+
299
+ def parse_leave_detail(detail: dict, name_map: dict[str, str], *,
300
+ user_info_map: dict[str, "UserInfo"] | None = None,
301
+ group_map: dict[str, str] | None = None,
302
+ corp_id: str = "",
303
+ ) -> list[list[str]]:
304
+ """解析请假审批单。"""
305
+ form_values = detail.get("formValueVOS", [])
306
+ user_id = detail.get("originatorUserid", "")
307
+ dept_name = detail.get("originatorDeptName", "")
308
+ instance_id = detail.get("processInstanceId", "")
309
+ status = format_status(detail.get("status", ""), detail.get("processInstanceResult", ""))
310
+
311
+ # 用户基础信息
312
+ info = (user_info_map or {}).get(user_id)
313
+ user_name = info.name if info else name_map.get(user_id, user_id)
314
+ dept = info.dept_name if info and info.dept_name else dept_name
315
+ job_number = info.job_number if info else ""
316
+ title = info.title if info else ""
317
+ group_name = (group_map or {}).get(user_id, "")
318
+ approve_cell = build_approve_cell(corp_id, instance_id, f"{user_name}提交的请假审批单")
319
+
320
+ holiday_field = find_holiday_field(form_values)
321
+ if not holiday_field:
322
+ return [[user_name, group_name, dept, job_number, title,
323
+ "", "", "", "", approve_cell, status]]
324
+
325
+ # value: ["开始时间","结束时间",天数,"单位","假期类型","请假类型"]
326
+ value_str = holiday_field.get("value", "")
327
+ leave_type = ""
328
+ leave_time = ""
329
+ try:
330
+ value_arr = json.loads(value_str)
331
+ if isinstance(value_arr, list) and len(value_arr) >= 2:
332
+ leave_time = normalize_am_pm(f"{value_arr[0]} ~ {value_arr[1]}")
333
+ if len(value_arr) > 4:
334
+ leave_type = str(value_arr[4])
335
+ except (json.JSONDecodeError, TypeError):
336
+ leave_time = normalize_am_pm(value_str)
337
+
338
+ ext = parse_ext_value(holiday_field)
339
+ duration_day = str(ext.get("durationInDay", ""))
340
+ duration_hour = str(ext.get("durationInHour", ""))
341
+
342
+ return [[user_name, group_name, dept, job_number, title,
343
+ leave_type, leave_time, duration_hour, duration_day,
344
+ approve_cell, status]]
345
+
346
+
347
+ def _extract_time_duration_from_fields(form_values: list[dict]) -> tuple[str, str, str, str]:
348
+ """
349
+ 从独立表单字段中提取时间范围和时长。
350
+
351
+ 适用于外出/出差表单的非 DDHolidayField 结构:
352
+ - startTime (DDDateField) + finishTime (DDDateField) → 时间范围
353
+ - duration (NumberField) → extValue 中含 durationInDay / durationInHour
354
+
355
+ Returns: (time_range, duration_hour, duration_day, ext_from_duration)
356
+ """
357
+ start_time = ""
358
+ end_time = ""
359
+ duration_hour = ""
360
+ duration_day = ""
361
+
362
+ for fv in form_values:
363
+ biz_alias = (fv.get("bizAlias") or "").lower()
364
+ name = (fv.get("name") or "").lower()
365
+ value = fv.get("value") or ""
366
+
367
+ # 开始时间
368
+ if biz_alias in ("starttime", "start_time") or "开始时间" in name:
369
+ if value and not start_time:
370
+ start_time = value
371
+ # 结束时间
372
+ if biz_alias in ("finishtime", "finish_time", "endtime", "end_time") or "结束时间" in name:
373
+ if value and not end_time:
374
+ end_time = value
375
+ # 时长字段 — extValue 中有 durationInDay / durationInHour
376
+ if biz_alias == "duration" or "时长" in name:
377
+ ext = parse_ext_value(fv)
378
+ if ext:
379
+ duration_day = str(ext.get("durationInDay", ""))
380
+ duration_hour = str(ext.get("durationInHour", ""))
381
+
382
+ time_range = ""
383
+ if start_time and end_time:
384
+ time_range = f"{start_time} ~ {end_time}"
385
+ elif start_time:
386
+ time_range = start_time
387
+
388
+ return time_range, duration_hour, duration_day
389
+
390
+
391
+ def parse_out_detail(detail: dict, name_map: dict[str, str], *,
392
+ user_info_map: dict[str, "UserInfo"] | None = None,
393
+ group_map: dict[str, str] | None = None,
394
+ corp_id: str = "",
395
+ ) -> list[list[str]]:
396
+ """解析外出审批单。兼容 DDHolidayField 和独立字段两种表单结构。"""
397
+ form_values = detail.get("formValueVOS", [])
398
+ user_id = detail.get("originatorUserid", "")
399
+ dept_name = detail.get("originatorDeptName", "")
400
+ instance_id = detail.get("processInstanceId", "")
401
+ status = format_status(detail.get("status", ""), detail.get("processInstanceResult", ""))
402
+
403
+ # 用户基础信息
404
+ info = (user_info_map or {}).get(user_id)
405
+ user_name = info.name if info else name_map.get(user_id, user_id)
406
+ dept = info.dept_name if info and info.dept_name else dept_name
407
+ job_number = info.job_number if info else ""
408
+ title = info.title if info else ""
409
+ group_name = (group_map or {}).get(user_id, "")
410
+ approve_cell = build_approve_cell(corp_id, instance_id, f"{user_name}提交的外出审批单")
411
+
412
+ # 优先尝试 DDHolidayField
413
+ holiday_field = find_holiday_field(form_values)
414
+ if holiday_field:
415
+ value_str = holiday_field.get("value", "")
416
+ time_range = ""
417
+ try:
418
+ value_arr = json.loads(value_str)
419
+ if isinstance(value_arr, list) and len(value_arr) >= 2:
420
+ time_range = normalize_am_pm(f"{value_arr[0]} ~ {value_arr[1]}")
421
+ except (json.JSONDecodeError, TypeError):
422
+ time_range = normalize_am_pm(value_str)
423
+ ext = parse_ext_value(holiday_field)
424
+ duration_day = str(ext.get("durationInDay", ""))
425
+ duration_hour = str(ext.get("durationInHour", ""))
426
+ else:
427
+ # 回退: 从独立字段提取
428
+ time_range, duration_hour, duration_day = _extract_time_duration_from_fields(form_values)
429
+
430
+ return [[user_name, group_name, dept, job_number, title,
431
+ time_range, duration_hour, duration_day,
432
+ approve_cell, status]]
433
+
434
+
435
+ def parse_trip_from_approve_record(
436
+ record: dict,
437
+ name_map: dict[str, str],
438
+ *,
439
+ user_info_map: dict[str, "UserInfo"] | None = None,
440
+ group_map: dict[str, str] | None = None,
441
+ corp_id: str = "",
442
+ ) -> list[str]:
443
+ """
444
+ 直接从 attendance approve list 的记录中解析出差行。
445
+
446
+ 不依赖 oa approval detail(该接口对出差单存在 saNode 类型冲突 bug),
447
+ 仅使用 approve list 返回的 beginTime/endTime/duration/durationUnit/originId。
448
+ """
449
+ user_id = record.get("userId", "")
450
+ info = (user_info_map or {}).get(user_id)
451
+ user_name = info.name if info else name_map.get(user_id, user_id)
452
+ dept = info.dept_name if info else ""
453
+ job_number = info.job_number if info else ""
454
+ title = info.title if info else ""
455
+ group_name = (group_map or {}).get(user_id, "")
456
+
457
+ begin_ms = record.get("beginTime")
458
+ end_ms = record.get("endTime")
459
+ begin_str = ms_to_date_str(begin_ms) if begin_ms else ""
460
+ end_str = ms_to_date_str(end_ms) if end_ms else ""
461
+ time_range = f"{begin_str} ~ {end_str}" if begin_str and end_str else begin_str or end_str
462
+
463
+ duration = record.get("duration", "")
464
+ duration_unit = record.get("durationUnit", "DAY")
465
+ unit_str = "天" if duration_unit == "DAY" else "小时"
466
+
467
+ instance_id = record.get("originId", "")
468
+ effective_corp_id = corp_id or record.get("corpId", "")
469
+ approve_cell = build_approve_cell(effective_corp_id, instance_id, f"{user_name}提交的出差审批单")
470
+
471
+ # approve list 没有审批状态,有 gmtFinished 说明已完结,视为审批通过
472
+ status = "审批通过" if record.get("gmtFinished") else "审批中"
473
+
474
+ return [user_name, group_name, dept, job_number, title,
475
+ time_range, str(duration), unit_str, approve_cell, status]
476
+
477
+
478
+ def fetch_check_results(user_ids: list[str], start: str, end: str) -> dict[str, list[dict]]:
479
+ """
480
+ 批量查询打卡结果,返回 {userId: [records...]} 映射。
481
+
482
+ 每条 record 含: workDate, timeResult, planCheckTime, userCheckTime 等。
483
+ """
484
+ result_map: dict[str, list[dict]] = {}
485
+ batch_size = 50
486
+ for i in range(0, len(user_ids), batch_size):
487
+ batch = user_ids[i:i + batch_size]
488
+ try:
489
+ result = run_dws([
490
+ "attendance", "check", "result",
491
+ "--users", ",".join(batch),
492
+ "--start", start,
493
+ "--end", end,
494
+ ])
495
+ records = []
496
+ if isinstance(result, list):
497
+ records = result
498
+ elif isinstance(result, dict):
499
+ records = result.get("result", result.get("list", []))
500
+ if not isinstance(records, list):
501
+ records = []
502
+ for rec in records:
503
+ uid = rec.get("userId", "")
504
+ if uid:
505
+ result_map.setdefault(uid, []).append(rec)
506
+ except DwsCallError as e:
507
+ warn(f"查询打卡结果失败(batch {i // batch_size + 1}): {e}")
508
+ return result_map
509
+
510
+
511
+ def fetch_user_group_map(user_ids: list[str]) -> dict[str, str]:
512
+ """
513
+ 查询考勤组列表并建立 userId → 考勤组名称映射。
514
+
515
+ 流程:先 group search 拿到所有考勤组 ID+名称,
516
+ 再对有成员的考勤组调用 filtered-get --member 获取成员列表。
517
+ """
518
+ group_map: dict[str, str] = {}
519
+ user_id_set = set(user_ids)
520
+
521
+ try:
522
+ result = run_dws(["attendance", "group", "search"])
523
+ items: list[dict] = []
524
+ if isinstance(result, list):
525
+ items = result
526
+ elif isinstance(result, dict):
527
+ # 适配 {items: [...]} 或 {result: {items: [...]}}
528
+ inner = result.get("items", result.get("result", result))
529
+ if isinstance(inner, dict):
530
+ items = inner.get("items", [])
531
+ elif isinstance(inner, list):
532
+ items = inner
533
+
534
+ for g in items:
535
+ group_name = g.get("name", g.get("groupName", ""))
536
+ group_id = g.get("id", g.get("groupId", ""))
537
+ member_count = g.get("memberCount", 0)
538
+
539
+ if not group_id or not group_name or not member_count:
540
+ continue
541
+
542
+ # 调用 filtered-get 获取成员 userId 列表
543
+ try:
544
+ detail = run_dws([
545
+ "attendance", "group", "filtered-get",
546
+ "--group-id", str(group_id), "--member",
547
+ ])
548
+ member_users: list[str] = []
549
+ if isinstance(detail, dict):
550
+ member_users = detail.get("memberUsers", [])
551
+ if not isinstance(member_users, list):
552
+ member_users = []
553
+ for uid in member_users:
554
+ uid_str = str(uid)
555
+ if uid_str in user_id_set:
556
+ group_map[uid_str] = group_name
557
+ except DwsCallError:
558
+ pass
559
+
560
+ except DwsCallError as e:
561
+ warn(f"查询考勤组失败: {e}")
562
+ return group_map
563
+
564
+
565
+ CHECK_TIME_RESULT_MAP = {
566
+ "Normal": "正常",
567
+ "Late": "迟到",
568
+ "Early": "早退",
569
+ "Absenteeism": "旷工",
570
+ "NotSigned": "未打卡",
571
+ "SeriousLate": "严重迟到",
572
+ }
573
+
574
+
575
+ def parse_patch_detail(detail: dict, name_map: dict[str, str], *,
576
+ user_info_map: dict[str, "UserInfo"] | None = None,
577
+ group_map: dict[str, str] | None = None,
578
+ check_result_map: dict[str, list[dict]] | None = None,
579
+ corp_id: str = "",
580
+ ) -> list[list[str]]:
581
+ """解析补卡审批单,输出完整列。"""
582
+ form_values = detail.get("formValueVOS", [])
583
+ user_id = detail.get("originatorUserid", "")
584
+ dept_name = detail.get("originatorDeptName", "")
585
+ instance_id = detail.get("processInstanceId", "")
586
+ status = format_status(detail.get("status", ""), detail.get("processInstanceResult", ""))
587
+
588
+ # 用户基础信息
589
+ info = (user_info_map or {}).get(user_id)
590
+ user_name = info.name if info else name_map.get(user_id, user_id)
591
+ dept = info.dept_name if info and info.dept_name else dept_name
592
+ job_number = info.job_number if info else ""
593
+ title = info.title if info else ""
594
+ group_name = (group_map or {}).get(user_id, "")
595
+ approve_cell = build_approve_cell(corp_id, instance_id, f"{user_name}提交的补卡审批单")
596
+
597
+ # 从表单解析补卡时间和原因
598
+ patch_time = ""
599
+ patch_reason = ""
600
+ work_date = ""
601
+ check_time_str = ""
602
+ ext_data: dict = {}
603
+
604
+ for fv in form_values:
605
+ comp_type = fv.get("componentType", "") or ""
606
+ biz_alias = (fv.get("bizAlias") or "").lower()
607
+ name = fv.get("name") or ""
608
+ value = fv.get("value") or ""
609
+
610
+ if comp_type == "DDDateField" or "checktime" in biz_alias or "补卡时间" in name:
611
+ if value and not patch_time:
612
+ patch_time = value
613
+ # 解析 extValue 获取考勤日期等
614
+ ext = parse_ext_value(fv)
615
+ if ext and not ext_data:
616
+ ext_data = ext
617
+ if "reason" in biz_alias or "原因" in name or "事由" in name or "理由" in name:
618
+ if value and not patch_reason:
619
+ patch_reason = value
620
+
621
+ # 从 extValue 提取考勤日期、考勤时间、原考勤状态
622
+ plan_tip = ""
623
+ plan_text = ""
624
+ if ext_data:
625
+ work_date_ms = ext_data.get("workDate")
626
+ if work_date_ms:
627
+ work_date = ms_to_date_str(work_date_ms)
628
+ plan_tip = ext_data.get("planTip", "")
629
+ plan_text = ext_data.get("planText", "")
630
+
631
+ # 从 planText / planTip 提取考勤时间(目标格式:YYYY-MM-DD HH:MM)
632
+ # planText 格式: "2026-04-25,星期六,195固定班次,上班时间09:00"
633
+ # planTip 格式: "周六上班(04.25 09:00) 缺卡" / "周一上班(04.27 09:00) 缺卡"
634
+ import re
635
+ plan_time_hhmm = ""
636
+
637
+ # 优先从 planTip 提取(更可靠,含具体日期和时间)
638
+ # planTip 格式: "周三下班(03.05 01:00) 缺卡"
639
+ plan_date_from_tip = "" # MM.DD → 用于跨日场景
640
+ if plan_tip:
641
+ # 匹配 "(MM.DD HH:MM)" 格式
642
+ tip_match = re.search(r"\((\d{2})\.(\d{2})\s+(\d{2}:\d{2})\)", plan_tip)
643
+ if tip_match:
644
+ plan_date_from_tip = f"{tip_match.group(1)}-{tip_match.group(2)}" # "03-05"
645
+ plan_time_hhmm = tip_match.group(3)
646
+
647
+ # 回退:从 planText 中提取
648
+ if not plan_time_hhmm and plan_text:
649
+ # 匹配 "上班时间HH:MM" 或 "下班时间HH:MM" 或 "时间HH:MM"
650
+ time_match = re.search(r"时间(\d{2}:\d{2})", plan_text)
651
+ if time_match:
652
+ plan_time_hhmm = time_match.group(1)
653
+
654
+ # 最后回退:任意 HH:MM 格式
655
+ if not plan_time_hhmm:
656
+ for source in (plan_tip, plan_text):
657
+ if source:
658
+ fallback_match = re.search(r"(\d{2}:\d{2})", source)
659
+ if fallback_match:
660
+ plan_time_hhmm = fallback_match.group(1)
661
+ break
662
+
663
+ # 拼接考勤时间:优先使用 planTip 中解析的完整日期(处理跨日班次)
664
+ if plan_date_from_tip and plan_time_hhmm and work_date:
665
+ # 用 work_date 的年份 + planTip 中的 MM-DD + HH:MM
666
+ year = work_date[:4]
667
+ check_time_str = f"{year}-{plan_date_from_tip} {plan_time_hhmm}"
668
+ elif work_date and plan_time_hhmm:
669
+ check_time_str = f"{work_date} {plan_time_hhmm}"
670
+ elif plan_time_hhmm:
671
+ check_time_str = plan_time_hhmm
672
+ elif plan_text:
673
+ check_time_str = plan_text
674
+ elif plan_tip:
675
+ check_time_str = plan_tip
676
+
677
+ # 如果 work_date 为空,从 patch_time 中提取日期
678
+ if not work_date and patch_time:
679
+ work_date = patch_time[:10] if len(patch_time) >= 10 else ""
680
+
681
+ # 从 planTip / planText 提取原考勤状态
682
+ # planTip 格式: "周六上班(04.25 09:00) 缺卡" / "Thursday ( 04.23 ) Adjust"
683
+ # planText 格式: "2026-04-25,星期六,195固定班次,上班时间09:00" 或 "周一上班(04.27 09:00) 缺卡"
684
+ original_check_time = ""
685
+ original_status = ""
686
+
687
+ tip_status_map = {
688
+ "缺卡": "缺卡", "未打卡": "未打卡",
689
+ "迟到": "迟到", "早退": "早退",
690
+ "旷工": "旷工", "正常": "正常",
691
+ "NotSigned": "未打卡", "Adjust": "已调整",
692
+ }
693
+ # 优先从 planTip 提取,回退到 planText
694
+ for source in (plan_tip, plan_text):
695
+ if source:
696
+ for keyword, label in tip_status_map.items():
697
+ if keyword in source:
698
+ original_status = label
699
+ break
700
+ if original_status:
701
+ break
702
+
703
+ # 回退: 尝试从 check result 接口获取(如果有数据)
704
+ if check_result_map and user_id in check_result_map:
705
+ for rec in check_result_map[user_id]:
706
+ rec_date = rec.get("workDate", "")
707
+ if isinstance(rec_date, (int, float)):
708
+ rec_date = ms_to_date_str(rec_date)
709
+ if rec_date == work_date:
710
+ user_check_ms = rec.get("userCheckTime")
711
+ if user_check_ms:
712
+ dt = ms_to_datetime(user_check_ms)
713
+ original_check_time = dt.strftime("%Y-%m-%d %H:%M") if dt else ""
714
+ time_result = rec.get("timeResult", "")
715
+ if time_result:
716
+ original_status = CHECK_TIME_RESULT_MAP.get(time_result, time_result)
717
+ break
718
+
719
+ # 补卡结果:审批通过 → 补卡成功
720
+ patch_result = ""
721
+ if status == "审批通过":
722
+ patch_result = "补卡成功"
723
+ elif status == "已拒绝":
724
+ patch_result = "补卡失败"
725
+ elif status == "审批中":
726
+ patch_result = "待审批"
727
+ elif status == "已撤销":
728
+ patch_result = "已撤销"
729
+
730
+ return [[user_name, group_name, dept, job_number, title, work_date, check_time_str,
731
+ original_check_time, original_status, patch_time, patch_result,
732
+ approve_cell, status]]
733
+
734
+
735
+ PARSERS = {
736
+ "leave": parse_leave_detail,
737
+ "out": parse_out_detail,
738
+ "patch": parse_patch_detail,
739
+ }
740
+
741
+ # 需要额外用户信息(考勤组/工号/职位)的类型
742
+ _TYPES_NEED_USER_INFO = {"leave", "out", "patch", "trip"}
743
+
744
+
745
+ # ─────────────────────────────────────────────────────────────────────────────
746
+ # 主流程
747
+ # ─────────────────────────────────────────────────────────────────────────────
748
+
749
+ def parse_args() -> argparse.Namespace:
750
+ parser = argparse.ArgumentParser(
751
+ description="考勤记录报表导出(补卡/出差/外出/请假)",
752
+ formatter_class=argparse.RawDescriptionHelpFormatter,
753
+ )
754
+ parser.add_argument("--type", required=True, choices=SUPPORTED_TYPES,
755
+ help="记录类型: leave(请假)/trip(出差)/out(外出)/patch(补卡)")
756
+ parser.add_argument("--users", required=True,
757
+ help="用户 ID 列表,逗号分隔(由 Agent 从人员获取阶段提供)")
758
+ parser.add_argument("--start", required=True,
759
+ help="开始日期 YYYY-MM-DD")
760
+ parser.add_argument("--end", required=True,
761
+ help="结束日期 YYYY-MM-DD")
762
+ parser.add_argument("--out", default="",
763
+ help="输出文件路径(不传则自动生成)")
764
+ return parser.parse_args()
765
+
766
+
767
+ def main() -> None:
768
+ args = parse_args()
769
+ record_type: str = args.type
770
+ user_ids = [u.strip() for u in args.users.split(",") if u.strip()]
771
+ start_date: str = args.start
772
+ end_date: str = args.end
773
+
774
+ if not user_ids:
775
+ error("--users 不能为空")
776
+ sys.exit(1)
777
+
778
+ try:
779
+ datetime.strptime(start_date, DATE_FMT)
780
+ datetime.strptime(end_date, DATE_FMT)
781
+ except ValueError:
782
+ error("日期格式错误,请使用 YYYY-MM-DD")
783
+ sys.exit(1)
784
+
785
+ sheet_name = SHEET_NAMES[record_type]
786
+ log(f"开始导出{sheet_name}:{len(user_ids)} 人,{start_date} ~ {end_date}")
787
+
788
+ # ── Step 1: 获取审批单列表 ──
789
+ log("步骤 1/4:查询审批单列表...")
790
+ approve_records = fetch_approve_list(user_ids, record_type, start_date, end_date)
791
+ log(f" 获取到 {len(approve_records)} 条审批记录")
792
+
793
+ if not approve_records:
794
+ log("未查询到任何记录")
795
+ print(f"{sheet_name}:0 条记录,无需生成文件")
796
+ sys.exit(0)
797
+
798
+ # 从 approve list 记录中提取 corpId(用于构建审批单跳转链接)
799
+ corp_id = ""
800
+ for r in approve_records:
801
+ if r.get("corpId"):
802
+ corp_id = r["corpId"]
803
+ break
804
+
805
+ # ── Step 2: 去重提取 instanceId ──
806
+ instance_ids = list(dict.fromkeys(
807
+ r.get("originId", "") for r in approve_records if r.get("originId")
808
+ ))
809
+ log(f"步骤 2/4:共 {len(instance_ids)} 个审批实例")
810
+
811
+ # ── Step 3: 解析用户信息 ──
812
+ log("步骤 3/4:解析用户信息...")
813
+ name_map = resolve_user_names(user_ids)
814
+
815
+ user_info_map: dict[str, UserInfo] | None = None
816
+ group_map: dict[str, str] | None = None
817
+ check_result_map: dict[str, list[dict]] | None = None
818
+
819
+ if record_type in _TYPES_NEED_USER_INFO:
820
+ log(" 获取用户完整信息(工号/职位)...")
821
+ user_info_map = resolve_user_info(user_ids)
822
+ log(" 查询考勤组映射...")
823
+ group_map = fetch_user_group_map(user_ids)
824
+
825
+ if record_type == "patch":
826
+ log(" 查询原打卡结果...")
827
+ check_result_map = fetch_check_results(user_ids, start_date, end_date)
828
+
829
+ all_rows: list[list[str]] = []
830
+
831
+ if record_type == "trip":
832
+ # 出差记录直接从 approve list 数据生成,不调用 oa approval detail
833
+ # (oa approval detail 对出差单存在 saNode result 字段类型冲突 bug)
834
+ log("步骤 4/4:从审批列表解析出差记录...")
835
+ for record in approve_records:
836
+ row = parse_trip_from_approve_record(
837
+ record, name_map,
838
+ user_info_map=user_info_map,
839
+ group_map=group_map,
840
+ corp_id=corp_id,
841
+ )
842
+ all_rows.append(row)
843
+ else:
844
+ log("步骤 4/4:查询审批详情并解析...")
845
+ for idx, instance_id in enumerate(instance_ids):
846
+ if (idx + 1) % 10 == 0:
847
+ log(f" 进度: {idx + 1}/{len(instance_ids)}")
848
+
849
+ detail = fetch_detail(instance_id)
850
+ if not detail:
851
+ continue
852
+
853
+ # 补充新发现的用户
854
+ originator = detail.get("originatorUserid", "")
855
+ if originator and originator not in name_map:
856
+ extra = resolve_user_names([originator])
857
+ name_map.update(extra)
858
+ if originator and user_info_map and originator not in user_info_map:
859
+ extra_info = resolve_user_info([originator])
860
+ user_info_map.update(extra_info)
861
+
862
+ if record_type == "patch":
863
+ rows = parse_patch_detail(
864
+ detail, name_map,
865
+ user_info_map=user_info_map,
866
+ group_map=group_map,
867
+ check_result_map=check_result_map,
868
+ corp_id=corp_id,
869
+ )
870
+ elif record_type == "leave":
871
+ rows = parse_leave_detail(
872
+ detail, name_map,
873
+ user_info_map=user_info_map,
874
+ group_map=group_map,
875
+ corp_id=corp_id,
876
+ )
877
+ elif record_type == "out":
878
+ rows = parse_out_detail(
879
+ detail, name_map,
880
+ user_info_map=user_info_map,
881
+ group_map=group_map,
882
+ corp_id=corp_id,
883
+ )
884
+ else:
885
+ rows = PARSERS[record_type](detail, name_map)
886
+ all_rows.extend(rows)
887
+
888
+ log(f" 解析完成,共 {len(all_rows)} 行")
889
+
890
+ if not all_rows:
891
+ log("无有效数据行")
892
+ print(f"{sheet_name}:解析后 0 行有效数据,无需生成文件")
893
+ sys.exit(0)
894
+
895
+ # ── 写入 Excel ──
896
+ out_path = args.out or f"attendance_report_record_{record_type}_{start_date}_{end_date}.xlsx"
897
+ headers = COLUMNS[record_type]
898
+ title = f"{sheet_name} 统计日期:{start_date} 至 {end_date}"
899
+ subtitle = f"报表生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}"
900
+
901
+ # 将 HyperlinkCell 转为纯文本供 write_excel 写入,之后再补超链接
902
+ plain_rows = []
903
+ hyperlink_cells: list[tuple[int, int, str]] = [] # (row_offset, col_idx, url)
904
+ for row_offset, row in enumerate(all_rows):
905
+ plain_row = []
906
+ for col_idx, cell in enumerate(row):
907
+ if isinstance(cell, HyperlinkCell):
908
+ plain_row.append(cell.label)
909
+ hyperlink_cells.append((row_offset, col_idx, cell.url))
910
+ else:
911
+ plain_row.append(cell)
912
+ plain_rows.append(plain_row)
913
+
914
+ write_excel(
915
+ out_path,
916
+ headers,
917
+ plain_rows,
918
+ sheet_name=sheet_name,
919
+ title=title,
920
+ subtitle=subtitle,
921
+ )
922
+
923
+ # 补充超链接
924
+ if hyperlink_cells:
925
+ from openpyxl import load_workbook
926
+ from openpyxl.styles import Font
927
+
928
+ wb = load_workbook(out_path)
929
+ ws = wb.active
930
+ # 计算标题行偏移:title + subtitle + header
931
+ title_row_count = (1 if title else 0) + (1 if subtitle else 0)
932
+ first_data_row = title_row_count + 2 # +1 for header, +1 for 1-indexed
933
+
934
+ link_font = Font(color="0563C1", underline="single")
935
+ for row_offset, col_idx, url in hyperlink_cells:
936
+ cell = ws.cell(row=first_data_row + row_offset, column=col_idx + 1)
937
+ cell.hyperlink = url
938
+ cell.font = link_font
939
+ wb.save(out_path)
940
+
941
+ abs_path = os.path.abspath(out_path)
942
+ log(f"✅ 导出完成: {abs_path}")
943
+ print(f"{sheet_name}导出完成:{abs_path}({len(all_rows)} 行,{len(instance_ids)} 个审批单)")
944
+
945
+
946
+ if __name__ == "__main__":
947
+ main()