weaver-work-cli 0.1.28 → 0.1.31
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.
- package/dist/chunks/archive-U766FTFH.js +2537 -0
- package/dist/chunks/archive-oa-DMWEKX3L.js +1317 -0
- package/dist/chunks/asset-TCR7VDEV.js +2109 -0
- package/dist/chunks/attachment-content-AQNX5N3S.js +23 -0
- package/dist/chunks/blog-EVZWGDVM.js +2157 -0
- package/dist/chunks/caizhilian-JOWS6SL5.js +10087 -0
- package/dist/chunks/calendar-5WNCV66Q.js +1195 -0
- package/dist/chunks/chunk-2DUV44UX.js +15606 -0
- package/dist/chunks/chunk-324YPTC2.js +1607 -0
- package/dist/chunks/chunk-65QMVWHS.js +231 -0
- package/dist/chunks/chunk-6V7ZLF34.js +387 -0
- package/dist/chunks/chunk-AC7KJWC5.js +358 -0
- package/dist/chunks/chunk-AJ62BGAW.js +29 -0
- package/dist/chunks/chunk-BUSC6UAE.js +43 -0
- package/dist/chunks/chunk-CVZ2JOSW.js +62 -0
- package/dist/chunks/chunk-D4LTJSJD.js +477 -0
- package/dist/chunks/chunk-EG6ZU444.js +654 -0
- package/dist/chunks/chunk-EZALDOQH.js +383 -0
- package/dist/chunks/chunk-FMPJJYYI.js +106 -0
- package/dist/chunks/chunk-G2JSIUWF.js +769 -0
- package/dist/chunks/chunk-GCG2NMWR.js +703 -0
- package/dist/chunks/chunk-GPMFD4RD.js +75 -0
- package/dist/chunks/chunk-H3WDBYJW.js +508 -0
- package/dist/chunks/chunk-H6XSTSLM.js +2357 -0
- package/dist/chunks/chunk-J7RJTZSV.js +5556 -0
- package/dist/chunks/chunk-LUSPKZKY.js +53 -0
- package/dist/chunks/chunk-NIAJ5ZCG.js +73 -0
- package/dist/chunks/chunk-NOODWMCL.js +584 -0
- package/dist/chunks/chunk-OOUTBLZ4.js +51 -0
- package/dist/chunks/chunk-P26ZWSKU.js +73 -0
- package/dist/chunks/chunk-QWY6PGL6.js +3472 -0
- package/dist/chunks/chunk-R2IJAK7T.js +464 -0
- package/dist/chunks/chunk-R7O5PUWO.js +205 -0
- package/dist/chunks/chunk-RCGYCOMG.js +6962 -0
- package/dist/chunks/chunk-VZTXRG6X.js +403 -0
- package/dist/chunks/chunk-WHD4XMRP.js +161 -0
- package/dist/chunks/chunk-YLCYX7LE.js +371 -0
- package/dist/chunks/chunk-ZEDWSH32.js +68 -0
- package/dist/chunks/continuation-2CKSBLA6.js +11 -0
- package/dist/chunks/continuation-HZIZRZBX.js +13 -0
- package/dist/chunks/continuation-NLECEJPX.js +11 -0
- package/dist/chunks/ebuilder-form-NJ4YK6CC.js +2276 -0
- package/dist/chunks/ehr-MMYN36VA.js +5241 -0
- package/dist/chunks/employee-SAK2QYXX.js +11 -0
- package/dist/chunks/esb-HIWHOU7Q.js +141 -0
- package/dist/chunks/fna-KBACQTA5.js +1444 -0
- package/dist/chunks/host-3OHS5QUX.js +21 -0
- package/dist/chunks/host-IG6KEH5Q.js +19 -0
- package/dist/chunks/host-KQL5N4HO.js +23 -0
- package/dist/chunks/host-LLSZQCWR.js +17 -0
- package/dist/chunks/hrm-LYEC67ZQ.js +244 -0
- package/dist/chunks/im-RN2UKAEU.js +252 -0
- package/dist/chunks/invoice-EEPHLAF6.js +5179 -0
- package/dist/chunks/jiuchuanhui-WVVZYSJE.js +1940 -0
- package/dist/chunks/lazy-dispatch-MDOFFL2S.js +28 -0
- package/dist/chunks/mail-LNQLTF6L.js +2695 -0
- package/dist/chunks/meeting-XSD3BDVQ.js +504 -0
- package/dist/chunks/msg-parse-4GSQ2WTO.js +41 -0
- package/dist/chunks/okr-TMWTIU2K.js +2482 -0
- package/dist/chunks/plan-A2FSFA7T.js +2025 -0
- package/dist/chunks/project-T26QLBQF.js +1361 -0
- package/dist/chunks/registry-2VLUAESA.js +14 -0
- package/dist/chunks/registry-JCFEYXOE.js +18 -0
- package/dist/chunks/registry-NYPYMZXG.js +16 -0
- package/dist/chunks/registry-Q7OQJ3SU.js +19 -0
- package/dist/chunks/registry-ZJMJVWFA.js +19 -0
- package/dist/chunks/room-recommend-UR4ZDZS6.js +20 -0
- package/dist/chunks/schedule-5L2PM4M6.js +24 -0
- package/dist/chunks/state-Y2XBP6YE.js +15 -0
- package/dist/chunks/workflow-TR6PG2SL.js +245 -0
- package/dist/cmd/argv.js +45 -0
- package/dist/cmd/docs/index.js +3 -3
- package/dist/cmd/lazy-dispatch.js +20 -0
- package/dist/cmd/root.js +2 -4
- package/dist/cmd/setup/index.js +2 -2
- package/dist/cmd/skills/index.js +2 -2
- package/dist/core/package-paths.js +47 -0
- package/dist/index.js +163 -7
- package/dist/internal/e10/auth/xiaoe.js +3 -3
- package/dist/internal/e10/request-runtime.js +18 -62
- package/dist/internal/skills/detector.js +2 -2
- package/dist/shortcuts/contract/data/performance-catalog.js +1306 -0
- package/dist/shortcuts/contract/host.js +91 -35
- package/dist/shortcuts/contract/index.js +213 -0
- package/dist/shortcuts/contract/manifest.js +928 -0
- package/dist/shortcuts/contract/ooxml/docx.js +295 -0
- package/dist/shortcuts/contract/ooxml/xlsx.js +221 -0
- package/dist/shortcuts/contract/ooxml/zip.js +158 -0
- package/dist/shortcuts/contract/operations/assistant.js +1156 -0
- package/dist/shortcuts/contract/operations/clause-library.js +522 -0
- package/dist/shortcuts/contract/operations/compare.js +193 -0
- package/dist/shortcuts/contract/operations/credit-page.js +34 -0
- package/dist/shortcuts/contract/operations/flow.js +98 -0
- package/dist/shortcuts/contract/operations/page.js +66 -0
- package/dist/shortcuts/contract/operations/performance.js +733 -0
- package/dist/shortcuts/contract/operations/quote.js +557 -0
- package/dist/shortcuts/contract/operations/recommend.js +78 -0
- package/dist/shortcuts/contract/operations/registry.js +18 -0
- package/dist/shortcuts/contract/operations/review.js +83 -0
- package/dist/shortcuts/contract/operations/shared.js +37 -0
- package/dist/shortcuts/contract/operations/upload.js +108 -0
- package/dist/shortcuts/contract/operations/write.js +3 -2
- package/dist/shortcuts/index.js +140 -40
- package/dist/shortcuts/invoice/host.js +2 -1
- package/dist/shortcuts/invoice/manifest.js +36 -12
- package/dist/shortcuts/invoice/operations/issuing-qys.js +132 -36
- package/dist/shortcuts/invoice/operations/reim-auto.js +9 -0
- package/dist/shortcuts/invoice/operations/reim.js +81 -5
- package/dist/shortcuts/invoice/operations/shared.js +12 -5
- package/dist/shortcuts/meeting/host.js +45 -3
- package/dist/shortcuts/meeting/invitation/docx.js +442 -0
- package/dist/shortcuts/meeting/operations/attachment-content.js +418 -0
- package/dist/shortcuts/meeting/operations/attachment.js +442 -0
- package/dist/shortcuts/meeting/operations/eb/cache.js +57 -0
- package/dist/shortcuts/meeting/operations/eb/catalog.js +343 -28
- package/dist/shortcuts/meeting/operations/eb/conflict.js +372 -11
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +710 -0
- package/dist/shortcuts/meeting/operations/eb/decision.js +47 -1
- package/dist/shortcuts/meeting/operations/eb/factory.js +129 -11
- package/dist/shortcuts/meeting/operations/eb/index.js +122 -2
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +378 -0
- package/dist/shortcuts/meeting/operations/eb/room-summary.js +84 -0
- package/dist/shortcuts/meeting/operations/eb/schedule.js +450 -0
- package/dist/shortcuts/meeting/operations/from-group-chat.js +576 -0
- package/dist/shortcuts/meeting/operations/invitation.js +458 -0
- package/dist/shortcuts/meeting/operations/minute-content.js +266 -0
- package/dist/shortcuts/meeting/operations/pipeline.js +489 -0
- package/dist/shortcuts/meeting/operations/qrcode.js +131 -0
- package/dist/shortcuts/meeting/operations/receipts.js +1 -1
- package/dist/shortcuts/meeting/operations/registry.js +18 -0
- package/dist/shortcuts/meeting/operations/summary.js +276 -0
- package/dist/shortcuts/meeting/operations/task-notify.js +325 -0
- package/dist/shortcuts/meeting/operations.js +42 -1
- package/dist/shortcuts/meeting/qr/encoder.js +427 -0
- package/dist/shortcuts/meeting/qr/render.js +103 -0
- package/dist/shortcuts/workflow/index.js +3 -0
- package/dist/shortcuts/workflow/operations/form.js +17 -5
- package/dist/shortcuts/workflow/operations/read.js +13 -6
- package/dist/shortcuts/workflow/operations/registry.js +2 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +331 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +133 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +326 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +113 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +259 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoListRows.js +64 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoOutputPlanner.js +28 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.aggregate.js +70 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +267 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +392 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +165 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +448 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +348 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +222 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +66 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +502 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +540 -0
- package/docs/_catalog.md +1 -0
- package/docs/contract.md +39 -4
- package/docs/invoice.md +7 -7
- package/docs/todo-smart-convert/README.md +171 -0
- package/docs/todo-smart-convert/data-contract.md +236 -0
- package/docs/todo-smart-convert/mock/ai-todo-query-target.json +209 -0
- package/docs/todo-smart-convert/subgroup-rules.md +245 -0
- package/package.json +3 -2
- package/scripts/bundle.mjs +63 -0
- package/scripts/package-skill.mjs +1 -1
- package/skill-template/business-info.json +25 -4
- package/skill-template/domains/jinchengda.md +16 -3
- package/skill-template/domains/yepiaotong.md +3 -3
- package/skills/weaver-e10-jinchengda/SKILL.md +111 -31
- package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +7 -13
- package/skills/weaver-e10-jinchengda/references/contract-ai-assistant.md +150 -0
- package/skills/weaver-e10-jinchengda/references/contract-ai-review.md +87 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-library-crud.md +111 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +0 -15
- package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +0 -13
- package/skills/weaver-e10-jinchengda/references/contract-credit-page.md +50 -0
- package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +4 -2
- package/skills/weaver-e10-jinchengda/references/contract-doc-compare.md +74 -0
- package/skills/weaver-e10-jinchengda/references/contract-flow-launch.md +84 -0
- package/skills/weaver-e10-jinchengda/references/contract-performance-query.md +29 -0
- package/skills/weaver-e10-jinchengda/references/contract-quote-to-contract.md +28 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-status.md +2 -21
- package/skills/weaver-e10-jinchengda/references/contract-request-write.md +0 -24
- package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +0 -15
- package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +0 -17
- package/skills/weaver-e10-jinchengda/references/contract-template-recommend.md +70 -0
- package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +0 -15
- package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +0 -15
- package/skills/weaver-e10-jinchengda/references/source-manifest.json +1168 -152
- package/skills/weaver-e10-meeting/SKILL.md +493 -17
- package/skills/weaver-e10-meeting/references/meeting-change.md +4 -4
- package/skills/weaver-e10-meeting/references/meeting-create.md +6 -6
- package/skills/weaver-e10-meeting/references/meeting-eb.md +503 -27
- package/skills/weaver-e10-meeting/references/meeting-invitation.md +113 -0
- package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +6 -6
- package/skills/weaver-e10-meeting/references/meeting-minute-file.md +425 -0
- package/skills/weaver-e10-meeting/references/meeting-queries.md +4 -4
- package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +15 -15
- package/skills/weaver-e10-meeting/references/meeting-verification.md +19 -11
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-workflow/SKILL.md +23 -1
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +282 -0
- package/skills/weaver-e10-workflow/scripts/todo-summary.cjs +695 -0
- package/skills/weaver-e10-yepiaotong/SKILL.md +15 -11
- package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +1 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +10 -7
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-form-fill-rules.md +41 -14
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-workflow.md +54 -26
- package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +6 -5
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +14 -11
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +97 -56
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +84 -60
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +142 -105
- package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +53 -194
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +7 -7
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +164 -72
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +8 -2
- package/templates/meeting/invitation.docx +0 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 批量获取流程表单详情 —— 摘要阶段的唯一取数入口。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么要单独一个文件
|
|
5
|
+
*
|
|
6
|
+
* 「怎么拉详情」这件事被**刻意收敛到本文件**:OA 侧后续会提供一个性能更好的批量接口,
|
|
7
|
+
* 到时候只改 `fetchOneDetail`(一个函数、一处 HTTP 调用)即可,调用方
|
|
8
|
+
* (`todoSmartConvert.ts` 的 `stage=summaryContext`)完全不用动。所以这里:
|
|
9
|
+
* - 对外只暴露 `fetchRequestDetailsBatch(env, requestIds, options)`;
|
|
10
|
+
* - 内部不掺任何结构组装、摘要生成、markdown 渲染;
|
|
11
|
+
* - 返回的是**按 requestId 索引的纯数据**,谁要用谁自己组装。
|
|
12
|
+
*
|
|
13
|
+
* ## 取数口径(复用现成链路,不另写一套)
|
|
14
|
+
*
|
|
15
|
+
* 与 `workflow.summary` / `workflow.detail` **同一个接口、同一份归一化**:
|
|
16
|
+
*
|
|
17
|
+
* POST /api/workflow/core/paService/v1/getWorkflowRequest
|
|
18
|
+
* { requestId, isGetRequestBaseInfo:true, isGetRequestLog:false, isGetFormData:true,
|
|
19
|
+
* isReadRequest:false, userId? }
|
|
20
|
+
*
|
|
21
|
+
* 归一再交给 `normalizeDetailResponse`(`read.ts`)→ `formData.fields[]` → 业务裁剪
|
|
22
|
+
* (`detailFieldRules.ts`:丢掉 `主送` 这类分发字段;内部留言类只留 `留言内容/发起人/发起时间`)
|
|
23
|
+
* → `selectSummaryFields`(业务字段筛选,剔除流程名称/发起人/时间/状态/节点等技术字段)。
|
|
24
|
+
*
|
|
25
|
+
* ★ `isReadRequest:false` 是硬要求:批量取数**不得**把用户的待办标成已读。`workflow.detail`
|
|
26
|
+
* 传的是 `true`,所以它不能被这里复用。
|
|
27
|
+
*
|
|
28
|
+
* ## 为什么并发而不是串行
|
|
29
|
+
*
|
|
30
|
+
* 真实数据 319 条待办,单条详情 HTTP 在数百毫秒级;串行要几分钟,逐条起 CLI 进程更慢
|
|
31
|
+
* (每条 ~5s,几乎全是启动与登录态开销)。这里在**进程内**用 worker 池并发,默认 10 路
|
|
32
|
+
* ——与用户给定口径一致,也不至于把服务端打满。
|
|
33
|
+
*/
|
|
34
|
+
import { WORKFLOW_ENDPOINTS } from '../../endpoints.js';
|
|
35
|
+
import { extractRowMetrics } from './businessRules.js';
|
|
36
|
+
import { dropDistributionDetailFields, isInternalMessageDetail, pickInternalMessageFieldPairs, } from './detailFieldRules.js';
|
|
37
|
+
import { normalizeDetailResponse, selectSummaryFields } from '../read.js';
|
|
38
|
+
/** 默认并发上限。 */
|
|
39
|
+
export const DETAIL_BATCH_CONCURRENCY = 10;
|
|
40
|
+
/** 单条详情默认超时。批量取数不是「补卡片」,不套用 5s 的短超时。 */
|
|
41
|
+
export const DETAIL_BATCH_TIMEOUT_MS = 10_000;
|
|
42
|
+
/** 默认重试次数(仅网络/超时;业务错误与登录失效不重试)。 */
|
|
43
|
+
export const DETAIL_BATCH_RETRIES = 1;
|
|
44
|
+
/** 单条最多取多少个业务字段(与 `workflow.summary` 的默认一致)。 */
|
|
45
|
+
export const DETAIL_BATCH_FIELD_LIMIT = 12;
|
|
46
|
+
/** 常见 HTML 实体 → 字符。`“` 这类 OA 富文本里到处都是,不解码会原样带进摘要。 */
|
|
47
|
+
const HTML_ENTITIES = {
|
|
48
|
+
amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: ' ',
|
|
49
|
+
ldquo: '“', rdquo: '”', lsquo: '‘', rsquo: '’', mdash: '—', ndash: '–',
|
|
50
|
+
hellip: '…', middot: '·', bull: '•', times: '×', divide: '÷', rarr: '→',
|
|
51
|
+
larr: '←', copy: '©', reg: '®', trade: '™', emsp: ' ', ensp: ' ', thinsp: ' ',
|
|
52
|
+
ge: '≥', le: '≤', laquo: '«', raquo: '»',
|
|
53
|
+
};
|
|
54
|
+
/** 数字实体(`“` / `“`)与命名实体统一解码;认不出的原样保留。 */
|
|
55
|
+
function decodeHtmlEntities(text) {
|
|
56
|
+
return text.replace(/&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z][a-zA-Z0-9]*);/g, (match, body) => {
|
|
57
|
+
try {
|
|
58
|
+
if (body.startsWith('#x') || body.startsWith('#X')) {
|
|
59
|
+
return String.fromCodePoint(Number.parseInt(body.slice(2), 16));
|
|
60
|
+
}
|
|
61
|
+
if (body.startsWith('#')) {
|
|
62
|
+
return String.fromCodePoint(Number.parseInt(body.slice(1), 10));
|
|
63
|
+
}
|
|
64
|
+
return HTML_ENTITIES[body.toLowerCase()] ?? match;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return match;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 清洗字段值(**不截断**):实体解码 + 去掉富文本残留 + 压缩空白。
|
|
73
|
+
*
|
|
74
|
+
* 单独拆出来是因为「内部留言正文有多长」要按清洗后的长度判:原始串里 `<p style=…>`、
|
|
75
|
+
* ` ` 能占掉一半篇幅,按原始长度判会把正常长度的留言误判成超长。
|
|
76
|
+
*/
|
|
77
|
+
function cleanFieldText(raw) {
|
|
78
|
+
let text = String(raw ?? '');
|
|
79
|
+
text = text.replace(/<(style|script)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, ' ');
|
|
80
|
+
text = text.replace(/<[^>]+>/g, ' ');
|
|
81
|
+
text = decodeHtmlEntities(text);
|
|
82
|
+
// 裸 CSS 声明块:`{ ... }` 整块丢弃(只丢大括号内的内容,不动前面的正文)
|
|
83
|
+
text = text.replace(/\{[^{}]{0,800}\}/g, ' ');
|
|
84
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
85
|
+
}
|
|
86
|
+
/** 错误 → 稳定的短语(用于 `RequestDetailContext.error`,不暴露内部堆栈)。 */
|
|
87
|
+
function stopReasonOf(error) {
|
|
88
|
+
const structured = error;
|
|
89
|
+
if (structured?.type === 'network') {
|
|
90
|
+
return structured.subtype === 'timeout' ? 'http_timeout' : 'network_error';
|
|
91
|
+
}
|
|
92
|
+
if (structured?.type === 'authentication')
|
|
93
|
+
return 'login_expired';
|
|
94
|
+
if (structured?.type === 'api')
|
|
95
|
+
return 'api_error';
|
|
96
|
+
return 'unknown_error';
|
|
97
|
+
}
|
|
98
|
+
/** 只有网络类错误值得重试;业务错误、登录失效重试也是同样结果。 */
|
|
99
|
+
function isRetryable(error) {
|
|
100
|
+
const structured = error;
|
|
101
|
+
if (structured?.type === 'network')
|
|
102
|
+
return true;
|
|
103
|
+
return structured?.retryable === true && structured?.type !== 'authentication';
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 单条详情取数。后续切换批量接口时,保持本文件对外的批量结果契约即可。
|
|
107
|
+
*/
|
|
108
|
+
async function fetchOneDetail(env, requestId, userId, options) {
|
|
109
|
+
const body = {
|
|
110
|
+
requestId,
|
|
111
|
+
isGetRequestBaseInfo: true,
|
|
112
|
+
isGetRequestLog: false,
|
|
113
|
+
isGetFormData: true,
|
|
114
|
+
// ★ 批量取数不得改变未读状态
|
|
115
|
+
isReadRequest: false,
|
|
116
|
+
};
|
|
117
|
+
if (userId)
|
|
118
|
+
body.userId = userId;
|
|
119
|
+
const response = await env.host.workflowRequest({
|
|
120
|
+
method: 'POST',
|
|
121
|
+
path: WORKFLOW_ENDPOINTS.REQUEST_DETAIL,
|
|
122
|
+
operation: 'workflow.todoSmartConvert.detail',
|
|
123
|
+
body,
|
|
124
|
+
timeoutMs: options.timeoutMs ?? DETAIL_BATCH_TIMEOUT_MS,
|
|
125
|
+
});
|
|
126
|
+
const normalized = normalizeDetailResponse(response);
|
|
127
|
+
const base = (normalized?.basicInfo ?? {});
|
|
128
|
+
const rawFields = Array.isArray(normalized?.formData?.fields) ? normalized.formData.fields : [];
|
|
129
|
+
// ① 分发类字段(`主送` …)**无条件丢弃**:值是几十个部门的收件人清单,对摘要零信息量,
|
|
130
|
+
// 还会挤占 `summaryFields` 的字段名额。必须在挑选之前丢,名额才不会被它吃掉。
|
|
131
|
+
const usableFields = dropDistributionDetailFields(rawFields);
|
|
132
|
+
// ② 内部留言类:只保留 留言内容 / 发起人 / 发起时间(其余是分发元数据,见 `detailFieldRules.ts`)。
|
|
133
|
+
const internalMessage = isInternalMessageDetail(base.workflowName, usableFields.map((field) => field?.fieldName));
|
|
134
|
+
// ③ 内部留言类只保留留言内容、发起人、发起时间;字段值只清洗不裁剪。
|
|
135
|
+
const cleanedFields = usableFields.map((field) => ({
|
|
136
|
+
...field,
|
|
137
|
+
value: cleanFieldText(String(field?.value ?? '')),
|
|
138
|
+
}));
|
|
139
|
+
const picked = internalMessage
|
|
140
|
+
? pickInternalMessageFieldPairs(cleanedFields, { creatorName: base.creatorName, createdAt: base.createdAt })
|
|
141
|
+
: selectSummaryFields(cleanedFields, options.fieldLimit ?? DETAIL_BATCH_FIELD_LIMIT, undefined, { fullValue: true });
|
|
142
|
+
const summaryFields = picked
|
|
143
|
+
.map((field) => ({
|
|
144
|
+
fieldName: String(field?.fieldName ?? '').trim(),
|
|
145
|
+
value: String(field?.value ?? ''),
|
|
146
|
+
}))
|
|
147
|
+
.filter((field) => field.fieldName && field.value);
|
|
148
|
+
// ④ 结构化指标(请假天数 / 假别 / 金额 / 费用类别):**只从流程详情数据的字段里取**,
|
|
149
|
+
// 不做标题兜底(2026-10-26 用户口径:详情取不到指标就不进阈值组,标题不能当证据)。
|
|
150
|
+
const metrics = extractRowMetrics(usableFields);
|
|
151
|
+
return {
|
|
152
|
+
requestId,
|
|
153
|
+
basicInfo: {
|
|
154
|
+
requestTitle: String(base.requestTitle ?? ''),
|
|
155
|
+
workflowName: String(base.workflowName ?? ''),
|
|
156
|
+
creatorName: String(base.creatorName ?? ''),
|
|
157
|
+
createdAt: String(base.createdAt ?? ''),
|
|
158
|
+
currentNode: String(base.currentNode ?? ''),
|
|
159
|
+
requestLevel: String(base.requestLevel ?? ''),
|
|
160
|
+
flowStatus: String(base.flowStatus ?? ''),
|
|
161
|
+
},
|
|
162
|
+
summaryFields,
|
|
163
|
+
contextAvailable: summaryFields.length > 0,
|
|
164
|
+
metrics,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* 批量拉取表单详情(并发上限默认 10,全量拉取,单条失败不拖垮整批)。
|
|
169
|
+
*
|
|
170
|
+
* 行为约定:
|
|
171
|
+
* - **按入参去重**,返回顺序与首次出现顺序一致;
|
|
172
|
+
* - 单条失败走重试(默认 1 次),仍失败则写入 `error`,**不抛错**——一条取不到不该让
|
|
173
|
+
* 整批 300 条白跑;
|
|
174
|
+
* - 认证失效(`login_expired`)会**立刻上抛**:后面每一条都会同样失败,继续跑没有意义,
|
|
175
|
+
* 而且会把真正的登录问题伪装成「一堆取数失败」;
|
|
176
|
+
* - 进度回调是可选的,只用于长批次的可观测性,不参与结果。
|
|
177
|
+
*/
|
|
178
|
+
export async function fetchRequestDetailsBatch(env, requestIds, options = {}) {
|
|
179
|
+
const startedAt = Date.now();
|
|
180
|
+
const unique = [];
|
|
181
|
+
const seen = new Set();
|
|
182
|
+
for (const raw of requestIds) {
|
|
183
|
+
const id = String(raw ?? '').trim();
|
|
184
|
+
if (!id || seen.has(id))
|
|
185
|
+
continue;
|
|
186
|
+
seen.add(id);
|
|
187
|
+
unique.push(id);
|
|
188
|
+
}
|
|
189
|
+
const concurrency = Math.max(1, Math.min(Number(options.concurrency ?? DETAIL_BATCH_CONCURRENCY), 32));
|
|
190
|
+
const retries = Math.max(0, Number(options.retries ?? DETAIL_BATCH_RETRIES));
|
|
191
|
+
const details = new Map();
|
|
192
|
+
const failures = [];
|
|
193
|
+
const session = await env.host.getSession();
|
|
194
|
+
const userId = session.userId || undefined;
|
|
195
|
+
let cursor = 0;
|
|
196
|
+
let fetched = 0;
|
|
197
|
+
let contextAvailable = 0;
|
|
198
|
+
let contextEmpty = 0;
|
|
199
|
+
const worker = async () => {
|
|
200
|
+
while (cursor < unique.length) {
|
|
201
|
+
const index = cursor;
|
|
202
|
+
cursor += 1;
|
|
203
|
+
const requestId = unique[index];
|
|
204
|
+
let attempt = 0;
|
|
205
|
+
let lastError;
|
|
206
|
+
while (attempt <= retries) {
|
|
207
|
+
try {
|
|
208
|
+
const context = await fetchOneDetail(env, requestId, userId, options);
|
|
209
|
+
details.set(requestId, context);
|
|
210
|
+
fetched += 1;
|
|
211
|
+
if (context.contextAvailable)
|
|
212
|
+
contextAvailable += 1;
|
|
213
|
+
else
|
|
214
|
+
contextEmpty += 1;
|
|
215
|
+
lastError = undefined;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
lastError = error;
|
|
220
|
+
// 登录失效:整批都会失败,立刻上抛而不是伪装成 N 条取数失败
|
|
221
|
+
if (stopReasonOf(error) === 'login_expired')
|
|
222
|
+
throw error;
|
|
223
|
+
attempt += 1;
|
|
224
|
+
if (attempt > retries || !isRetryable(error))
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (lastError !== undefined) {
|
|
229
|
+
const reason = stopReasonOf(lastError);
|
|
230
|
+
failures.push({ requestId, error: reason });
|
|
231
|
+
details.set(requestId, {
|
|
232
|
+
requestId,
|
|
233
|
+
basicInfo: {
|
|
234
|
+
requestTitle: '', workflowName: '', creatorName: '', createdAt: '',
|
|
235
|
+
currentNode: '', requestLevel: '', flowStatus: '',
|
|
236
|
+
},
|
|
237
|
+
summaryFields: [],
|
|
238
|
+
contextAvailable: false,
|
|
239
|
+
metrics: {},
|
|
240
|
+
error: reason,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, Math.max(unique.length, 1)) }, () => worker()));
|
|
246
|
+
return {
|
|
247
|
+
details,
|
|
248
|
+
stats: {
|
|
249
|
+
requested: unique.length,
|
|
250
|
+
fetched,
|
|
251
|
+
contextAvailable,
|
|
252
|
+
contextEmpty,
|
|
253
|
+
failed: failures.length,
|
|
254
|
+
concurrency: Math.min(concurrency, Math.max(unique.length, 1)),
|
|
255
|
+
durationMs: Date.now() - startedAt,
|
|
256
|
+
},
|
|
257
|
+
failures: failures.slice(0, 20),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `getTodoListData` 响应的 **displayData 取数口径**(待办汇总链路专用,独立成文件)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么单独放一个文件而不是塞进 `read.ts`:`read.ts` 是 `workflow.search` / `detail` /
|
|
5
|
+
* `summary` 共用的老模块,被其他 skill(查询、批量、写操作)大量引用;本模块只服务
|
|
6
|
+
* `workflow.todoSmartConvert`,放在这里可以让老模块**零改动**,改动面收敛在新文件里。
|
|
7
|
+
*
|
|
8
|
+
* 一条响应里只有 `displayData` 是显示层数据:同一份响应还带 `data`(技术字段行,几十个
|
|
9
|
+
* requestid/sysCode/…,与 displayData 一一重复但不含 `creatername`/`requestlevelShow` 等显示列)、
|
|
10
|
+
* `columns`(列定义)、`operates`、`pageUid`、`tableType`。汇总链路一个都不需要。
|
|
11
|
+
*/
|
|
12
|
+
import { responseData } from '../shared.js';
|
|
13
|
+
/**
|
|
14
|
+
* 逐行白名单:按**目标格式**(`docs/todo-smart-convert/mock/ai-todo-query-target.json` 里
|
|
15
|
+
* `displayItems[].requestData` / `requestDatas[]`)收敛——定位键、状态位、展示字段、时间列
|
|
16
|
+
* 都是目标结构要的;其余 100+ 个属性(sysCode、creatorMasterJodId、tenantKey、rowKey、
|
|
17
|
+
* 统计辅助位等)一律丢弃。
|
|
18
|
+
*/
|
|
19
|
+
const DISPLAY_ROW_FIELDS = [
|
|
20
|
+
// 定位键
|
|
21
|
+
'requestId', 'requestid',
|
|
22
|
+
'workflowid', 'workflowId',
|
|
23
|
+
'userMasterId', 'userid',
|
|
24
|
+
'isremark', 'isremarktype',
|
|
25
|
+
'nodeid', 'currentNodeId',
|
|
26
|
+
// 状态位
|
|
27
|
+
'overdue', 'userOverTime',
|
|
28
|
+
'requestlevel', 'requestLevelShow', 'requestlevelShow', 'requestlevelTitle',
|
|
29
|
+
// 展示字段
|
|
30
|
+
'requestnamehtml', 'requestnameTitle', 'requestnametitle', 'requestname',
|
|
31
|
+
'creater', 'creatername', 'creatorName',
|
|
32
|
+
'workflowname', 'workflowName', 'workflowTypeName',
|
|
33
|
+
'requestmark', 'flowstatusShow',
|
|
34
|
+
'receivedatetime', 'operatedatetime',
|
|
35
|
+
// 最后反馈时间:`getTodoListData.displayData` 里的原始键是全小写,大小写变体一并保留。
|
|
36
|
+
'lastfeedbackdatetime', 'lastFeedbackDateTime',
|
|
37
|
+
];
|
|
38
|
+
/**
|
|
39
|
+
* 只保留 `displayData`(且逐行白名单)的原始行。
|
|
40
|
+
*
|
|
41
|
+
* 取值路径**不**回落到 `record.data.list` / `record.list`:那两个键在 `getTodoListData` 里
|
|
42
|
+
* 指向技术字段行;宁可返回空数组,也不把非显示列当显示列。
|
|
43
|
+
*/
|
|
44
|
+
export function projectDisplayRows(response) {
|
|
45
|
+
const record = responseData(response);
|
|
46
|
+
const raw = record?.data?.displayData ?? record?.displayData ?? [];
|
|
47
|
+
const rows = Array.isArray(raw) ? raw : [];
|
|
48
|
+
const projected = [];
|
|
49
|
+
for (const row of rows) {
|
|
50
|
+
if (!row || typeof row !== 'object' || Array.isArray(row))
|
|
51
|
+
continue;
|
|
52
|
+
const source = row;
|
|
53
|
+
const picked = {};
|
|
54
|
+
for (const key of DISPLAY_ROW_FIELDS) {
|
|
55
|
+
const value = source[key];
|
|
56
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
57
|
+
picked[key] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (Object.keys(picked).length > 0)
|
|
61
|
+
projected.push(picked);
|
|
62
|
+
}
|
|
63
|
+
return projected;
|
|
64
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function mergeWarnings(meta, warnings) {
|
|
2
|
+
const list = [...new Set((warnings ?? []).filter((item) => typeof item === 'string' && item.trim() !== ''))];
|
|
3
|
+
return list.length > 0 ? { ...(meta ?? {}), warnings: list } : meta;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* 兼容旧调用方的名称,但不再按预算投影业务数据。
|
|
7
|
+
* requestData/requestDatas/displayItems 必须在最终 JSON 中完整返回;前端自行根据
|
|
8
|
+
* defaultExpanded 决定初始展示状态。历史 Omitted/ref 字段不属于业务协议。
|
|
9
|
+
*/
|
|
10
|
+
export function projectTodoOutput(data, _budgetBytes) {
|
|
11
|
+
return {
|
|
12
|
+
plan: 'full',
|
|
13
|
+
note: '',
|
|
14
|
+
data: (data && typeof data === 'object' ? data : {}),
|
|
15
|
+
notes: [],
|
|
16
|
+
bytes: Buffer.byteLength(JSON.stringify(data ?? null), 'utf8'),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* todoSmartConvert 的输出规划兼容层:只合并告警并原样透传数据。
|
|
21
|
+
* 公共 workflow 输出入口会对本 operation 关闭递归压缩。
|
|
22
|
+
*/
|
|
23
|
+
export function planTodoOutput(options) {
|
|
24
|
+
return {
|
|
25
|
+
data: options.data,
|
|
26
|
+
meta: mergeWarnings(options.meta, options.warnings),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { assertKnownKeys, groupKeyOf, readGroupBy, readItems, readTopN, renderMarkdown, } from './todoSmartConvert.core.js';
|
|
2
|
+
export function runSmartConvert(args) {
|
|
3
|
+
assertKnownKeys(args);
|
|
4
|
+
const { rawCount, rows, duplicateCount, unreadableCount } = readItems(args.items);
|
|
5
|
+
const groupBy = readGroupBy(args.groupBy);
|
|
6
|
+
const topN = readTopN(args.topN);
|
|
7
|
+
const buckets = new Map();
|
|
8
|
+
let amountSumTotal = 0;
|
|
9
|
+
let amountMissing = 0;
|
|
10
|
+
for (const row of rows) {
|
|
11
|
+
const key = groupKeyOf(row, groupBy);
|
|
12
|
+
let group = buckets.get(key);
|
|
13
|
+
if (!group) {
|
|
14
|
+
group = { key, count: 0, amountCount: 0, amountSum: 0, requestIds: [] };
|
|
15
|
+
buckets.set(key, group);
|
|
16
|
+
}
|
|
17
|
+
group.count += 1;
|
|
18
|
+
if (row.requestId)
|
|
19
|
+
group.requestIds.push(row.requestId);
|
|
20
|
+
if (typeof row.amount === 'number') {
|
|
21
|
+
group.amountCount += 1;
|
|
22
|
+
group.amountSum += row.amount;
|
|
23
|
+
amountSumTotal += row.amount;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
amountMissing += 1;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const sorted = [...buckets.values()].sort((a, b) => {
|
|
30
|
+
if (b.count !== a.count)
|
|
31
|
+
return b.count - a.count;
|
|
32
|
+
return a.key.localeCompare(b.key, 'zh-CN');
|
|
33
|
+
});
|
|
34
|
+
const groups = sorted.slice(0, topN);
|
|
35
|
+
const rest = sorted.slice(topN);
|
|
36
|
+
const restCount = rest.reduce((sum, group) => sum + group.count, 0);
|
|
37
|
+
const warnings = [];
|
|
38
|
+
if (duplicateCount > 0) {
|
|
39
|
+
warnings.push(`已按 requestId 去重:原始 ${rawCount} 条,唯一 ${rows.length} 条,重复 ${duplicateCount} 条`);
|
|
40
|
+
}
|
|
41
|
+
if (unreadableCount > 0) {
|
|
42
|
+
warnings.push(`有 ${unreadableCount} 条不是对象结构,已跳过`);
|
|
43
|
+
}
|
|
44
|
+
if (amountMissing > 0 && amountMissing < rows.length) {
|
|
45
|
+
warnings.push(`有 ${amountMissing} 条缺少金额,金额合计只统计了带金额的条目`);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
groupBy,
|
|
49
|
+
totalInput: rawCount,
|
|
50
|
+
total: rows.length,
|
|
51
|
+
duplicateCount,
|
|
52
|
+
groupCount: sorted.length,
|
|
53
|
+
totals: {
|
|
54
|
+
count: rows.length,
|
|
55
|
+
amountSum: Math.round(amountSumTotal * 100) / 100,
|
|
56
|
+
amountMissing,
|
|
57
|
+
},
|
|
58
|
+
groups,
|
|
59
|
+
restGroups: rest.length > 0 ? { groupCount: rest.length, count: restCount } : null,
|
|
60
|
+
warnings,
|
|
61
|
+
markdown: renderMarkdown({
|
|
62
|
+
groupBy,
|
|
63
|
+
total: rows.length,
|
|
64
|
+
groups,
|
|
65
|
+
restGroupCount: rest.length,
|
|
66
|
+
restCount,
|
|
67
|
+
hasAmount: amountMissing < rows.length,
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
}
|