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
|
@@ -32,6 +32,42 @@ function formatSecondNow() {
|
|
|
32
32
|
const DECISION_DONE = '2';
|
|
33
33
|
/** 会议状态「结束」:产生决议后会议即视为结束。 */
|
|
34
34
|
const MT_STATUS_OVER = '8';
|
|
35
|
+
/**
|
|
36
|
+
* **被取消过的会议**在「建决议 → 立刻标记已决议」时,ESB 会把同一批明细生成**两轮**任务。
|
|
37
|
+
*
|
|
38
|
+
* 2026-10-27 真机隔离(6 组对照):
|
|
39
|
+
* | 会议状态 | create → mark 间隔 | 任务数(1 条明细) |
|
|
40
|
+
* |---|---|---|
|
|
41
|
+
* | 正常(未开始) | 立刻 | 1 ✅ |
|
|
42
|
+
* | 曾被取消 | 立刻 | **2** ❌ |
|
|
43
|
+
* | 曾被取消 | 500ms / 1s / 2s / 3s | 1 ✅ |
|
|
44
|
+
* | 曾被取消 | mark 前先只建决议、隔 3s 再标记 | 1 ✅ |
|
|
45
|
+
* | 正常发布后再取消 | — | 1 ✅(取消本身不重触发) |
|
|
46
|
+
*
|
|
47
|
+
* 窗口很短(<500ms),故对"曾取消"的会议在两步之间插 800ms 余量;正常会议不加延迟。
|
|
48
|
+
*/
|
|
49
|
+
const CANCELLED_MARK_DELAY_MS = 800;
|
|
50
|
+
/** 会议是否处于「已取消」状态(mt_status=4/名称含"取消",或 operate_type 仍留着 CANCEL)。读不到就按未取消处理。 */
|
|
51
|
+
async function isCancelledMeeting(env, mtId) {
|
|
52
|
+
try {
|
|
53
|
+
const { getMeetingOperation } = await import('../registry.js');
|
|
54
|
+
const listed = await getMeetingOperation('meeting.eb.meeting.list').handler(
|
|
55
|
+
// withCount:false —— 这里只要一行状态,不必额外请求 count(少一次往返,也便于测试替身)
|
|
56
|
+
{ mt_id: mtId, pageNo: 1, pageSize: 1, withCount: false }, env);
|
|
57
|
+
const row = (listed?.data?.rows ?? [])[0] ?? {};
|
|
58
|
+
const main = row.mainTable ?? row;
|
|
59
|
+
const status = main?.mt_status;
|
|
60
|
+
const statusId = typeof status === 'object' && status !== null ? String(status.id ?? '') : String(status ?? '');
|
|
61
|
+
const statusName = typeof status === 'object' && status !== null ? String(status.name ?? '') : String(status ?? '');
|
|
62
|
+
// ⚠️ 实测:取消后再发布决议,mt_status 会被写成"结束",唯独 operate_type 一直留着 CANCEL
|
|
63
|
+
// ⇒ 只判 mt_status 会漏(我第一次改就漏了),必须同时看 operate_type。
|
|
64
|
+
const operateType = String(main?.operate_type ?? '').toUpperCase();
|
|
65
|
+
return statusId === '4' || statusName.includes('取消') || operateType.includes('CANCEL');
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
35
71
|
function requireEndpoint(interfacePk) {
|
|
36
72
|
const endpoint = EB_ENDPOINTS.find((item) => item.interfacePk === interfacePk);
|
|
37
73
|
if (!endpoint)
|
|
@@ -187,6 +223,9 @@ const decisionPublishApply = {
|
|
|
187
223
|
}
|
|
188
224
|
const session = await env.host.getSession();
|
|
189
225
|
const { create, mtId, mtStatus } = payload;
|
|
226
|
+
// 被取消过的会议:两步之间必须留间隔,否则 ESB 会把同一批明细生成两轮任务(见 CANCELLED_MARK_DELAY_MS)
|
|
227
|
+
const cancelled = await isCancelledMeeting(env, mtId);
|
|
228
|
+
const warnings = [];
|
|
190
229
|
// 第 1 步:写决议概述 + 明细
|
|
191
230
|
// ⚠️ 真机教训(2026-09-21):EB 写接口**报错不等于没写库** —— 实测一次因字段校验失败
|
|
192
231
|
// (data_status 不被接受)的 create_mt_decision,主表与明细其实已经落库;调用方若把"报错"
|
|
@@ -223,6 +262,13 @@ const decisionPublishApply = {
|
|
|
223
262
|
+ `请先用 meeting.eb.decision.list({ mt_id: "${mtId}" }) 回查该会议是否已生成决议,再决定是否单独调用 meeting.eb.meeting.markDecided 标记;**不要**重复创建决议。`, { mtId, attempted: { interfacePk: 'create_mt_decision', body: create } }, { stage: applyName });
|
|
224
263
|
}
|
|
225
264
|
// 第 2 步:写回会议(关联决议 + 决议状态 + 会议状态)
|
|
265
|
+
// ⚠️ 曾取消的会议:紧接着调用会让 ESB 触发两轮(真机:1 条明细 → 2 条任务)。
|
|
266
|
+
// 实测 500ms 即够,这里留 800ms 余量;正常会议不走这个分支,不增加耗时。
|
|
267
|
+
if (cancelled) {
|
|
268
|
+
await new Promise((resolve) => { setTimeout(resolve, CANCELLED_MARK_DELAY_MS); });
|
|
269
|
+
warnings.push('该会议此前被取消过:已在「写决议」与「标记已决议」之间留 '
|
|
270
|
+
+ `${CANCELLED_MARK_DELAY_MS}ms 间隔,避免 ESB 把同一批明细生成两轮任务(真机验证过的坑)`);
|
|
271
|
+
}
|
|
226
272
|
const markMainTable = { id: mtId, decision_Id: decisionId, is_decision: DECISION_DONE, mt_status: mtStatus };
|
|
227
273
|
const markPayload = { mainTable: markMainTable, details: {}, operateType: {} };
|
|
228
274
|
try {
|
|
@@ -250,7 +296,7 @@ const decisionPublishApply = {
|
|
|
250
296
|
isDecision: DECISION_DONE,
|
|
251
297
|
mtStatus,
|
|
252
298
|
result: createResponse,
|
|
253
|
-
});
|
|
299
|
+
}, warnings.length > 0 ? { warnings } : {});
|
|
254
300
|
},
|
|
255
301
|
};
|
|
256
302
|
const signMarkPrepare = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { meetingConfirmationError, meetingPartialError, meetingValidationError } from '../../errors.js';
|
|
3
3
|
import { meetingSuccess, SCHEMA_ID } from '../types.js';
|
|
4
|
-
import { assertDateTimeMinute, assertDateTimeSecond, toIdString } from '../shared.js';
|
|
4
|
+
import { assertDate, assertDateTimeMinute, assertDateTimeSecond, toIdString } from '../shared.js';
|
|
5
5
|
import { isWriteKind } from './catalog.js';
|
|
6
6
|
/** EB(eBuilder formdata)通用路径前缀(资料核实)。 */
|
|
7
7
|
export const EB_BASE = '/api/ebuilder/form/formdata/v2';
|
|
@@ -109,6 +109,11 @@ export function extractEbRows(response) {
|
|
|
109
109
|
const total = typeof dj?.total === 'number' ? dj.total : typeof value?.total === 'number' ? value.total : undefined;
|
|
110
110
|
return { rows: raw, ...(total === undefined ? {} : { total }) };
|
|
111
111
|
}
|
|
112
|
+
// EB 个别接口会把单条记录的对象(而非数组)直接放进 datas —— 按一行处理,
|
|
113
|
+
// 否则会静默变成 0 行,调用方拿着"未找到"继续往下走(2026-10-28 排查明细透出时的同类隐患)。
|
|
114
|
+
if (raw !== null && typeof raw === 'object') {
|
|
115
|
+
return { rows: [raw] };
|
|
116
|
+
}
|
|
112
117
|
return { rows: [] };
|
|
113
118
|
}
|
|
114
119
|
/** 从 count 响应中取总数(datajson.datas 为数值)。 */
|
|
@@ -174,12 +179,22 @@ function buildReadOperation(endpoint) {
|
|
|
174
179
|
// EB 的 getFormDataByPk 与 list 一致,把数据放在 `datajson.datas[].mainTable`。
|
|
175
180
|
// 早期实现直接返回整个 datajson → data 变成 `{datas,status}`(2026-09-21 真机发现)。
|
|
176
181
|
const { rows } = extractEbRows(response);
|
|
177
|
-
const
|
|
182
|
+
const row = rows[0] ?? null;
|
|
183
|
+
const data = row?.mainTable ?? response?.datajson?.mainTable ?? null;
|
|
178
184
|
if (data === undefined || data === null) {
|
|
179
185
|
// 不存在的 id 必须明确报「未找到」,否则调用方会拿着空对象继续往下走。
|
|
180
186
|
throw meetingValidationError('not_found', `${endpoint.interfacePk} 未找到 id=${id} 的数据(可能已被删除,或当前账号无权限)`, { id, interfacePk: endpoint.interfacePk });
|
|
181
187
|
}
|
|
182
|
-
|
|
188
|
+
// 行内 mainTable 之外的节点即明细(detail1/detail2/…)。接口配置了返回明细时
|
|
189
|
+
// 会出现在这里——原实现直接丢弃,导致"接口明明回了明细、CLI 却看不见"(2026-10-28)。
|
|
190
|
+
const detailNodes = {};
|
|
191
|
+
if (row) {
|
|
192
|
+
for (const [key, value] of Object.entries(row)) {
|
|
193
|
+
if (key !== 'mainTable')
|
|
194
|
+
detailNodes[key] = value;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return meetingSuccess(endpoint.operation, { id, data, ...(Object.keys(detailNodes).length > 0 ? { details: detailNodes } : {}) }, endpoint.notes ? { meta: { notes: endpoint.notes } } : {});
|
|
183
198
|
}
|
|
184
199
|
const query = {};
|
|
185
200
|
for (const param of endpoint.queryParams ?? []) {
|
|
@@ -248,15 +263,30 @@ function buildReadOperation(endpoint) {
|
|
|
248
263
|
: summarized !== null && typeof summarized === 'object'
|
|
249
264
|
? { ...summarized, page: pageMeta }
|
|
250
265
|
: { value: summarized, page: pageMeta };
|
|
266
|
+
// ⚠️ 分页提示:本页条数 < 总数时明确告诉调用方"还有没取到的",
|
|
267
|
+
// 避免只看本页就下结论(2026-10-23 我本人就因此误判"该环境只有 10 个会议",
|
|
268
|
+
// 实际 total=46,只是第一页 + stdout 预览把数组截到 10 条)。
|
|
269
|
+
// 注意:EB 服务端**不会**压低 pageSize(pageSize=50/200 都能一次拿全 46 条),
|
|
270
|
+
// 所以这里只提示翻页/加大 pageSize,不要写成"服务端限制"。
|
|
271
|
+
const effectiveTotal = Math.max(resolvedTotal, rows.length);
|
|
272
|
+
const pageWarnings = [];
|
|
273
|
+
if (effectiveTotal > rows.length) {
|
|
274
|
+
const missing = effectiveTotal - rows.length;
|
|
275
|
+
pageWarnings.push(`本页返回 ${rows.length} 条,共 ${effectiveTotal} 条:还有 ${missing} 条未取到。`
|
|
276
|
+
+ `需要全量请翻页(pageNo=${pageNo + 1} 起)或加大 pageSize,或收窄筛选条件`);
|
|
277
|
+
}
|
|
251
278
|
return meetingSuccess(endpoint.operation, {
|
|
252
|
-
total:
|
|
279
|
+
total: effectiveTotal,
|
|
253
280
|
pageNo,
|
|
254
281
|
pageSize,
|
|
255
282
|
count: rows.length,
|
|
256
283
|
...(progress ? { progress } : {}),
|
|
257
284
|
rows,
|
|
258
285
|
filters: query,
|
|
259
|
-
},
|
|
286
|
+
}, {
|
|
287
|
+
...(endpoint.notes ? { meta: { notes: endpoint.notes } } : {}),
|
|
288
|
+
...(pageWarnings.length > 0 ? { warnings: pageWarnings } : {}),
|
|
289
|
+
});
|
|
260
290
|
},
|
|
261
291
|
};
|
|
262
292
|
}
|
|
@@ -271,6 +301,8 @@ function writePkQuery(endpoint, payload) {
|
|
|
271
301
|
// delete 一律把主键放 URL:真机实测 `delete_roomusage` 只在 body 传 id 会报「主键传入不能为空」
|
|
272
302
|
// (与 `update_meeting_sign_info` 同因);update 按接口的 pkQueryParam 声明。
|
|
273
303
|
// 多带一个 `?id=` 对已支持 body 主键的接口无害(`room.delete` / `update_room` 均已实测通过)。
|
|
304
|
+
if (endpoint.kind === 'delete' && endpoint.batchDeleteFields)
|
|
305
|
+
return undefined;
|
|
274
306
|
const param = endpoint.pkQueryParam ?? (endpoint.kind === 'delete' ? 'id' : undefined);
|
|
275
307
|
if (!param)
|
|
276
308
|
return undefined;
|
|
@@ -352,7 +384,9 @@ function normalizeDetailRow(spec, row, index) {
|
|
|
352
384
|
* 只报“缺少必填字段”会让调用方无从下手。
|
|
353
385
|
*/
|
|
354
386
|
export function normalizeEbWrite(endpoint, args, userId) {
|
|
355
|
-
const fields = endpoint.kind === 'delete'
|
|
387
|
+
const fields = endpoint.kind === 'delete'
|
|
388
|
+
? (endpoint.batchDeleteFields ?? ['id'])
|
|
389
|
+
: endpoint.fields ?? [];
|
|
356
390
|
const mainTable = {};
|
|
357
391
|
for (const field of fields) {
|
|
358
392
|
const value = args[field];
|
|
@@ -364,10 +398,23 @@ export function normalizeEbWrite(endpoint, args, userId) {
|
|
|
364
398
|
else if ((endpoint.secondDateFields ?? []).includes(field)) {
|
|
365
399
|
mainTable[field] = assertDateTimeSecond(value, field);
|
|
366
400
|
}
|
|
401
|
+
else if ((endpoint.dateOnlyFields ?? []).includes(field)) {
|
|
402
|
+
// 纯日期(如议题的 proposal_date,真机存的是 2026-10-23)
|
|
403
|
+
mainTable[field] = assertDate(value, field);
|
|
404
|
+
}
|
|
367
405
|
else {
|
|
368
406
|
mainTable[field] = value;
|
|
369
407
|
}
|
|
370
408
|
}
|
|
409
|
+
// create 的字段默认值:EB 漏传 Select 字段时**不是留空**,而是落成接口配置的默认项
|
|
410
|
+
// (真机:create_room 漏传 status 建出「封存」的会议室)。只对 create 生效,
|
|
411
|
+
// update 补默认值等于擅自改用户没提的字段。
|
|
412
|
+
if (endpoint.kind === 'create' && endpoint.fieldDefaults) {
|
|
413
|
+
for (const [field, value] of Object.entries(endpoint.fieldDefaults)) {
|
|
414
|
+
if (mainTable[field] === undefined)
|
|
415
|
+
mainTable[field] = value;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
371
418
|
if (endpoint.interfacePk === 'create_meeting') {
|
|
372
419
|
if (mainTable.caller === undefined)
|
|
373
420
|
mainTable.caller = userId;
|
|
@@ -379,7 +426,9 @@ export function normalizeEbWrite(endpoint, args, userId) {
|
|
|
379
426
|
if (hours !== undefined)
|
|
380
427
|
mainTable.mt_hours = hours;
|
|
381
428
|
}
|
|
382
|
-
const required = endpoint.kind === 'delete'
|
|
429
|
+
const required = endpoint.kind === 'delete'
|
|
430
|
+
? (endpoint.batchDeleteFields ?? ['id'])
|
|
431
|
+
: endpoint.requiredFields ?? [];
|
|
383
432
|
const missing = required.filter((field) => mainTable[field] === undefined);
|
|
384
433
|
if (missing.length > 0) {
|
|
385
434
|
throw meetingValidationError('missing_required_field', `缺少必填字段:${missing.join(', ')}`, { missing });
|
|
@@ -404,7 +453,26 @@ export function normalizeEbWrite(endpoint, args, userId) {
|
|
|
404
453
|
if (rows.length > 0)
|
|
405
454
|
details[spec.node] = rows;
|
|
406
455
|
}
|
|
407
|
-
|
|
456
|
+
// 明细写入模式:默认「更新追加」(EB 不传 operateType 时的行为);只有声明了 detailCover 的接口才允许 cover。
|
|
457
|
+
const operateType = {};
|
|
458
|
+
const detailMode = args.detail1Mode;
|
|
459
|
+
if (detailMode !== undefined && detailMode !== null && detailMode !== '') {
|
|
460
|
+
if (!endpoint.detailCover) {
|
|
461
|
+
throw meetingValidationError('unsupported_detail_mode', `${endpoint.interfacePk} 不支持 detail1Mode(该接口未声明明细覆盖能力),请去掉该参数按默认追加写入`, { interfacePk: endpoint.interfacePk });
|
|
462
|
+
}
|
|
463
|
+
if (detailMode !== 'append' && detailMode !== 'cover') {
|
|
464
|
+
throw meetingValidationError('invalid_detail_mode', `detail1Mode 只能是 append(追加,默认)或 cover(覆盖:传几行就只剩几行)`, { detail1Mode: String(detailMode) });
|
|
465
|
+
}
|
|
466
|
+
if (detailMode === 'cover') {
|
|
467
|
+
// 只对**真正带了明细**的节点生效:没传 detail1 却要求 cover,等同于"清空全部明细",不允许。
|
|
468
|
+
if (Object.keys(details).length === 0) {
|
|
469
|
+
throw meetingValidationError('invalid_detail_mode', 'detail1Mode=cover 时必须同时传 detail1(至少 1 行):cover 会覆盖整组明细,空数组等于把明细全删掉', { node: 'detail1' });
|
|
470
|
+
}
|
|
471
|
+
for (const node of Object.keys(details))
|
|
472
|
+
operateType[node] = 'cover';
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return { mainTable, details, operateType };
|
|
408
476
|
}
|
|
409
477
|
function buildWriteInputSchema(endpoint, includeConfirm) {
|
|
410
478
|
const properties = {};
|
|
@@ -413,7 +481,15 @@ function buildWriteInputSchema(endpoint, includeConfirm) {
|
|
|
413
481
|
properties.continuation = { type: 'string', minLength: 1 };
|
|
414
482
|
}
|
|
415
483
|
if (endpoint.kind === 'delete') {
|
|
416
|
-
|
|
484
|
+
if (endpoint.batchDeleteFields && endpoint.batchDeleteFields.length > 0) {
|
|
485
|
+
// 批量删除:按业务条件删(如 delete_roomusage 的 mt_id),不是主键
|
|
486
|
+
for (const field of endpoint.batchDeleteFields) {
|
|
487
|
+
properties[field] = { type: 'string', description: `${field}(删除条件,该接口按它批量删除)` };
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
properties.id = { ...SCHEMA_ID, description: '数据主键 id(必填)' };
|
|
492
|
+
}
|
|
417
493
|
}
|
|
418
494
|
else {
|
|
419
495
|
for (const field of endpoint.fields ?? []) {
|
|
@@ -448,9 +524,16 @@ function buildWriteInputSchema(endpoint, includeConfirm) {
|
|
|
448
524
|
},
|
|
449
525
|
};
|
|
450
526
|
}
|
|
527
|
+
if (endpoint.detailCover) {
|
|
528
|
+
properties.detail1Mode = {
|
|
529
|
+
type: 'string',
|
|
530
|
+
enum: ['append', 'cover'],
|
|
531
|
+
description: '明细写入模式:append=追加(默认,老明细保留)/cover=覆盖(整组替换,传几行就只剩几行)',
|
|
532
|
+
};
|
|
533
|
+
}
|
|
451
534
|
}
|
|
452
535
|
const requiredBase = endpoint.kind === 'delete'
|
|
453
|
-
? ['id']
|
|
536
|
+
? (endpoint.batchDeleteFields ?? ['id'])
|
|
454
537
|
: [
|
|
455
538
|
...(endpoint.requiredFields ?? []),
|
|
456
539
|
...(endpoint.details ?? []).filter((spec) => (spec.minRows ?? 0) > 0).map((spec) => spec.node),
|
|
@@ -464,6 +547,22 @@ function buildWriteInputSchema(endpoint, includeConfirm) {
|
|
|
464
547
|
required: includeConfirm ? ['confirm', 'continuation'] : requiredBase,
|
|
465
548
|
};
|
|
466
549
|
}
|
|
550
|
+
/** 查员工所属部门 id(`create_mt_topic` 的 `proposal_dept` 默认值用)。失败静默返回 null,不阻塞创建。 */
|
|
551
|
+
async function lookupEmployeeDepartmentId(env, userId) {
|
|
552
|
+
try {
|
|
553
|
+
const { hrmEmployeeOperations } = await import('../../../hrm/operations/employee.js');
|
|
554
|
+
const batch = hrmEmployeeOperations.find((operation) => operation.name === 'hrm.employee.batch');
|
|
555
|
+
if (!batch)
|
|
556
|
+
return null;
|
|
557
|
+
const result = await batch.handler({ ids: [userId] }, env);
|
|
558
|
+
const row = (result?.data?.rows ?? [])[0];
|
|
559
|
+
const deptId = String(row?.departmentId ?? '').trim();
|
|
560
|
+
return deptId || null;
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
467
566
|
function buildWriteOperations(endpoint, preflight, afterWrite) {
|
|
468
567
|
const prepareName = `${endpoint.operation}.prepare`;
|
|
469
568
|
const applyName = `${endpoint.operation}.apply`;
|
|
@@ -484,8 +583,26 @@ function buildWriteOperations(endpoint, preflight, afterWrite) {
|
|
|
484
583
|
handler: async (args, env) => {
|
|
485
584
|
const session = await env.host.getSession();
|
|
486
585
|
const payload = normalizeEbWrite(endpoint, args, session.userId);
|
|
586
|
+
// 议题库创建默认值(2026-10-28 用户口径):提议人员/提议单位不该留空——
|
|
587
|
+
// 默认当前人员 + 其所属部门 id(查不到部门就静默跳过,不阻塞创建)。
|
|
588
|
+
if (endpoint.interfacePk === 'create_mt_topic') {
|
|
589
|
+
if (payload.mainTable.proposal_person === undefined)
|
|
590
|
+
payload.mainTable.proposal_person = session.userId;
|
|
591
|
+
if (payload.mainTable.proposal_dept === undefined) {
|
|
592
|
+
const deptId = await lookupEmployeeDepartmentId(env, session.userId);
|
|
593
|
+
if (deptId)
|
|
594
|
+
payload.mainTable.proposal_dept = deptId;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
487
597
|
// EB 版建会缺少强制冲突检测会重复占用会议室/参会人撞会,故 prepare 阶段由钩子补齐。
|
|
488
|
-
const
|
|
598
|
+
const extras = {};
|
|
599
|
+
const warnings = preflight ? await preflight({ endpoint, args, payload, env, extras }) : [];
|
|
600
|
+
// 支持明细覆盖的接口(如 update_mt_decision)默认是「更新追加」:真机实测同 sort_number 也会**新增一行**。
|
|
601
|
+
// 调用方最容易在这里理解错,故 prepare 就明确提示,而不是等回读发现多了一行。
|
|
602
|
+
if (endpoint.detailCover && Object.keys(payload.details).length > 0 && payload.operateType.detail1 !== 'cover') {
|
|
603
|
+
warnings.push('detail1 默认是「更新追加」:本次会新增明细行,**不会**覆盖 sort_number 相同的旧行'
|
|
604
|
+
+ '(真机实测:2 行 → 传 1 行 → 变 3 行)。要整组替换请传 detail1Mode: "cover"');
|
|
605
|
+
}
|
|
489
606
|
const token = await env.continuation.issue({
|
|
490
607
|
operation: applyName,
|
|
491
608
|
baseUrl: session.baseUrl,
|
|
@@ -503,6 +620,7 @@ function buildWriteOperations(endpoint, preflight, afterWrite) {
|
|
|
503
620
|
path: pathWithPk(endpoint, path, payload),
|
|
504
621
|
notes: endpoint.notes ?? [],
|
|
505
622
|
},
|
|
623
|
+
...(Object.keys(extras).length > 0 ? { decisions: extras } : {}),
|
|
506
624
|
continuation: token,
|
|
507
625
|
expiresInSeconds: CONTINUATION_TTL_SECONDS,
|
|
508
626
|
}, warnings.length > 0 ? { warnings } : {});
|
|
@@ -3,6 +3,10 @@ import { buildEbOperations } from './factory.js';
|
|
|
3
3
|
import { ebMeetingWritePreflight, meetingEbConflictOperations } from './conflict.js';
|
|
4
4
|
import { meetingEbDecisionOperations } from './decision.js';
|
|
5
5
|
import { ebMeetingAssociation } from './association.js';
|
|
6
|
+
import { ebRoomRecommendOperation, recommendRooms, rememberFrequentRoom } from './room-recommend.js';
|
|
7
|
+
import { meetingValidationError } from '../../errors.js';
|
|
8
|
+
import { meetingEbScheduleOperations } from './schedule.js';
|
|
9
|
+
import { meetingEbCreateFlowOperations } from './create-flow.js';
|
|
6
10
|
/**
|
|
7
11
|
* 会议 EB 版(eBuilder)operations。
|
|
8
12
|
*
|
|
@@ -28,12 +32,128 @@ import { ebMeetingAssociation } from './association.js';
|
|
|
28
32
|
* - `ebMeetingAssociation` 挂到 apply 之后:`create_meeting` 只写会议主表,**不会**生成
|
|
29
33
|
* 占用 / 回执 / 签到,而这三样是会议室冲突检测、参会人冲突检测与补签的硬依赖,故由 CLI 补齐
|
|
30
34
|
* (见 association.ts:主写入已成功,补建失败只回报 warnings,不中断)。
|
|
35
|
+
*
|
|
36
|
+
* 日程空闲 + 会议室推荐 + 建会自动化(2026-10-28 新增,用户明确要求):
|
|
37
|
+
* - `meetingEbScheduleOperations`:`meeting.eb.schedule.free` 查**日程应用**(ESB 动作流),
|
|
38
|
+
* 与 `conflict.member`(查会议表)是两件事,别混;
|
|
39
|
+
* - `ebRoomRecommendOperation`:按人数/要求/个人常用/时段占用推荐会议室,含两级本地缓存;
|
|
40
|
+
* - `ebAutoRoomPreflight`:建会没填会议室 → 自动推荐并写入(在冲突检测之前,选中房间照样过校验);
|
|
41
|
+
* 关掉用 `WEAVER_MEETING_AUTO_ROOM=0`;
|
|
42
|
+
* - `ebAutoInvitationAfterWrite`:建会成功后**直接生成邀请函**,路径在 `data.association.invitation`,
|
|
43
|
+
* 失败只回 warning 不中断;关掉用 `WEAVER_MEETING_AUTO_INVITATION=0`。
|
|
31
44
|
*/
|
|
45
|
+
/** 建会未指定会议室时的自动推荐(包在冲突检测外层:先选房,再按选中房间校验冲突)。 */
|
|
46
|
+
const ebAutoRoomPreflight = async (context) => {
|
|
47
|
+
const { extras } = context;
|
|
48
|
+
const warnings = [];
|
|
49
|
+
const enabled = String(process.env.WEAVER_MEETING_AUTO_ROOM ?? '1') !== '0';
|
|
50
|
+
const isCreate = context.endpoint.kind === 'create' && context.endpoint.interfacePk === 'create_meeting';
|
|
51
|
+
// 会议室策略(用户口径):**不替用户决定** —— 没填会议室就列候选让用户选。
|
|
52
|
+
// - WEAVER_MEETING_AUTO_ROOM=1 显式开启"自动选推荐间"(自动化场景)
|
|
53
|
+
// - WEAVER_MEETING_AUTO_ROOM=0 不推荐也不拦(线上会/无会议室场景)
|
|
54
|
+
// - 未设置(默认) 返回 session 级候选,prepare 抛 room_selection_required 带候选
|
|
55
|
+
const mode = String(process.env.WEAVER_MEETING_AUTO_ROOM ?? '').trim();
|
|
56
|
+
if (isCreate && !context.payload.mainTable.address && mode !== '0') {
|
|
57
|
+
const begin = String(context.payload.mainTable.begin_datetime ?? '').trim();
|
|
58
|
+
const end = String(context.payload.mainTable.end_datetime ?? '').trim();
|
|
59
|
+
const memberCount = String(context.payload.mainTable.hrm_members ?? '')
|
|
60
|
+
.split(/[,;,;\s]+/)
|
|
61
|
+
.map((item) => item.trim())
|
|
62
|
+
.filter((item) => item !== '').length;
|
|
63
|
+
if (begin && end) {
|
|
64
|
+
try {
|
|
65
|
+
const result = await recommendRooms(context.env, {
|
|
66
|
+
beginDatetime: begin,
|
|
67
|
+
endDatetime: end,
|
|
68
|
+
...(memberCount > 0 ? { memberCount } : {}),
|
|
69
|
+
topN: 5,
|
|
70
|
+
});
|
|
71
|
+
const roomOptions = result.candidates.map((candidate, index) => ({
|
|
72
|
+
key: `R${index + 1}`,
|
|
73
|
+
label: `${candidate.name}${candidate.capacity.max > 0 ? `(可容 ${candidate.capacity.max} 人)` : ''}`
|
|
74
|
+
+ `${index === 0 ? ' · 推荐' : ''}`,
|
|
75
|
+
value: { id: candidate.id, name: candidate.name },
|
|
76
|
+
reason: candidate.reasons.join(';'),
|
|
77
|
+
}));
|
|
78
|
+
extras.autoRoom = {
|
|
79
|
+
picked: null,
|
|
80
|
+
candidates: result.candidates,
|
|
81
|
+
meta: result.meta,
|
|
82
|
+
};
|
|
83
|
+
extras.roomOptions = roomOptions;
|
|
84
|
+
if (mode === '1' && result.best) {
|
|
85
|
+
// 仅显式开启时才自动写入
|
|
86
|
+
context.payload.mainTable.address = result.best.id;
|
|
87
|
+
extras.autoRoom = {
|
|
88
|
+
picked: { id: result.best.id, name: result.best.name, score: result.best.score, reasons: result.best.reasons },
|
|
89
|
+
candidates: result.candidates,
|
|
90
|
+
meta: result.meta,
|
|
91
|
+
};
|
|
92
|
+
warnings.push(`已按 WEAVER_MEETING_AUTO_ROOM=1 自动选择「${result.best.name}」(${result.best.reasons.join(';')});`
|
|
93
|
+
+ '不认可请重新 prepare 并显式传 address。');
|
|
94
|
+
}
|
|
95
|
+
else if (roomOptions.length > 0) {
|
|
96
|
+
throw meetingValidationError('room_selection_required', `未指定会议室:请从 ${roomOptions.length} 个候选中选择一个(按人数/常用/时段占用排序),`
|
|
97
|
+
+ '把选中的 id 作为 address 重新 prepare。若这场会不需要会议室,设置 WEAVER_MEETING_AUTO_ROOM=0 后重试。', { roomOptions, meta: result.meta });
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
warnings.push(...result.warnings);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (error?.subtype === 'room_selection_required')
|
|
105
|
+
throw error;
|
|
106
|
+
warnings.push(`会议室推荐失败(${String(error?.message ?? error).slice(0, 80)}):本次不填会议室继续,请与用户确认地点`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
warnings.push('会议开始/结束时间不完整,跳过会议室推荐');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const conflictWarnings = await ebMeetingWritePreflight(context);
|
|
114
|
+
warnings.push(...conflictWarnings);
|
|
115
|
+
return warnings;
|
|
116
|
+
};
|
|
117
|
+
/** 建会成功后自动生成邀请函(失败只回报,绝不中断建会)。 */
|
|
118
|
+
const ebAutoInvitationAfterWrite = async (context) => {
|
|
119
|
+
const association = await ebMeetingAssociation(context);
|
|
120
|
+
// 建会成功即把该会议室计入「个人常用」缓存:用户今天连开几场,推荐偏好当天就变,
|
|
121
|
+
// 不必等 7 天 TTL 过期重算(2026-10-28 用户明确要求)。
|
|
122
|
+
if (context.endpoint.interfacePk === 'create_meeting') {
|
|
123
|
+
const roomId = String(context.payload.mainTable.address ?? '').trim();
|
|
124
|
+
if (roomId)
|
|
125
|
+
await rememberFrequentRoom(context.env, roomId).catch(() => false);
|
|
126
|
+
}
|
|
127
|
+
const enabled = String(process.env.WEAVER_MEETING_AUTO_INVITATION ?? '1') !== '0';
|
|
128
|
+
const isCreate = context.endpoint.kind === 'create' && context.endpoint.interfacePk === 'create_meeting';
|
|
129
|
+
const mtId = String(context.dataIds?.[0] ?? '').trim();
|
|
130
|
+
if (!isCreate || !enabled || !mtId)
|
|
131
|
+
return association;
|
|
132
|
+
try {
|
|
133
|
+
const { getMeetingOperation } = await import('../registry.js');
|
|
134
|
+
const render = getMeetingOperation('meeting.invitation.render');
|
|
135
|
+
const result = await render.handler({ mtId }, context.env);
|
|
136
|
+
return { ...(association ?? {}), invitation: result?.data ?? null };
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return {
|
|
140
|
+
...(association ?? {}),
|
|
141
|
+
invitation: {
|
|
142
|
+
ok: false,
|
|
143
|
+
error: String(error?.message ?? error).slice(0, 200),
|
|
144
|
+
hint: '会议已建好;邀请函生成失败不影响建会,可稍后单独调用 meeting.invitation.render',
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
};
|
|
32
149
|
export const meetingEbOperations = [
|
|
33
150
|
...buildEbOperations(EB_ENDPOINTS, {
|
|
34
|
-
preflight:
|
|
35
|
-
afterWrite:
|
|
151
|
+
preflight: ebAutoRoomPreflight,
|
|
152
|
+
afterWrite: ebAutoInvitationAfterWrite,
|
|
36
153
|
}),
|
|
37
154
|
...meetingEbConflictOperations,
|
|
38
155
|
...meetingEbDecisionOperations,
|
|
156
|
+
...meetingEbScheduleOperations,
|
|
157
|
+
...meetingEbCreateFlowOperations,
|
|
158
|
+
ebRoomRecommendOperation,
|
|
39
159
|
];
|