weaver-work-cli 0.1.20 → 0.1.22

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 (77) hide show
  1. package/README.md +19 -11
  2. package/dist/core/refs.js +39 -36
  3. package/dist/internal/e10/auth/commands.js +33 -18
  4. package/dist/internal/e10/auth/session.js +60 -2
  5. package/dist/internal/e10/auth/xiaoe.js +253 -53
  6. package/dist/internal/e10/context.js +18 -6
  7. package/dist/internal/e10/request-runtime.js +3 -1
  8. package/dist/shortcuts/caizhilian/operations/confirm.js +1 -1
  9. package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +901 -20
  10. package/dist/shortcuts/caizhilian/operations/handlers/folder.js +55 -0
  11. package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +4 -4
  12. package/dist/shortcuts/caizhilian/operations/specs.js +75 -0
  13. package/dist/shortcuts/hrm/manifest.js +16 -9
  14. package/dist/shortcuts/hrm/operations/registry.js +2 -0
  15. package/dist/shortcuts/hrm/operations/resolve.js +449 -0
  16. package/dist/shortcuts/invoice/host.js +5 -4
  17. package/dist/shortcuts/meeting/operations/eb/association.js +151 -0
  18. package/dist/shortcuts/meeting/operations/eb/attendance-summary.js +153 -0
  19. package/dist/shortcuts/meeting/operations/eb/catalog.js +185 -13
  20. package/dist/shortcuts/meeting/operations/eb/conflict.js +17 -11
  21. package/dist/shortcuts/meeting/operations/eb/decision.js +385 -0
  22. package/dist/shortcuts/meeting/operations/eb/factory.js +222 -35
  23. package/dist/shortcuts/meeting/operations/eb/index.js +18 -1
  24. package/dist/shortcuts/meeting/operations/eb/link.js +212 -0
  25. package/dist/shortcuts/meeting/operations/eb/task-progress.js +123 -0
  26. package/dist/shortcuts/meeting/operations/env.js +78 -61
  27. package/dist/shortcuts/meeting/operations/link.js +125 -0
  28. package/dist/shortcuts/meeting/operations/registry.js +2 -0
  29. package/dist/shortcuts/meeting/operations/shared.js +13 -0
  30. package/dist/shortcuts/workflow/host.js +5 -1
  31. package/dist/shortcuts/workflow/operations/form.js +95 -11
  32. package/dist/shortcuts/workflow/operations/read.js +785 -26
  33. package/docs/e10-auth.md +8 -2
  34. package/docs/hrm.md +34 -2
  35. package/docs/invoice.md +3 -0
  36. package/package.json +2 -2
  37. package/scripts/xiaoe_env.py +118 -0
  38. package/skill-template/domains/hrm.md +2 -1
  39. package/skill-template/skill-template.md +1 -1
  40. package/skills/weaver-e10-blog/SKILL.md +1 -1
  41. package/skills/weaver-e10-caizhilian/SKILL.md +5 -4
  42. package/skills/weaver-e10-caizhilian/references/file-collection.md +37 -0
  43. package/skills/weaver-e10-caizhilian/references/folder.md +5 -1
  44. package/skills/weaver-e10-caizhilian/references/setting-attach.md +35 -5
  45. package/skills/weaver-e10-caizhilian/references/source-manifest.json +620 -5
  46. package/skills/weaver-e10-calendar/SKILL.md +1 -1
  47. package/skills/weaver-e10-ebuilder-form/SKILL.md +1 -1
  48. package/skills/weaver-e10-esb/SKILL.md +1 -1
  49. package/skills/weaver-e10-hrm/SKILL.md +52 -5
  50. package/skills/weaver-e10-hrm/references/org.md +107 -1
  51. package/skills/weaver-e10-jinchengda/SKILL.md +2 -1
  52. package/skills/weaver-e10-jiuchuanhui/SKILL.md +1 -1
  53. package/skills/weaver-e10-jucailin/SKILL.md +1 -1
  54. package/skills/weaver-e10-mail/SKILL.md +1 -1
  55. package/skills/weaver-e10-meeting/SKILL.md +27 -11
  56. package/skills/weaver-e10-meeting/references/meeting-eb.md +162 -7
  57. package/skills/weaver-e10-meeting/references/meeting-verification.md +271 -0
  58. package/skills/weaver-e10-meeting.zip +0 -0
  59. package/skills/weaver-e10-okr/SKILL.md +1 -1
  60. package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
  61. package/skills/weaver-e10-plan/SKILL.md +1 -1
  62. package/skills/weaver-e10-qiyecheng/SKILL.md +1 -1
  63. package/skills/weaver-e10-shared/SKILL.md +10 -13
  64. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +11 -17
  65. package/skills/weaver-e10-shared/references/non-interactive-environment.md +21 -14
  66. package/skills/weaver-e10-shijingran/SKILL.md +1 -1
  67. package/skills/weaver-e10-skill-maker/SKILL.md +1 -1
  68. package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +1 -1
  69. package/skills/weaver-e10-wenshuding/SKILL.md +1 -1
  70. package/skills/weaver-e10-workflow/SKILL.md +19 -19
  71. package/skills/weaver-e10-workflow/references/source-manifest.json +13 -13
  72. package/skills/weaver-e10-workflow/references/workflow-create.md +11 -7
  73. package/skills/weaver-e10-workflow/references/workflow-query.md +9 -14
  74. package/skills/weaver-e10-yepiaotong/SKILL.md +1 -2
  75. package/skills/weaver-e10-yimiaoban/SKILL.md +1 -1
  76. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
  77. package/skills/weaver-e10-ziguanjia/SKILL.md +1 -1
@@ -0,0 +1,385 @@
1
+ /**
2
+ * 会议决议(决议概述 + 决议任务)与签到补签的**语义化操作**。
3
+ *
4
+ * 为什么要封装(2026-09-21 新增,需求:把会议内容汇总成决议任务):
5
+ * EB 原生接口是「多张表 + 多个写接口」的拼装式 API,直接暴露给调用方有三个坑:
6
+ *
7
+ * 1. `create_mt_decision` 只写「决议概述 + 明细」,**不会**改会议自身状态;必须再用
8
+ * `update_mt_isdecision` 写回 `is_decision=2` + `decision_Id` + `mt_status`,
9
+ * 否则会议页仍显示"未决议"、用户会以为决议没生效 —— 而这一步极易被漏掉(真实场景高频)。
10
+ * 2. `sign.create` 与 `sign.update` 字段几乎一致,**补签用错就会新增一条签到记录**(用户实测反馈)。
11
+ * 3. 决议明细一次可能十几条,若不指名"第几条、缺哪个字段",调用方无法定位。
12
+ *
13
+ * 故提供两个语义化入口,把多步串成一次调用(仍走 prepare → 用户确认 → apply 的高风险写入链):
14
+ * - `meeting.eb.decision.publish`:写决议(概述 + 明细)→ 自动写回会议为已决议(含部分失败语义);
15
+ * - `meeting.eb.sign.mark`:按「会议 + 参会人」自动定位签到记录,走 update 补签 / 补签退。
16
+ */
17
+ import { EB_BASE, ebBusinessSuccess, extractEbRows, fingerprint, normalizeEbWrite, queryBody, requireConfirm, verifyLease, writeBody, } from './factory.js';
18
+ import { EB_ENDPOINTS } from './catalog.js';
19
+ import { meetingSuccess } from '../types.js';
20
+ import { meetingPartialError, meetingValidationError } from '../../errors.js';
21
+ import { toIdString } from '../shared.js';
22
+ const CONTINUATION_TTL_SECONDS = 600;
23
+ const DATE_PATTERN = '^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$';
24
+ /** 决议状态:0未决议 / 1决议临时保存 / 2决议提交完成。 */
25
+ const DECISION_DONE = '2';
26
+ /** 会议状态「结束」:产生决议后会议即视为结束。 */
27
+ const MT_STATUS_OVER = '8';
28
+ function requireEndpoint(interfacePk) {
29
+ const endpoint = EB_ENDPOINTS.find((item) => item.interfacePk === interfacePk);
30
+ if (!endpoint)
31
+ throw new Error(`EB catalog 缺少 ${interfacePk},请检查 catalog.ts 声明`);
32
+ return endpoint;
33
+ }
34
+ function businessFailed(response, interfacePk) {
35
+ const datajson = response?.datajson ?? {};
36
+ if (datajson.status === false) {
37
+ throw meetingValidationError('eb_write_not_applied', `${interfacePk} 返回 status=false,服务端未生效,请核对字段与权限后重试`);
38
+ }
39
+ }
40
+ /** 决议明细最少 1 条:没有明细就不会有决议任务,等于白建。 */
41
+ function normalizeDecisionRows(input) {
42
+ if (input === undefined || input === null) {
43
+ throw meetingValidationError('missing_required_field', '缺少决议明细 detail1:至少 1 条(每条决议明细会由 ESB 联动生成一条决议任务)', { node: 'detail1', minRows: 1 });
44
+ }
45
+ if (!Array.isArray(input)) {
46
+ throw meetingValidationError('invalid_detail', 'detail1 必须是数组,每个元素为一条决议明细', { node: 'detail1' });
47
+ }
48
+ return input.map((row, index) => {
49
+ const source = (row && typeof row === 'object' ? row : {});
50
+ return {
51
+ // 明细序号与两个状态字段给默认值:调用方通常不关心,缺了会让列表顺序不稳/状态为空。
52
+ sort_number: String(index + 1),
53
+ decision_status: '0',
54
+ related_type: '0',
55
+ ...source,
56
+ };
57
+ });
58
+ }
59
+ const decisionPublishPrepare = {
60
+ name: 'meeting.eb.decision.publish.prepare',
61
+ resource: 'eb',
62
+ method: 'meeting.eb.decision.publish.prepare',
63
+ action: 'meeting.eb.decision.publish.prepare',
64
+ summary: '准备发布会议决议:写「决议概述 + 明细」,并把会议标记为已决议(生成确认预览,不写入)',
65
+ capability: 'eb.decision',
66
+ risk: 'read-before-write',
67
+ requiresConfirmation: true,
68
+ inputSchema: {
69
+ type: 'object',
70
+ additionalProperties: false,
71
+ properties: {
72
+ mt_id: { type: 'string', description: '会议 id(必填)' },
73
+ mt_content: { type: 'string', description: '决议概述文本(必填):由调用方结合会议议题 / 纪要 / 用户提供的材料汇总' },
74
+ detail1: {
75
+ type: 'array',
76
+ minItems: 1,
77
+ description: '决议明细(至少 1 条):每条 = 一项待办任务,ESB 联动据此生成决议任务;executors 必须传**员工 id**',
78
+ items: {
79
+ type: 'object',
80
+ additionalProperties: false,
81
+ properties: {
82
+ decision_title: { type: 'string', description: '决议标题(必填)' },
83
+ executors: { type: 'string', description: '执行人**员工 id**(必填,多人用逗号分隔)' },
84
+ begin_datetime: { type: 'string', pattern: DATE_PATTERN, description: '开始时间(必填,yyyy-MM-dd HH:mm)' },
85
+ end_datetime: { type: 'string', pattern: DATE_PATTERN, description: '结束时间(必填,yyyy-MM-dd HH:mm)' },
86
+ rummager: { type: 'string', description: '检查人**员工 id**(非必填)' },
87
+ sort_number: { type: 'string', description: '明细序号(省略时按数组顺序自动编号)' },
88
+ decision_status: { type: 'string', description: '决议状态(默认 0 未完成;0未完成/1已完成/2已延期)' },
89
+ related_type: { type: 'string', description: '关联类型(默认 0 未关联;0未关联/1日程/2任务)' },
90
+ relate_task: { type: 'string', description: '关联任务' },
91
+ relate_agenda: { type: 'string', description: '关联日程' },
92
+ },
93
+ required: ['decision_title', 'executors', 'begin_datetime', 'end_datetime'],
94
+ },
95
+ },
96
+ mt_status: { type: 'string', description: `写回会议状态,默认 ${MT_STATUS_OVER}(结束);产生决议即视为会议结束` },
97
+ },
98
+ required: ['mt_id', 'mt_content', 'detail1'],
99
+ },
100
+ handler: async (args, env) => {
101
+ const session = await env.host.getSession();
102
+ const create = normalizeEbWrite(requireEndpoint('create_mt_decision'), {
103
+ mt_id: args.mt_id,
104
+ mt_content: args.mt_content,
105
+ // 注意:**不要传 data_status** —— 2026-09-21 真机实测该接口拒绝该字段
106
+ // (DataError:dataKey:data_status,该字段不存在),提交状态由系统自动置 1。
107
+ detail1: normalizeDecisionRows(args.detail1),
108
+ }, session.userId);
109
+ const mtStatus = args.mt_status === undefined || args.mt_status === null || args.mt_status === ''
110
+ ? MT_STATUS_OVER
111
+ : String(args.mt_status);
112
+ const payload = { create, mtId: toIdString(args.mt_id, 'mt_id'), mtStatus };
113
+ const token = await env.continuation.issue({
114
+ operation: 'meeting.eb.decision.publish.apply',
115
+ baseUrl: session.baseUrl,
116
+ profile: session.profile,
117
+ userId: session.userId,
118
+ payload,
119
+ fingerprint: fingerprint(payload),
120
+ }, CONTINUATION_TTL_SECONDS * 1000);
121
+ return meetingSuccess('meeting.eb.decision.publish.prepare', {
122
+ status: 'AWAITING_CONFIRMATION',
123
+ preview: {
124
+ steps: [
125
+ {
126
+ step: 1,
127
+ interfacePk: 'create_mt_decision',
128
+ path: `${EB_BASE}/saveFormData/create_mt_decision`,
129
+ mainTable: create.mainTable,
130
+ detail1: create.details.detail1 ?? [],
131
+ },
132
+ {
133
+ step: 2,
134
+ interfacePk: 'update_mt_isdecision',
135
+ mainTable: { id: payload.mtId, decision_Id: '<第 1 步返回的 dataIds[0]>', is_decision: DECISION_DONE, mt_status: mtStatus },
136
+ },
137
+ ],
138
+ decisionCount: (create.details.detail1 ?? []).length,
139
+ },
140
+ continuation: token,
141
+ expiresInSeconds: CONTINUATION_TTL_SECONDS,
142
+ }, {
143
+ meta: {
144
+ notes: [
145
+ '第 1 步写入决议概述与明细,第 2 步把会议写回为「已决议(is_decision=2)」并置会议状态。',
146
+ '决议任务由 ESB 联动按明细生成,本操作不直接写任务表。',
147
+ ],
148
+ },
149
+ });
150
+ },
151
+ };
152
+ const decisionPublishApply = {
153
+ name: 'meeting.eb.decision.publish.apply',
154
+ resource: 'eb',
155
+ method: 'meeting.eb.decision.publish.apply',
156
+ action: 'meeting.eb.decision.publish.apply',
157
+ summary: '提交会议决议:写入决议概述 + 明细,并把会议标记为已决议;必须用户明确确认后调用',
158
+ capability: 'eb.decision',
159
+ risk: 'high-risk-write',
160
+ requiresConfirmation: true,
161
+ inputSchema: {
162
+ type: 'object',
163
+ additionalProperties: false,
164
+ properties: {
165
+ confirm: { const: true },
166
+ continuation: { type: 'string', minLength: 1 },
167
+ },
168
+ required: ['confirm', 'continuation'],
169
+ },
170
+ handler: async (args, env) => {
171
+ const applyName = 'meeting.eb.decision.publish.apply';
172
+ requireConfirm(args, applyName);
173
+ const lease = await verifyLease(args, applyName, env);
174
+ const payload = lease.payload;
175
+ if (lease.fingerprint !== fingerprint(payload)) {
176
+ throw meetingValidationError('target_changed', '提交内容与 prepare 时的快照不一致,请重新执行 prepare', {
177
+ expected: lease.payload,
178
+ actual: payload,
179
+ });
180
+ }
181
+ const session = await env.host.getSession();
182
+ const { create, mtId, mtStatus } = payload;
183
+ // 第 1 步:写决议概述 + 明细
184
+ // ⚠️ 真机教训(2026-09-21):EB 写接口**报错不等于没写库** —— 实测一次因字段校验失败
185
+ // (data_status 不被接受)的 create_mt_decision,主表与明细其实已经落库;调用方若把"报错"
186
+ // 当"没写入"并重试,就会产生**重复决议 + 重复任务**(本次实测因此出现了 4 条任务)。
187
+ // 所以这里只要拿不到明确的成功证据,一律按「可能已写入」处理(抛 partial,提示先回查)。
188
+ let createResponse;
189
+ try {
190
+ createResponse = await env.host.request({
191
+ method: 'POST',
192
+ path: `${EB_BASE}/saveFormData/create_mt_decision`,
193
+ operation: applyName,
194
+ body: writeBody(session.userId, create),
195
+ acceptBusiness: ebBusinessSuccess,
196
+ });
197
+ }
198
+ catch (error) {
199
+ throw meetingPartialError(`决议写入请求失败:${error instanceof Error ? error.message : String(error)}。`
200
+ + '注意:EB 接口报错**不代表没有写入**(实测校验失败时主表也可能已落库)。'
201
+ + `请先用 meeting.eb.decision.list({ mt_id: "${mtId}" }) 回查该会议是否已生成决议,`
202
+ + '再决定是重试还是删除,**不要盲目重试以免产生重复决议与重复任务**。', { mtId, attempted: { interfacePk: 'create_mt_decision', body: create } }, { stage: applyName });
203
+ }
204
+ const createData = createResponse?.datajson ?? {};
205
+ if (createData.status === false) {
206
+ throw meetingPartialError('create_mt_decision 返回 status=false(服务端未确认成功)。'
207
+ + 'EB 接口报错/返回 false **不代表没有写入**,请先用 meeting.eb.decision.list({ mt_id: "'
208
+ + `${mtId}" }) 回查是否已生成决议,**不要盲目重试**。`, { mtId, attempted: { interfacePk: 'create_mt_decision', body: create }, response: createResponse }, { stage: applyName });
209
+ }
210
+ const rawIds = createData.dataIds ?? createData.ids;
211
+ const dataIds = Array.isArray(rawIds) ? rawIds : [];
212
+ const decisionId = dataIds.length > 0 ? String(dataIds[0]) : undefined;
213
+ if (!decisionId) {
214
+ // 决议可能已写入但拿不到 id → 无法关联会议。**不能**在这里重试创建(会重复)。
215
+ throw meetingPartialError('决议概述可能已写入,但接口未返回 dataIds(拿不到决议 id),无法自动写回会议。'
216
+ + `请先用 meeting.eb.decision.list({ mt_id: "${mtId}" }) 回查该会议是否已生成决议,再决定是否单独调用 meeting.eb.meeting.markDecided 标记;**不要**重复创建决议。`, { mtId, attempted: { interfacePk: 'create_mt_decision', body: create } }, { stage: applyName });
217
+ }
218
+ // 第 2 步:写回会议(关联决议 + 决议状态 + 会议状态)
219
+ const markMainTable = { id: mtId, decision_Id: decisionId, is_decision: DECISION_DONE, mt_status: mtStatus };
220
+ const markPayload = { mainTable: markMainTable, details: {}, operateType: {} };
221
+ try {
222
+ const markResponse = await env.host.request({
223
+ method: 'POST',
224
+ path: `${EB_BASE}/updateFormData/update_mt_isdecision`,
225
+ operation: applyName,
226
+ // 主键必须走 URL(updateFormData 端点统一要求,2026-09-21 真机实测)
227
+ query: { id: mtId },
228
+ body: writeBody(session.userId, markPayload),
229
+ acceptBusiness: ebBusinessSuccess,
230
+ });
231
+ businessFailed(markResponse, 'update_mt_isdecision');
232
+ }
233
+ catch (error) {
234
+ // 决议已经建成、只是"会议标记"没写上 —— 必须说清楚,否则用户会重复建决议。
235
+ throw meetingPartialError(`决议已创建成功(decision_Id=${decisionId}),但把会议标记为「已决议」失败:`
236
+ + `${error instanceof Error ? error.message : String(error)}。`
237
+ + '请**不要**重复创建决议;可稍后单独调用 meeting.eb.meeting.markDecided 重试标记,或到会议详情页确认。', { mtId, decisionId, attempted: { interfacePk: 'update_mt_isdecision', body: markMainTable } }, { stage: applyName });
238
+ }
239
+ return meetingSuccess(applyName, {
240
+ status: 'COMPLETE',
241
+ mtId,
242
+ decisionId,
243
+ isDecision: DECISION_DONE,
244
+ mtStatus,
245
+ result: createResponse,
246
+ });
247
+ },
248
+ };
249
+ const signMarkPrepare = {
250
+ name: 'meeting.eb.sign.mark.prepare',
251
+ resource: 'eb',
252
+ method: 'meeting.eb.sign.mark.prepare',
253
+ action: 'meeting.eb.sign.mark.prepare',
254
+ summary: '准备补签 / 补签退:按「会议 + 参会人」自动定位签到记录,生成确认预览(不写入)',
255
+ capability: 'eb.sign',
256
+ risk: 'read-before-write',
257
+ requiresConfirmation: true,
258
+ inputSchema: {
259
+ type: 'object',
260
+ additionalProperties: false,
261
+ properties: {
262
+ mt_id: { type: 'string', description: '会议 id(必填)' },
263
+ user_id: { type: 'string', description: '被补签人的**员工 id**(必填)' },
264
+ is_sign: { type: 'string', description: '签到状态:0缺席/1正常/2请假/3迟到/4早退;补签一般传 1' },
265
+ sign_time: { type: 'string', pattern: DATE_PATTERN, description: '签到时间(补签时必给,否则记录为空)' },
266
+ sign_out_status: { type: 'string', description: '签退状态:0已签退/1未签退/2早退' },
267
+ signback_time: { type: 'string', pattern: DATE_PATTERN, description: '签退时间(补签退时给)' },
268
+ sign_remark: { type: 'string', description: '签到日志 / 说明' },
269
+ attend_type: { type: 'string', description: '参会人员类型:0外部/1内部/2召集人/3联系人/4创建人' },
270
+ },
271
+ required: ['mt_id', 'user_id'],
272
+ },
273
+ handler: async (args, env) => {
274
+ const session = await env.host.getSession();
275
+ const mtId = toIdString(args.mt_id, 'mt_id');
276
+ const userId = toIdString(args.user_id, 'user_id');
277
+ const listResponse = await env.host.request({
278
+ method: 'POST',
279
+ path: `${EB_BASE}/getFormDataList/query_meeting_sign_list`,
280
+ operation: 'meeting.eb.sign.mark.prepare',
281
+ query: { mt_id: mtId, signMember: userId, pageNo: 1, pageSize: 50 },
282
+ // 分页必须进 body.pageInfo(EB 只认 body):否则恒返回 10 条,参会人多的会议会找不到签到记录。
283
+ body: queryBody(session.userId, { pageNo: 1, pageSize: 50 }),
284
+ acceptBusiness: ebBusinessSuccess,
285
+ });
286
+ const { rows } = extractEbRows(listResponse);
287
+ const record = rows
288
+ .map((row) => row?.mainTable ?? row)
289
+ .find((main) => String(main?.user_id ?? '') === userId);
290
+ const recordId = record?.id === undefined || record?.id === null ? undefined : String(record.id);
291
+ if (!recordId) {
292
+ // 找不到记录时必须明确停手:改走 create 会新增一条,造成"同一人两条签到"。
293
+ throw meetingValidationError('sign_record_not_found', `未在该会议(${mtId})中找到员工 ${userId} 的签到记录,无法补签。`
294
+ + '请先用 meeting.eb.sign.list 确认该会议是否已生成签到数据(若会议尚无签到记录,属建会初始化问题,需先用 meeting.eb.sign.create 初始化后再补签)。', { mtId, userId });
295
+ }
296
+ const update = normalizeEbWrite(requireEndpoint('update_meeting_sign_info'), { ...args, id: recordId, mt_id: mtId }, session.userId);
297
+ const payload = { mtId, signRecordId: recordId, update };
298
+ const token = await env.continuation.issue({
299
+ operation: 'meeting.eb.sign.mark.apply',
300
+ baseUrl: session.baseUrl,
301
+ profile: session.profile,
302
+ userId: session.userId,
303
+ payload,
304
+ fingerprint: fingerprint(payload),
305
+ }, CONTINUATION_TTL_SECONDS * 1000);
306
+ return meetingSuccess('meeting.eb.sign.mark.prepare', {
307
+ status: 'AWAITING_CONFIRMATION',
308
+ preview: {
309
+ interfacePk: 'update_meeting_sign_info',
310
+ // 主键必须走 URL(真机实测:只在 body 里传 id 会报「主键传入不能为空」)
311
+ path: `${EB_BASE}/updateFormData/update_meeting_sign_info?id=${recordId}`,
312
+ signRecordId: recordId,
313
+ before: { sign_time: record?.sign_time ?? '', is_sign: record?.is_sign ?? '', signback_time: record?.signback_time ?? '' },
314
+ after: update.mainTable,
315
+ },
316
+ continuation: token,
317
+ expiresInSeconds: CONTINUATION_TTL_SECONDS,
318
+ }, {
319
+ meta: {
320
+ notes: [
321
+ '走的是**更新**(update_meeting_sign_info),不会新增签到记录。',
322
+ '未传 sign_time 时签到时间仍为空,请按需补上。',
323
+ ],
324
+ },
325
+ });
326
+ },
327
+ };
328
+ const signMarkApply = {
329
+ name: 'meeting.eb.sign.mark.apply',
330
+ resource: 'eb',
331
+ method: 'meeting.eb.sign.mark.apply',
332
+ action: 'meeting.eb.sign.mark.apply',
333
+ summary: '提交补签 / 补签退(update_meeting_sign_info);必须用户明确确认后调用',
334
+ capability: 'eb.sign',
335
+ risk: 'high-risk-write',
336
+ requiresConfirmation: true,
337
+ inputSchema: {
338
+ type: 'object',
339
+ additionalProperties: false,
340
+ properties: {
341
+ confirm: { const: true },
342
+ continuation: { type: 'string', minLength: 1 },
343
+ },
344
+ required: ['confirm', 'continuation'],
345
+ },
346
+ handler: async (args, env) => {
347
+ const applyName = 'meeting.eb.sign.mark.apply';
348
+ requireConfirm(args, applyName);
349
+ const lease = await verifyLease(args, applyName, env);
350
+ const payload = lease.payload;
351
+ if (lease.fingerprint !== fingerprint(payload)) {
352
+ throw meetingValidationError('target_changed', '提交内容与 prepare 时的快照不一致,请重新执行 prepare', {
353
+ expected: lease.payload,
354
+ actual: payload,
355
+ });
356
+ }
357
+ const session = await env.host.getSession();
358
+ const response = await env.host.request({
359
+ method: 'POST',
360
+ path: `${EB_BASE}/updateFormData/update_meeting_sign_info`,
361
+ operation: applyName,
362
+ // 主键走 URL query(真机实测要求);body 里仍保留 id 以满足文档「id 必填」
363
+ query: { id: payload.signRecordId },
364
+ body: writeBody(session.userId, payload.update),
365
+ acceptBusiness: ebBusinessSuccess,
366
+ });
367
+ businessFailed(response, 'update_meeting_sign_info');
368
+ return meetingSuccess(applyName, {
369
+ status: 'COMPLETE',
370
+ mtId: payload.mtId,
371
+ signRecordId: payload.signRecordId,
372
+ applied: payload.update.mainTable,
373
+ result: response,
374
+ });
375
+ },
376
+ };
377
+ /** 决议 / 补签的语义化 operations。 */
378
+ export const meetingEbDecisionOperations = [
379
+ decisionPublishPrepare,
380
+ decisionPublishApply,
381
+ signMarkPrepare,
382
+ signMarkApply,
383
+ ];
384
+ /** 供测试与调用方复用的常量(避免魔法值散落)。 */
385
+ export const EB_DECISION_CONSTANTS = { DECISION_DONE, MT_STATUS_OVER };