weaver-work-cli 0.1.29 → 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.
Files changed (185) hide show
  1. package/dist/chunks/archive-U766FTFH.js +2537 -0
  2. package/dist/chunks/archive-oa-DMWEKX3L.js +1317 -0
  3. package/dist/chunks/asset-TCR7VDEV.js +2109 -0
  4. package/dist/chunks/attachment-content-AQNX5N3S.js +23 -0
  5. package/dist/chunks/blog-EVZWGDVM.js +2157 -0
  6. package/dist/chunks/caizhilian-JOWS6SL5.js +10087 -0
  7. package/dist/chunks/calendar-5WNCV66Q.js +1195 -0
  8. package/dist/chunks/chunk-2DUV44UX.js +15606 -0
  9. package/dist/chunks/chunk-324YPTC2.js +1607 -0
  10. package/dist/chunks/chunk-65QMVWHS.js +231 -0
  11. package/dist/chunks/chunk-6V7ZLF34.js +387 -0
  12. package/dist/chunks/chunk-AC7KJWC5.js +358 -0
  13. package/dist/chunks/chunk-AJ62BGAW.js +29 -0
  14. package/dist/chunks/chunk-BUSC6UAE.js +43 -0
  15. package/dist/chunks/chunk-CVZ2JOSW.js +62 -0
  16. package/dist/chunks/chunk-D4LTJSJD.js +477 -0
  17. package/dist/chunks/chunk-EG6ZU444.js +654 -0
  18. package/dist/chunks/chunk-EZALDOQH.js +383 -0
  19. package/dist/chunks/chunk-FMPJJYYI.js +106 -0
  20. package/dist/chunks/chunk-G2JSIUWF.js +769 -0
  21. package/dist/chunks/chunk-GCG2NMWR.js +703 -0
  22. package/dist/chunks/chunk-GPMFD4RD.js +75 -0
  23. package/dist/chunks/chunk-H3WDBYJW.js +508 -0
  24. package/dist/chunks/chunk-H6XSTSLM.js +2357 -0
  25. package/dist/chunks/chunk-J7RJTZSV.js +5556 -0
  26. package/dist/chunks/chunk-LUSPKZKY.js +53 -0
  27. package/dist/chunks/chunk-NIAJ5ZCG.js +73 -0
  28. package/dist/chunks/chunk-NOODWMCL.js +584 -0
  29. package/dist/chunks/chunk-OOUTBLZ4.js +51 -0
  30. package/dist/chunks/chunk-P26ZWSKU.js +73 -0
  31. package/dist/chunks/chunk-QWY6PGL6.js +3472 -0
  32. package/dist/chunks/chunk-R2IJAK7T.js +464 -0
  33. package/dist/chunks/chunk-R7O5PUWO.js +205 -0
  34. package/dist/chunks/chunk-RCGYCOMG.js +6962 -0
  35. package/dist/chunks/chunk-VZTXRG6X.js +403 -0
  36. package/dist/chunks/chunk-WHD4XMRP.js +161 -0
  37. package/dist/chunks/chunk-YLCYX7LE.js +371 -0
  38. package/dist/chunks/chunk-ZEDWSH32.js +68 -0
  39. package/dist/chunks/continuation-2CKSBLA6.js +11 -0
  40. package/dist/chunks/continuation-HZIZRZBX.js +13 -0
  41. package/dist/chunks/continuation-NLECEJPX.js +11 -0
  42. package/dist/chunks/ebuilder-form-NJ4YK6CC.js +2276 -0
  43. package/dist/chunks/ehr-MMYN36VA.js +5241 -0
  44. package/dist/chunks/employee-SAK2QYXX.js +11 -0
  45. package/dist/chunks/esb-HIWHOU7Q.js +141 -0
  46. package/dist/chunks/fna-KBACQTA5.js +1444 -0
  47. package/dist/chunks/host-3OHS5QUX.js +21 -0
  48. package/dist/chunks/host-IG6KEH5Q.js +19 -0
  49. package/dist/chunks/host-KQL5N4HO.js +23 -0
  50. package/dist/chunks/host-LLSZQCWR.js +17 -0
  51. package/dist/chunks/hrm-LYEC67ZQ.js +244 -0
  52. package/dist/chunks/im-RN2UKAEU.js +252 -0
  53. package/dist/chunks/invoice-EEPHLAF6.js +5179 -0
  54. package/dist/chunks/jiuchuanhui-WVVZYSJE.js +1940 -0
  55. package/dist/chunks/lazy-dispatch-MDOFFL2S.js +28 -0
  56. package/dist/chunks/mail-LNQLTF6L.js +2695 -0
  57. package/dist/chunks/meeting-XSD3BDVQ.js +504 -0
  58. package/dist/chunks/msg-parse-4GSQ2WTO.js +41 -0
  59. package/dist/chunks/okr-TMWTIU2K.js +2482 -0
  60. package/dist/chunks/plan-A2FSFA7T.js +2025 -0
  61. package/dist/chunks/project-T26QLBQF.js +1361 -0
  62. package/dist/chunks/registry-2VLUAESA.js +14 -0
  63. package/dist/chunks/registry-JCFEYXOE.js +18 -0
  64. package/dist/chunks/registry-NYPYMZXG.js +16 -0
  65. package/dist/chunks/registry-Q7OQJ3SU.js +19 -0
  66. package/dist/chunks/registry-ZJMJVWFA.js +19 -0
  67. package/dist/chunks/room-recommend-UR4ZDZS6.js +20 -0
  68. package/dist/chunks/schedule-5L2PM4M6.js +24 -0
  69. package/dist/chunks/state-Y2XBP6YE.js +15 -0
  70. package/dist/chunks/workflow-TR6PG2SL.js +245 -0
  71. package/dist/cmd/argv.js +45 -0
  72. package/dist/cmd/docs/index.js +3 -3
  73. package/dist/cmd/lazy-dispatch.js +20 -0
  74. package/dist/cmd/root.js +2 -4
  75. package/dist/cmd/setup/index.js +2 -2
  76. package/dist/cmd/skills/index.js +2 -2
  77. package/dist/core/package-paths.js +47 -0
  78. package/dist/index.js +163 -7
  79. package/dist/internal/e10/auth/xiaoe.js +3 -3
  80. package/dist/internal/e10/request-runtime.js +18 -62
  81. package/dist/internal/skills/detector.js +2 -2
  82. package/dist/shortcuts/contract/data/performance-catalog.js +1306 -0
  83. package/dist/shortcuts/contract/host.js +91 -35
  84. package/dist/shortcuts/contract/index.js +213 -0
  85. package/dist/shortcuts/contract/manifest.js +928 -0
  86. package/dist/shortcuts/contract/ooxml/docx.js +295 -0
  87. package/dist/shortcuts/contract/ooxml/xlsx.js +221 -0
  88. package/dist/shortcuts/contract/ooxml/zip.js +158 -0
  89. package/dist/shortcuts/contract/operations/assistant.js +1156 -0
  90. package/dist/shortcuts/contract/operations/clause-library.js +522 -0
  91. package/dist/shortcuts/contract/operations/compare.js +193 -0
  92. package/dist/shortcuts/contract/operations/credit-page.js +34 -0
  93. package/dist/shortcuts/contract/operations/flow.js +98 -0
  94. package/dist/shortcuts/contract/operations/page.js +66 -0
  95. package/dist/shortcuts/contract/operations/performance.js +733 -0
  96. package/dist/shortcuts/contract/operations/quote.js +557 -0
  97. package/dist/shortcuts/contract/operations/recommend.js +78 -0
  98. package/dist/shortcuts/contract/operations/registry.js +18 -0
  99. package/dist/shortcuts/contract/operations/review.js +83 -0
  100. package/dist/shortcuts/contract/operations/shared.js +37 -0
  101. package/dist/shortcuts/contract/operations/upload.js +108 -0
  102. package/dist/shortcuts/contract/operations/write.js +3 -2
  103. package/dist/shortcuts/index.js +140 -40
  104. package/dist/shortcuts/invoice/manifest.js +34 -11
  105. package/dist/shortcuts/invoice/operations/reim-auto.js +9 -0
  106. package/dist/shortcuts/invoice/operations/reim.js +81 -5
  107. package/dist/shortcuts/invoice/operations/shared.js +12 -5
  108. package/dist/shortcuts/meeting/host.js +45 -3
  109. package/dist/shortcuts/meeting/invitation/docx.js +442 -0
  110. package/dist/shortcuts/meeting/operations/attachment-content.js +418 -0
  111. package/dist/shortcuts/meeting/operations/attachment.js +442 -0
  112. package/dist/shortcuts/meeting/operations/eb/cache.js +57 -0
  113. package/dist/shortcuts/meeting/operations/eb/catalog.js +343 -28
  114. package/dist/shortcuts/meeting/operations/eb/conflict.js +372 -11
  115. package/dist/shortcuts/meeting/operations/eb/create-flow.js +710 -0
  116. package/dist/shortcuts/meeting/operations/eb/decision.js +47 -1
  117. package/dist/shortcuts/meeting/operations/eb/factory.js +129 -11
  118. package/dist/shortcuts/meeting/operations/eb/index.js +122 -2
  119. package/dist/shortcuts/meeting/operations/eb/room-recommend.js +378 -0
  120. package/dist/shortcuts/meeting/operations/eb/room-summary.js +84 -0
  121. package/dist/shortcuts/meeting/operations/eb/schedule.js +450 -0
  122. package/dist/shortcuts/meeting/operations/from-group-chat.js +576 -0
  123. package/dist/shortcuts/meeting/operations/invitation.js +458 -0
  124. package/dist/shortcuts/meeting/operations/minute-content.js +266 -0
  125. package/dist/shortcuts/meeting/operations/pipeline.js +489 -0
  126. package/dist/shortcuts/meeting/operations/qrcode.js +131 -0
  127. package/dist/shortcuts/meeting/operations/receipts.js +1 -1
  128. package/dist/shortcuts/meeting/operations/registry.js +18 -0
  129. package/dist/shortcuts/meeting/operations/summary.js +276 -0
  130. package/dist/shortcuts/meeting/operations/task-notify.js +325 -0
  131. package/dist/shortcuts/meeting/operations.js +42 -1
  132. package/dist/shortcuts/meeting/qr/encoder.js +427 -0
  133. package/dist/shortcuts/meeting/qr/render.js +103 -0
  134. package/dist/shortcuts/workflow/operations/form.js +17 -5
  135. package/docs/contract.md +39 -4
  136. package/docs/invoice.md +5 -5
  137. package/package.json +3 -2
  138. package/scripts/bundle.mjs +63 -0
  139. package/skill-template/business-info.json +25 -4
  140. package/skill-template/domains/jinchengda.md +16 -3
  141. package/skill-template/domains/yepiaotong.md +1 -1
  142. package/skills/weaver-e10-jinchengda/SKILL.md +111 -31
  143. package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +7 -13
  144. package/skills/weaver-e10-jinchengda/references/contract-ai-assistant.md +150 -0
  145. package/skills/weaver-e10-jinchengda/references/contract-ai-review.md +87 -0
  146. package/skills/weaver-e10-jinchengda/references/contract-clause-library-crud.md +111 -0
  147. package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +0 -15
  148. package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +0 -13
  149. package/skills/weaver-e10-jinchengda/references/contract-credit-page.md +50 -0
  150. package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +4 -2
  151. package/skills/weaver-e10-jinchengda/references/contract-doc-compare.md +74 -0
  152. package/skills/weaver-e10-jinchengda/references/contract-flow-launch.md +84 -0
  153. package/skills/weaver-e10-jinchengda/references/contract-performance-query.md +29 -0
  154. package/skills/weaver-e10-jinchengda/references/contract-quote-to-contract.md +28 -0
  155. package/skills/weaver-e10-jinchengda/references/contract-request-status.md +2 -21
  156. package/skills/weaver-e10-jinchengda/references/contract-request-write.md +0 -24
  157. package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +0 -15
  158. package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +0 -17
  159. package/skills/weaver-e10-jinchengda/references/contract-template-recommend.md +70 -0
  160. package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +0 -15
  161. package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +0 -15
  162. package/skills/weaver-e10-jinchengda/references/source-manifest.json +1168 -152
  163. package/skills/weaver-e10-meeting/SKILL.md +493 -17
  164. package/skills/weaver-e10-meeting/references/meeting-change.md +4 -4
  165. package/skills/weaver-e10-meeting/references/meeting-create.md +6 -6
  166. package/skills/weaver-e10-meeting/references/meeting-eb.md +503 -27
  167. package/skills/weaver-e10-meeting/references/meeting-invitation.md +113 -0
  168. package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +6 -6
  169. package/skills/weaver-e10-meeting/references/meeting-minute-file.md +425 -0
  170. package/skills/weaver-e10-meeting/references/meeting-queries.md +4 -4
  171. package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +15 -15
  172. package/skills/weaver-e10-meeting/references/meeting-verification.md +19 -11
  173. package/skills/weaver-e10-meeting.zip +0 -0
  174. package/skills/weaver-e10-yepiaotong/SKILL.md +9 -5
  175. package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +1 -1
  176. package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +6 -5
  177. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +14 -11
  178. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +97 -56
  179. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +84 -60
  180. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +142 -105
  181. package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +53 -194
  182. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +7 -7
  183. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +164 -72
  184. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +6 -0
  185. package/templates/meeting/invitation.docx +0 -0
@@ -0,0 +1,458 @@
1
+ /**
2
+ * 会议邀请函 docx 生成(需求 ⑦)—— 只生成、不发送。
3
+ *
4
+ * 场景(2026-10-23 用户定稿):在小E 智能体工作台里,按会议真实数据生成一份邀请函 docx,
5
+ * 项目人员拿到后自行修改再对外发出。
6
+ *
7
+ * 已锁定的产品决策:
8
+ * - 邀请对象是**一个组织**(不是个人)⇒ 单份文档,不做逐人循环、不做发送。
9
+ * - 模板固定为「泛微标准模板」,**不支持替换**(无 templatePath 入参)。
10
+ * - 取不到的字段渲染成**黄色高亮 + 【待补充】**,让人工一眼看到要改哪里。
11
+ * - 租户自定义字段(如「来访客户名称」)**接口不会返回**,只能由 `extra` 传入;
12
+ * 实现上仍保留「接口有值优先、extra 覆盖」的顺序,便于将来接口补字段时零改动。
13
+ * - 数据源必须是 `meeting.eb.meeting.list` 单查(47 字段);
14
+ * `meeting.eb.meeting.detail` 在该环境只回 `{id}`,不可用。
15
+ */
16
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
17
+ import { dirname, join, resolve } from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { meetingValidationError } from '../errors.js';
20
+ import { meetingStateDir } from './env.js';
21
+ import { renderDocx } from '../invitation/docx.js';
22
+ import { meetingSuccess, SCHEMA_ID } from './types.js';
23
+ /** 模板随 CLI 包分发(`templates/` 已在 package.json 的 files 白名单内)。 */
24
+ function templatePath() {
25
+ const override = process.env.WEAVER_MEETING_INVITATION_TEMPLATE;
26
+ if (override)
27
+ return resolve(override);
28
+ // 本文件编译后位于 <pkg>/dist/shortcuts/meeting/operations/,向上四级即包根
29
+ const here = dirname(fileURLToPath(import.meta.url));
30
+ return resolve(here, '../../../../templates/meeting/invitation.docx');
31
+ }
32
+ const WEEKDAYS = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
33
+ /**
34
+ * 落款公司全称的兜底值。
35
+ *
36
+ * 口径(2026-10-23 用户确认):落款默认用**公司全称**,不是部门;泛微场景下就是这一串。
37
+ * 对外开放时客户不需要改代码 —— 传 `extra.company_name` 即可覆盖(优先级最高)。
38
+ * 之所以不做成"取 E10 根组织名":组织树里只有「泛微网络」,没有工商全称。
39
+ */
40
+ const DEFAULT_COMPANY_NAME = '上海泛微网络科技股份有限公司';
41
+ function pad(value) {
42
+ return String(value).padStart(2, '0');
43
+ }
44
+ /** 解析 `yyyy-MM-dd HH:mm`(接口返回口径,不带秒)。 */
45
+ function parseDateTime(raw) {
46
+ const text = String(raw ?? '').trim();
47
+ const match = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})/.exec(text);
48
+ if (!match)
49
+ return null;
50
+ const [, y, m, d, hh, mm] = match;
51
+ const date = new Date(Number(y), Number(m) - 1, Number(d), Number(hh), Number(mm));
52
+ return Number.isNaN(date.getTime()) ? null : date;
53
+ }
54
+ function formatDate(date) {
55
+ return `${date.getFullYear()}年${pad(date.getMonth() + 1)}月${pad(date.getDate())}日`;
56
+ }
57
+ function formatTime(date) {
58
+ return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
59
+ }
60
+ /** 姓名串:接口给的是英文逗号分隔,正式函件需要中文顿号。 */
61
+ function humanizeMembers(raw) {
62
+ const text = String(raw ?? '').trim();
63
+ if (!text)
64
+ return '';
65
+ return text
66
+ .split(/[,,;;]/)
67
+ .map((item) => item.trim())
68
+ .filter(Boolean)
69
+ .join('、');
70
+ }
71
+ function safeFileName(text) {
72
+ return text.replace(/[\\/:*?"<>|\r\n]+/g, '_').replace(/\s+/g, ' ').trim().slice(0, 80) || '未命名会议';
73
+ }
74
+ const AGENDA_HEADER_KEYWORD = '议程';
75
+ /** 时间段文案:`14:00-14:45`;只有起点时给起点;都没有返回空串。 */
76
+ function formatTimeRange(start, end) {
77
+ if (start && end)
78
+ return `${formatTime(start)}-${formatTime(end)}`;
79
+ if (start)
80
+ return formatTime(start);
81
+ return '';
82
+ }
83
+ /**
84
+ * 取会议议题并转成议程表行。
85
+ *
86
+ * 口径(2026-10-23 与用户确认):**有议题就按议题填,没有就保留模板原样**。
87
+ * 议题表在部分租户未注册(返回空数组属正常),所以这里任何失败都静默降级为空,
88
+ * 由调用方继续走「extra → 模板原样」的兜底,绝不能因为取议题失败让邀请函生成失败。
89
+ */
90
+ export async function loadAgendaFromTopics(env, mtId) {
91
+ try {
92
+ const { getMeetingOperation } = await import('./registry.js');
93
+ const operation = getMeetingOperation('meeting.eb.topic.list');
94
+ const result = await operation.handler({ mt_id: mtId, pageNo: 1, pageSize: 200 }, env);
95
+ const rows = (result.data?.rows ?? []);
96
+ const topics = rows.map((item) => item?.mainTable ?? item);
97
+ const items = topics
98
+ .filter((topic) => String(topic?.mt_id ?? '').trim() === mtId)
99
+ .map((topic) => ({
100
+ // 2026-10-28 用户纠正:议题名称用 `topic_title`,不能用 `name`
101
+ // (那是 eBuilder 自动标题「议题库-张三 2026-10-27 18:18」,不是业务字段)。
102
+ title: String(topic?.topic_title ?? '').trim(),
103
+ // 议程内容 = 议题内容概要
104
+ content: String(topic?.topic_content ?? '').trim(),
105
+ num: String(topic?.topic_num ?? '').trim(),
106
+ start: null,
107
+ end: null,
108
+ }))
109
+ .filter((item) => item.title !== '');
110
+ // 时间段在**会议议题明细**(detail1)里,议题库表没有 start/end——
111
+ // 按 topic_num ↔ detail1.topic_no 匹配;匹配不上就留空,不阻塞议程。
112
+ try {
113
+ const detailOp = getMeetingOperation('meeting.eb.meeting.detail');
114
+ const detail = await detailOp.handler({ id: mtId }, env);
115
+ const detail1 = (detail.data?.details?.detail1 ?? []);
116
+ const timeByNum = new Map();
117
+ for (const row of detail1) {
118
+ const no = String(row?.topic_no ?? '').trim();
119
+ if (!no)
120
+ continue;
121
+ timeByNum.set(no, {
122
+ start: parseDateTime(row?.start_time),
123
+ end: parseDateTime(row?.end_time),
124
+ });
125
+ }
126
+ for (const item of items) {
127
+ const times = item.num ? timeByNum.get(item.num) : undefined;
128
+ if (times) {
129
+ item.start = times.start;
130
+ item.end = times.end;
131
+ }
132
+ }
133
+ }
134
+ catch {
135
+ // 明细取不到(旧环境 detail 不回明细)→ 时间列留空,议程其余照常
136
+ }
137
+ // 有开始时间的排前面并按时间升序,没有时间的排后面(保持接口原顺序)
138
+ const indexed = items.map((item, index) => ({ ...item, index }));
139
+ indexed.sort((left, right) => {
140
+ const leftTime = left.start ? left.start.getTime() : Number.POSITIVE_INFINITY;
141
+ const rightTime = right.start ? right.start.getTime() : Number.POSITIVE_INFINITY;
142
+ return leftTime === rightTime ? left.index - right.index : leftTime - rightTime;
143
+ });
144
+ // 列顺序 = 模板列顺序(议程安排 | 议程内容 | 议程时间),2026-10-28 用户纠正
145
+ return indexed.map((item) => [
146
+ item.title,
147
+ item.content,
148
+ formatTimeRange(item.start, item.end),
149
+ ]);
150
+ }
151
+ catch {
152
+ return [];
153
+ }
154
+ }
155
+ /**
156
+ * `extra.agenda` 兜底:按行拆分(换行或分号),每行写成 `议程安排|议程内容|议程时间`
157
+ * (即 模板列顺序直传:议题名称|内容概要|时间段,2026-10-28 用户纠正列语义后同步调整)。
158
+ */
159
+ function parseAgendaFromExtra(raw) {
160
+ const text = String(raw ?? '').trim();
161
+ if (!text)
162
+ return [];
163
+ return text
164
+ .split(/[\r\n;;]+/u)
165
+ .map((line) => line.trim())
166
+ .filter(Boolean)
167
+ .map((line) => {
168
+ const parts = line.split('|').map((part) => part.trim());
169
+ const [name = '', content = '', timeRange = ''] = parts.length > 1 ? parts : [parts[0], '', ''];
170
+ return [name, content, timeRange];
171
+ })
172
+ .filter((row) => row[0] !== '');
173
+ }
174
+ /**
175
+ * 把会议接口返回的任意字段值压成可渲染文本。
176
+ * - 字符串/数字/布尔:直接返回
177
+ * - 数组:逐个取值后用「、」拼接(人员多选/部门多选常见)
178
+ * - 对象:优先 name / label / text(EB 的人员、部门、关联记录控件都是这种形态)
179
+ * 取不到返回 undefined,交由调用方走【待补充】。
180
+ */
181
+ function flattenFieldValue(raw) {
182
+ if (raw === undefined || raw === null)
183
+ return undefined;
184
+ if (typeof raw === 'string')
185
+ return raw.trim() ? raw : undefined;
186
+ if (typeof raw === 'number' || typeof raw === 'boolean')
187
+ return String(raw);
188
+ if (Array.isArray(raw)) {
189
+ const parts = raw.map((item) => flattenFieldValue(item)).filter((item) => Boolean(item));
190
+ return parts.length > 0 ? parts.join('、') : undefined;
191
+ }
192
+ if (typeof raw === 'object') {
193
+ const record = raw;
194
+ for (const key of ['name', 'label', 'text', 'value', 'title']) {
195
+ const candidate = flattenFieldValue(record[key]);
196
+ if (candidate)
197
+ return candidate;
198
+ }
199
+ }
200
+ return undefined;
201
+ }
202
+ /**
203
+ * 取「发起人所属组织」。
204
+ *
205
+ * 背景(2026-10-23 用户口径):落款单位默认应该是**发起人的所属组织**,而不是会议的
206
+ * `org_members`(很多会议该字段为空,真机实测 `会议申请流程-李四-2026-10-16` 就是空串)。
207
+ *
208
+ * 数据来源:会议行的 `creator` 字段是**人员 id**(真机实测 `6064722198805071427`),
209
+ * 用它调 `hrm.employee.batch`(按人员 id 批量取部门/上级公司)即可。
210
+ * 直接复用 hrm 业务线已实现的 operation,不复制接口契约,也不改它的代码。
211
+ *
212
+ * 失败一律**静默降级为 null**(落款继续走【待补充】),不能因为查人失败让整个邀请函生成失败。
213
+ */
214
+ async function resolveIssuerOrganization(env, creatorId) {
215
+ try {
216
+ const { hrmEmployeeOperations } = await import('../../hrm/operations/employee.js');
217
+ const batch = hrmEmployeeOperations.find((operation) => operation.name === 'hrm.employee.batch');
218
+ if (!batch)
219
+ return null;
220
+ const result = await batch.handler({ ids: [creatorId] }, env);
221
+ const row = (result?.data?.rows ?? [])[0];
222
+ if (!row)
223
+ return null;
224
+ const department = String(row.department ?? '').trim();
225
+ const subCompany = String(row.subCompany ?? '').trim();
226
+ if (!department && !subCompany)
227
+ return null;
228
+ return {
229
+ ...(department ? { department } : {}),
230
+ ...(subCompany ? { subCompany } : {}),
231
+ };
232
+ }
233
+ catch {
234
+ return null;
235
+ }
236
+ }
237
+ /** 组装占位符解析器:派生字段 → 会议标准字段 → extra(租户自定义字段)→ 待补充。 */
238
+ export function buildResolver(row, extra, issuerOrg) {
239
+ const begin = parseDateTime(row.begin_datetime);
240
+ const end = parseDateTime(row.end_datetime);
241
+ const derived = {
242
+ // 落款日期用「生成日期」,不是会议日期(邀请函是今天开出去的)
243
+ issue_date: formatDate(new Date()),
244
+ };
245
+ if (begin) {
246
+ derived.begin_date = formatDate(begin);
247
+ derived.begin_weekday = WEEKDAYS[begin.getDay()];
248
+ derived.begin_time = formatTime(begin);
249
+ }
250
+ if (end) {
251
+ derived.end_date = formatDate(end);
252
+ derived.end_time = formatTime(end);
253
+ }
254
+ if (begin && end) {
255
+ // 同日会议不重复日期(2026-10-28 用户纠正):模板写的是 `会议时间:{begin_datetime}~{end_datetime}`,
256
+ // 原始字段直接拼接会渲染成「2026-10-28 09:00~2026-10-28 10:16」。
257
+ // 派生层覆写:同日 → end 只给时间(09:00~10:16);跨日 → end 带完整日期。`~` 分隔。
258
+ // 日期部分取原始字段(2026-10-28),不用 formatDate(那是「2026年10月28日」中文落款格式)。
259
+ const beginDateIso = String(row.begin_datetime ?? '').trim().slice(0, 10) || formatDate(begin);
260
+ const sameDay = beginDateIso === String(row.end_datetime ?? '').trim().slice(0, 10);
261
+ const endPart = sameDay ? formatTime(end) : `${String(row.end_datetime ?? '').trim().slice(0, 10)} ${formatTime(end)}`;
262
+ derived.begin_datetime = `${beginDateIso} ${formatTime(begin)}`;
263
+ derived.end_datetime = endPart;
264
+ derived.time_range = `${beginDateIso} ${formatTime(begin)}~${endPart}`;
265
+ }
266
+ // 落款单位:优先会议自带的参会部门;为空时回落到**发起人所属组织**(部门 → 上级公司)。
267
+ // 真机实测 `org_members` 常年为空串,只用它会让落款永远是【待补充】。
268
+ const meetingOrg = humanizeMembers(row.org_members);
269
+ const issuerDepartment = issuerOrg?.department ?? '';
270
+ const issuerCompany = issuerOrg?.subCompany ?? '';
271
+ const orgMembers = meetingOrg || issuerDepartment || issuerCompany;
272
+ const orgSource = meetingOrg ? 'meeting.org_members'
273
+ : issuerDepartment ? 'issuer.department'
274
+ : issuerCompany ? 'issuer.subCompany'
275
+ : 'none';
276
+ const standard = {
277
+ mt_name: String(row.mt_name ?? ''),
278
+ mt_desc: String(row.mt_desc ?? ''),
279
+ address: String(row.address ?? ''),
280
+ hrm_members: humanizeMembers(row.hrm_members),
281
+ org_members: orgMembers,
282
+ // 落款公司全称:`extra.company_name` 可覆盖(对外开放时客户传自己的公司名)
283
+ company_name: String(extra.company_name ?? '').trim() || DEFAULT_COMPANY_NAME,
284
+ // 单独暴露上级公司,模板需要「公司级落款」时可直接用
285
+ issuer_company: issuerCompany,
286
+ other_members: humanizeMembers(row.other_members),
287
+ total_member: row.total_member === undefined || row.total_member === null ? '' : String(row.total_member),
288
+ video_url: String(row.video_url ?? ''),
289
+ // 召集人:会议没填 `caller` 时回落到**创建人** ——
290
+ // 否则整句「{caller} 诚邀您参加…」变成【待补充】,而用户明明能看到"召集人:张三"
291
+ // (那是创建人/参会人里的信息)。来源单独回报,别让调用方误以为是会议填的。
292
+ caller: String(row.caller ?? '').trim() || String(row.mt_creator ?? '').trim(),
293
+ contacter: String(row.contacter ?? ''),
294
+ topic_presenter: String(row.topic_presenter ?? ''),
295
+ mt_format: String(row.mt_format ?? ''),
296
+ mt_type: String(row.mt_type ?? ''),
297
+ mt_creator: String(row.mt_creator ?? ''),
298
+ with_secauth: String(row.with_secauth ?? ''),
299
+ begin_datetime: String(row.begin_datetime ?? ''),
300
+ end_datetime: String(row.end_datetime ?? ''),
301
+ create_time: String(row.create_time ?? ''),
302
+ request_id: String(row.request_id ?? ''),
303
+ };
304
+ const lookup = (key) => {
305
+ if (key in derived)
306
+ return derived[key];
307
+ if (key in standard)
308
+ return standard[key];
309
+ if (key.startsWith('extra.') && key.slice(6) in extra)
310
+ return extra[key.slice(6)];
311
+ if (key in extra)
312
+ return extra[key];
313
+ // 兜底:会议接口返回的**任意字段**都能直接用 `{dataKey}` 引用。
314
+ // 读侧(meeting.eb.meeting.list)不投影、不过滤字段,所以租户在 eBuilder 接口里
315
+ // 新增自定义字段(如「来访客户名称」)后,只要模板写 `{该字段的 dataKey}` 就能取值,
316
+ // CLI 不用改代码、不用发版。(对象型字段取 name,数组取各元素 name 拼接)
317
+ const raw = row[key];
318
+ const flattened = flattenFieldValue(raw);
319
+ if (flattened)
320
+ return flattened;
321
+ return undefined;
322
+ };
323
+ return {
324
+ resolver: (key) => {
325
+ const value = lookup(key);
326
+ const text = String(value ?? '').trim();
327
+ if (!text)
328
+ return { text: key, pending: true };
329
+ return { text };
330
+ },
331
+ labelOf: (key) => key,
332
+ orgSource,
333
+ };
334
+ }
335
+ const meetingInvitationRender = {
336
+ name: 'meeting.invitation.render',
337
+ resource: 'invitation',
338
+ method: 'render',
339
+ action: 'meeting.invitation.render',
340
+ summary: '按会议真实数据生成邀请函 docx(只读,不发送):数据取 meeting.eb.meeting.list 单查;议程有议题就按议题自动填、没有则保留模板示例行;取不到的字段渲染成黄色高亮【待补充】;租户自定义字段用 extra 传入',
341
+ capability: 'invitation',
342
+ risk: 'read',
343
+ inputSchema: {
344
+ type: 'object',
345
+ additionalProperties: false,
346
+ properties: {
347
+ mtId: { ...SCHEMA_ID, description: '会议 id(EB 版 mt_detail 主键)' },
348
+ extra: {
349
+ type: 'object',
350
+ additionalProperties: { type: 'string' },
351
+ description: '租户自定义字段(如 guest_org=来访客户名称)。接口不会返回这类字段,只能从这里传;模板里写 {extra.guest_org} 或 {guest_org} 引用。另有保留键 agenda=议程(每行「内容」或「内容|时长|时间段」,换行分隔),仅在会议没有议题时生效',
352
+ },
353
+ outputPath: {
354
+ type: 'string',
355
+ description: '输出文件完整路径(可选)。缺省写到 ~/.weaver-work-cli/meeting/invitations/邀请函-{会议名}-{日期}.docx',
356
+ },
357
+ },
358
+ required: ['mtId'],
359
+ },
360
+ handler: async (args, env) => {
361
+ const mtId = String(args.mtId ?? '').trim();
362
+ if (!mtId) {
363
+ throw meetingValidationError('mt_id_required', '必须提供会议 id(mtId)');
364
+ }
365
+ const extra = {};
366
+ if (args.extra && typeof args.extra === 'object') {
367
+ for (const [key, value] of Object.entries(args.extra)) {
368
+ if (value !== undefined && value !== null)
369
+ extra[key] = String(value);
370
+ }
371
+ }
372
+ // 复用已实现的 EB 列表操作(内部先 count 再 list,筛选走 URL query)
373
+ const { getMeetingOperation } = await import('./registry.js');
374
+ const listOperation = getMeetingOperation('meeting.eb.meeting.list');
375
+ let row;
376
+ try {
377
+ const listed = await listOperation.handler({ mt_id: mtId, pageNo: 1, pageSize: 1 }, env);
378
+ const rows = (listed.data?.rows ?? []);
379
+ row = rows[0]?.mainTable ?? rows[0];
380
+ }
381
+ catch (error) {
382
+ throw meetingValidationError('invitation_source_unavailable', `读取会议数据失败(邀请函当前依赖 EB 版会议列表):${error instanceof Error ? error.message : String(error)}`, { mtId });
383
+ }
384
+ if (!row) {
385
+ throw meetingValidationError('not_found', `未找到会议 id=${mtId},或当前账号无权限查看`, { mtId });
386
+ }
387
+ // 落款单位回落:会议 org_members 为空时,用「发起人(creator 人员 id)的所属组织」
388
+ const creatorId = String(row.creator ?? '').trim();
389
+ const needsIssuerOrg = !String(row.org_members ?? '').trim() && creatorId !== '';
390
+ const issuerOrg = needsIssuerOrg ? await resolveIssuerOrganization(env, creatorId) : null;
391
+ const { resolver, orgSource } = buildResolver(row, extra, issuerOrg);
392
+ // 议程:议题表 → extra.agenda → 保留模板里的示例行(用户口径:没有就这样)
393
+ let agendaRows = await loadAgendaFromTopics(env, mtId);
394
+ let agendaSource = agendaRows.length > 0 ? 'meeting.topic' : 'template';
395
+ if (agendaRows.length === 0) {
396
+ const fromExtra = parseAgendaFromExtra(extra.agenda);
397
+ if (fromExtra.length > 0) {
398
+ agendaRows = fromExtra;
399
+ agendaSource = 'extra';
400
+ }
401
+ }
402
+ let template;
403
+ try {
404
+ template = await readFile(templatePath());
405
+ }
406
+ catch (error) {
407
+ throw meetingValidationError('template_missing', `邀请函模板缺失:${templatePath()}(CLI 包应自带 templates/meeting/invitation.docx)`, { error: error instanceof Error ? error.message : String(error) });
408
+ }
409
+ const rendered = renderDocx(template, resolver, {
410
+ agenda: { headerKeyword: AGENDA_HEADER_KEYWORD, rows: agendaRows },
411
+ });
412
+ const begin = parseDateTime(row.begin_datetime);
413
+ const dateTag = begin ? `${begin.getFullYear()}${pad(begin.getMonth() + 1)}${pad(begin.getDate())}` : '未知日期';
414
+ const fileName = `邀请函-${safeFileName(String(row.mt_name ?? ''))}-${dateTag}.docx`;
415
+ const target = args.outputPath
416
+ ? resolve(String(args.outputPath))
417
+ : join(meetingStateDir(env.ctx), 'invitations', fileName);
418
+ await mkdir(dirname(target), { recursive: true });
419
+ await writeFile(target, rendered.buffer);
420
+ const warnings = [];
421
+ if (rendered.pending.length > 0) {
422
+ warnings.push(`以下字段取不到值,已渲染成黄色高亮【待补充】,请人工补充:${rendered.pending.join('、')}`);
423
+ }
424
+ if (rendered.skippedParagraphs > 0) {
425
+ warnings.push(`有 ${rendered.skippedParagraphs} 个含复杂结构(图片/超链接/域)的段落未做替换,请人工检查`);
426
+ }
427
+ if (agendaSource === 'template') {
428
+ warnings.push('该会议没有议题,议程表保留模板里的示例行(“15分钟/泛微公司介绍”等),请人工改成真实议程');
429
+ }
430
+ // 召集人缺失要讲清楚"为什么缺",否则调用方会误以为"会议明明有召集人却让补"
431
+ // (2026-10-28 用户反馈的真实困惑:那是创建人/参会人,不是会议表的 caller 字段)。
432
+ if (String(row.caller ?? '').trim() === '') {
433
+ warnings.push(String(row.mt_creator ?? '').trim()
434
+ ? `会议未填「召集人」(caller 字段),邀请函已用**创建人**「${String(row.mt_creator).trim()}」代替;如不对可传 extra.caller 覆盖`
435
+ : '会议既没填「召集人」(caller)也没有创建人,邀请函里该处为【待补充】;可传 extra.caller 指定');
436
+ }
437
+ return meetingSuccess('meeting.invitation.render', {
438
+ filePath: target,
439
+ fileName,
440
+ bytes: rendered.buffer.length,
441
+ meetingId: mtId,
442
+ meetingName: String(row.mt_name ?? ''),
443
+ filledFields: rendered.filled,
444
+ pendingFields: rendered.pending,
445
+ // 落款单位的实际来源,便于调用方判断是否需要人工改写
446
+ orgSource,
447
+ // 召集人来源:meeting.caller=会议自己填的 / meeting.mt_creator=回落到创建人 / none=都没有
448
+ callerSource: String(row.caller ?? '').trim()
449
+ ? 'meeting.caller'
450
+ : (String(row.mt_creator ?? '').trim() ? 'meeting.mt_creator' : 'none'),
451
+ // 议程来源:meeting.topic=按议题自动填 / extra=调用方传入 / template=保留模板示例行
452
+ agendaSource,
453
+ agendaRows: agendaRows.length,
454
+ agendaTableFilled: rendered.agenda?.filled ?? false,
455
+ }, warnings.length > 0 ? { warnings } : undefined);
456
+ },
457
+ };
458
+ export const meetingInvitationOperations = [meetingInvitationRender];