weaver-work-cli 0.1.33 → 0.1.38
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-FVFY6GZN.js → archive-PDV724YK.js} +5 -4
- package/dist/chunks/{archive-oa-NW3OD4UV.js → archive-oa-4XE4MNXQ.js} +4 -3
- package/dist/chunks/{asset-7QJKMIDV.js → asset-PDL2QE2N.js} +8 -7
- package/dist/chunks/{attachment-content-OSAVA6K3.js → attachment-content-MDDGOE5E.js} +3 -3
- package/dist/chunks/{blog-I7SDPNW4.js → blog-UYR3EUXW.js} +5 -4
- package/dist/chunks/{caizhilian-O37ZIQNY.js → caizhilian-LG2X5NMF.js} +5 -4
- package/dist/chunks/{calendar-7KXE4ED3.js → calendar-FG4BAZHE.js} +4 -3
- package/dist/chunks/{chunk-Q6LWB5HX.js → chunk-6FTTSA45.js} +4 -3
- package/dist/chunks/{chunk-MQ2I5AA3.js → chunk-6LTFIFLM.js} +3 -2
- package/dist/chunks/{chunk-WRRJ4GG2.js → chunk-CORFRD7Q.js} +4 -3
- package/dist/chunks/{chunk-WY74UBJ6.js → chunk-DMV2TOMH.js} +2194 -240
- package/dist/chunks/{chunk-MEEFGWTM.js → chunk-ELYDWBZJ.js} +135 -10
- package/dist/chunks/{chunk-EEWPQE3O.js → chunk-FUWVHMVV.js} +7 -6
- package/dist/chunks/{chunk-HEL5XTYI.js → chunk-GGJFMNJM.js} +26 -4
- package/dist/chunks/{chunk-KAS45KNR.js → chunk-IZUDA353.js} +1 -1
- package/dist/chunks/{chunk-2FLWXFVS.js → chunk-J7USS3ED.js} +4 -4
- package/dist/chunks/{chunk-IHUYP6LO.js → chunk-JSMFPO43.js} +1 -1
- package/dist/chunks/{chunk-RBGOMK4Y.js → chunk-KKRL3REV.js} +5 -4
- package/dist/chunks/{chunk-OCU4RLHC.js → chunk-NI6GIPIM.js} +195 -36
- package/dist/chunks/{chunk-PRHJGPMA.js → chunk-NR6RKXPA.js} +2 -2
- package/dist/chunks/{chunk-D223PTXU.js → chunk-OJQUJZQF.js} +33 -7
- package/dist/chunks/{chunk-ILMBNVSG.js → chunk-UJJD4QWF.js} +4 -3
- package/dist/chunks/{chunk-6ATGWRZM.js → chunk-UZZBA6PV.js} +177 -14
- package/dist/chunks/{chunk-GFPXQIY2.js → chunk-VRYHGG6O.js} +2 -2
- package/dist/chunks/{chunk-NHA62QD7.js → chunk-XJY7G4DD.js} +3 -3
- package/dist/chunks/{chunk-VBCFFFGT.js → chunk-Z32KWY6G.js} +4 -1
- package/dist/chunks/{conflict-SFNEVJY5.js → conflict-BQZEQ4SP.js} +2 -2
- package/dist/chunks/contract-XIRAFTEV.js +9111 -0
- package/dist/chunks/{cowork-CKWYHXYB.js → cowork-BLG62BL2.js} +46 -26
- package/dist/chunks/{ebuilder-form-KOLJRVZD.js → ebuilder-form-NUKG5QW4.js} +3 -3
- package/dist/chunks/{ehr-WFOC3B4D.js → ehr-2STW5ILH.js} +174 -10
- package/dist/chunks/{esb-LJZTN663.js → esb-UUDQUNKY.js} +4 -4
- package/dist/chunks/{fna-F7QRIIZC.js → fna-6RCY4NU5.js} +15 -9
- package/dist/chunks/{host-VQ6JJXGQ.js → host-7RXEDMAH.js} +3 -3
- package/dist/chunks/{host-KXPTEWFS.js → host-EJZFROU3.js} +3 -3
- package/dist/chunks/{host-BCGUVEWX.js → host-FWDOSRH5.js} +3 -3
- package/dist/chunks/{host-DGE2TZD4.js → host-RHM3NPXV.js} +3 -3
- package/dist/chunks/{host-BQBRHVR3.js → host-UFF33TWJ.js} +3 -3
- package/dist/chunks/{hrm-NI6XAKW5.js → hrm-PKJ6ELSH.js} +3 -3
- package/dist/chunks/{im-7TRTF3KT.js → im-YJAXS7CO.js} +3 -3
- package/dist/chunks/{invoice-AOXWJKYT.js → invoice-HWVSXGX6.js} +3 -3
- package/dist/chunks/{jiuchuanhui-7NQWEZAW.js → jiuchuanhui-L7VKILZB.js} +4 -3
- package/dist/chunks/{lazy-dispatch-RNS4EE4S.js → lazy-dispatch-NMYEIHRH.js} +4 -4
- package/dist/chunks/{mail-VDHJAB4O.js → mail-B42RRU7G.js} +46 -24
- package/dist/chunks/{meeting-GA7XCNNP.js → meeting-CFYVBJSS.js} +77 -12
- package/dist/chunks/{okr-TQIRERPQ.js → okr-GCLF6V7W.js} +4 -3
- package/dist/chunks/{plan-4RY4LN2P.js → plan-ZVL7GBRI.js} +4 -3
- package/dist/chunks/{project-D6XT2OC5.js → project-4YWAAE2K.js} +4 -3
- package/dist/chunks/{registry-QKMI43Q6.js → registry-EP4VTHZS.js} +8 -8
- package/dist/chunks/{registry-JHRIDQ3A.js → registry-FRJ6H7BT.js} +4 -4
- package/dist/chunks/{registry-VOGKFFTR.js → registry-SG2PGVCF.js} +2 -2
- package/dist/chunks/{room-recommend-D4EUOOWY.js → room-recommend-R3JVUUAC.js} +4 -4
- package/dist/chunks/{schedule-XPFBHPCV.js → schedule-NIJBTYDO.js} +4 -4
- package/dist/chunks/{workflow-HLBZRWIY.js → workflow-BB24DFBY.js} +11 -4
- package/dist/index.js +31 -26
- package/dist/internal/e10/auth-recovery.js +114 -1
- package/dist/internal/e10/client.js +16 -2
- package/dist/internal/e10/fetch-timing.js +31 -0
- package/dist/internal/e10/request-runtime.js +108 -7
- package/dist/shortcuts/archive/host.js +3 -2
- package/dist/shortcuts/archive-oa/host.js +2 -1
- package/dist/shortcuts/asset/host.js +6 -5
- package/dist/shortcuts/blog/host.js +3 -2
- package/dist/shortcuts/caizhilian/host.js +3 -2
- package/dist/shortcuts/calendar/host.js +2 -1
- package/dist/shortcuts/contract/host.js +2 -1
- package/dist/shortcuts/cowork/host.js +10 -1
- package/dist/shortcuts/cowork/operations/types.js +17 -2
- package/dist/shortcuts/ebuilder-form/host.js +3 -1
- package/dist/shortcuts/ehr/host.js +2 -1
- package/dist/shortcuts/ehr/operations/recruit-write.js +35 -0
- package/dist/shortcuts/ehr/operations/recruit.js +147 -0
- package/dist/shortcuts/esb/host.js +2 -1
- package/dist/shortcuts/fna/host.js +2 -1
- package/dist/shortcuts/fna/operations/expense-chart.js +19 -7
- package/dist/shortcuts/hrm/host.js +2 -1
- package/dist/shortcuts/im/host.js +3 -2
- package/dist/shortcuts/index.js +5 -0
- package/dist/shortcuts/invoice/host.js +5 -4
- package/dist/shortcuts/jiuchuanhui/host.js +2 -1
- package/dist/shortcuts/mail/host.js +10 -1
- package/dist/shortcuts/mail/operations/types.js +17 -2
- package/dist/shortcuts/meeting/host.js +3 -2
- package/dist/shortcuts/meeting/index.js +79 -1
- package/dist/shortcuts/meeting/invitation/text.js +71 -0
- package/dist/shortcuts/meeting/manifest.js +10 -0
- package/dist/shortcuts/meeting/operations/eb/catalog.js +5 -0
- package/dist/shortcuts/meeting/operations/eb/conflict.js +6 -1
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +2105 -169
- package/dist/shortcuts/meeting/operations/eb/crm-search.js +478 -0
- package/dist/shortcuts/meeting/operations/eb/factory.js +161 -8
- package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +36 -2
- package/dist/shortcuts/meeting/operations/eb-fallback.js +4 -0
- package/dist/shortcuts/meeting/operations/env.js +6 -0
- package/dist/shortcuts/meeting/operations/invitation.js +200 -28
- package/dist/shortcuts/okr/host.js +2 -1
- package/dist/shortcuts/plan/host.js +2 -1
- package/dist/shortcuts/project/host.js +2 -1
- package/dist/shortcuts/workflow/endpoints.js +8 -3
- package/dist/shortcuts/workflow/operations/form.js +341 -36
- package/dist/shortcuts/workflow/operations.js +23 -1
- package/docs/ehr.md +1 -1
- package/docs/fna.md +2 -1
- package/package.json +1 -1
- package/skill-template/business-info.json +18 -0
- package/skill-template/domains/cowork.md +14 -0
- package/skills/weaver-e10-cowork/SKILL.md +101 -0
- package/skills/weaver-e10-cowork/references/cowork-read.md +90 -0
- package/skills/weaver-e10-cowork/references/cowork-write.md +131 -0
- package/skills/weaver-e10-cowork/references/safety-boundaries.md +45 -0
- package/skills/weaver-e10-jucailin/SKILL.md +21 -3
- package/skills/weaver-e10-jucailin/references/recruit-read.md +25 -0
- package/skills/weaver-e10-jucailin/references/recruit-write.md +13 -0
- package/skills/weaver-e10-jucailin/references/salary.md +15 -11
- package/skills/weaver-e10-mail/SKILL.md +12 -9
- package/skills/weaver-e10-meeting/SKILL.md +499 -300
- package/skills/weaver-e10-meeting/references/meeting-eb.md +334 -0
- package/skills/weaver-e10-meeting/references/meeting-invitation.md +64 -6
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- package/skills/weaver-e10-qiyecheng/SKILL.md +5 -2
- package/skills/weaver-e10-qiyecheng/references/catalog.md +2 -2
- package/skills/weaver-e10-qiyecheng/references/expense-chart.md +15 -1
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +22 -21
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
|
@@ -13,20 +13,37 @@
|
|
|
13
13
|
* - 数据源必须是 `meeting.eb.meeting.list` 单查(47 字段);
|
|
14
14
|
* `meeting.eb.meeting.detail` 在该环境只回 `{id}`,不可用。
|
|
15
15
|
*/
|
|
16
|
+
import { existsSync } from 'node:fs';
|
|
16
17
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
17
18
|
import { dirname, join, resolve } from 'node:path';
|
|
18
19
|
import { fileURLToPath } from 'node:url';
|
|
19
20
|
import { meetingValidationError } from '../errors.js';
|
|
20
21
|
import { meetingStateDir } from './env.js';
|
|
21
22
|
import { renderDocx } from '../invitation/docx.js';
|
|
23
|
+
import { renderInvitationMarkdown } from '../invitation/text.js';
|
|
22
24
|
import { meetingSuccess, SCHEMA_ID } from './types.js';
|
|
23
25
|
/** 模板随 CLI 包分发(`templates/` 已在 package.json 的 files 白名单内)。 */
|
|
24
26
|
function templatePath() {
|
|
25
27
|
const override = process.env.WEAVER_MEETING_INVITATION_TEMPLATE;
|
|
26
28
|
if (override)
|
|
27
29
|
return resolve(override);
|
|
28
|
-
//
|
|
30
|
+
// ⛔ 不能写死「向上四级」:同一个源文件在两种产物里的位置不一样 ——
|
|
31
|
+
// · tsc 直出:<pkg>/dist/shortcuts/meeting/operations/ → 向上四级是包根
|
|
32
|
+
// · bundle 打包:<pkg>/dist/chunks/chunk-xxxx.js → 向上两级才是包根
|
|
33
|
+
// 2026-10-30 真机踩过:小e 跑的是 bundle 版,写死四级会算出 <pkg>/../templates/… 而报「模板缺失」。
|
|
34
|
+
// 所以直接逐级向上找,找到第一个存在的就是它。
|
|
29
35
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
let dir = here;
|
|
37
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
38
|
+
const candidate = join(dir, 'templates', 'meeting', 'invitation.docx');
|
|
39
|
+
if (existsSync(candidate))
|
|
40
|
+
return candidate;
|
|
41
|
+
const parent = dirname(dir);
|
|
42
|
+
if (parent === dir)
|
|
43
|
+
break;
|
|
44
|
+
dir = parent;
|
|
45
|
+
}
|
|
46
|
+
// 都没找到:仍按传统层级拼一个路径返回,让报错信息能指向具体位置
|
|
30
47
|
return resolve(here, '../../../../templates/meeting/invitation.docx');
|
|
31
48
|
}
|
|
32
49
|
const WEEKDAYS = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
|
|
@@ -234,8 +251,80 @@ async function resolveIssuerOrganization(env, creatorId) {
|
|
|
234
251
|
return null;
|
|
235
252
|
}
|
|
236
253
|
}
|
|
237
|
-
/**
|
|
238
|
-
|
|
254
|
+
/**
|
|
255
|
+
* 取当前登录人的姓名 —— 会议既没填召集人、又没有创建人名时,用**发起人本人**兜底。
|
|
256
|
+
*
|
|
257
|
+
* 理由:邀请函里「XXX 诚邀您参加…」这句必须有个人名,否则通篇【待补充】没法看。
|
|
258
|
+
* 实际业务上发起建会的人就是召集人,比留空更接近事实(2026-10-31 用户对【待补充】有意见)。
|
|
259
|
+
*/
|
|
260
|
+
async function resolveSessionUserName(env) {
|
|
261
|
+
try {
|
|
262
|
+
const session = await env.host.getSession();
|
|
263
|
+
const userId = String(session?.userId ?? '').trim();
|
|
264
|
+
if (!userId)
|
|
265
|
+
return '';
|
|
266
|
+
const { hrmEmployeeOperations } = await import('../../hrm/operations/employee.js');
|
|
267
|
+
const batch = hrmEmployeeOperations.find((operation) => operation.name === 'hrm.employee.batch');
|
|
268
|
+
if (!batch)
|
|
269
|
+
return '';
|
|
270
|
+
const result = await batch.handler({ ids: [userId] }, env);
|
|
271
|
+
const row = (result?.data?.rows ?? [])[0];
|
|
272
|
+
return String(row?.name ?? '').trim();
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return '';
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* 用 `meeting.eb.meeting.detail` 补列表单查里**空着**的字段。
|
|
280
|
+
*
|
|
281
|
+
* 为什么需要(2026-10-31 真机取证):同一个会议
|
|
282
|
+
* · `meeting.eb.meeting.list`(单查)→ `hrm_members = ""`
|
|
283
|
+
* · `meeting.eb.meeting.detail` → `hrm_members = "王丹丹"`
|
|
284
|
+
* 邀请函原先只读 list,于是"明明有参会人却写成【待补充】"—— 这正是用户投诉过的
|
|
285
|
+
* 「参会人员没有带出来」。detail 的价值在于**字段更多且有值**,代价是 `address` 回的是
|
|
286
|
+
* 会议室 id(不是名称),所以这里**只补白名单字段、且只填空值**,绝不覆盖 list 已有的值。
|
|
287
|
+
*/
|
|
288
|
+
const DETAIL_FALLBACK_KEYS = [
|
|
289
|
+
'hrm_members', 'mt_desc', 'total_member', 'caller', 'contacter',
|
|
290
|
+
'begin_datetime', 'end_datetime', 'other_members', 'mt_creator',
|
|
291
|
+
'topic_presenter', 'mt_format', 'mt_type', 'video_url',
|
|
292
|
+
'org_members', 'with_secauth',
|
|
293
|
+
];
|
|
294
|
+
async function supplementRowFromDetail(row, mtId, env) {
|
|
295
|
+
const isBlank = (value) => {
|
|
296
|
+
if (value === undefined || value === null)
|
|
297
|
+
return true;
|
|
298
|
+
if (Array.isArray(value))
|
|
299
|
+
return value.length === 0;
|
|
300
|
+
return String(value).trim() === '';
|
|
301
|
+
};
|
|
302
|
+
const missing = DETAIL_FALLBACK_KEYS.filter((key) => isBlank(row[key]));
|
|
303
|
+
if (missing.length === 0)
|
|
304
|
+
return { row, filled: [] };
|
|
305
|
+
try {
|
|
306
|
+
const { getMeetingOperation } = await import('./registry.js');
|
|
307
|
+
const detailOperation = getMeetingOperation('meeting.eb.meeting.detail');
|
|
308
|
+
const result = await detailOperation.handler({ id: mtId }, env);
|
|
309
|
+
// detail 的字段是**扁平**挂在 `data.data` 下(不是 mainTable),两种都兜一下
|
|
310
|
+
const flat = (result?.data?.data ?? result?.data ?? {});
|
|
311
|
+
const merged = { ...row };
|
|
312
|
+
const filled = [];
|
|
313
|
+
for (const key of missing) {
|
|
314
|
+
const value = flat[key];
|
|
315
|
+
if (!isBlank(value)) {
|
|
316
|
+
merged[key] = value;
|
|
317
|
+
filled.push(key);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return { row: merged, filled };
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
// detail 读失败不能拖垮邀请函:列表数据照样能出一份(缺的字段仍标【待补充】)
|
|
324
|
+
return { row, filled: [] };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/** 组装占位符解析器:派生字段 → 会议标准字段 → extra(租户自定义字段)→ 待补充。 */ export function buildResolver(row, extra, issuerOrg) {
|
|
239
328
|
const begin = parseDateTime(row.begin_datetime);
|
|
240
329
|
const end = parseDateTime(row.end_datetime);
|
|
241
330
|
const derived = {
|
|
@@ -281,6 +370,9 @@ export function buildResolver(row, extra, issuerOrg) {
|
|
|
281
370
|
org_members: orgMembers,
|
|
282
371
|
// 落款公司全称:`extra.company_name` 可覆盖(对外开放时客户传自己的公司名)
|
|
283
372
|
company_name: String(extra.company_name ?? '').trim() || DEFAULT_COMPANY_NAME,
|
|
373
|
+
// 客户单位(「尊敬的 XXX」):先看 extra(建会时由 create.flow 把参会客户名带进来),
|
|
374
|
+
// 再看会议主表的 crm_members(部分租户会存),都没有才留【待补充】
|
|
375
|
+
guest_org: String(extra.guest_org ?? '').trim() || flattenFieldValue(row.crm_members) || '',
|
|
284
376
|
// 单独暴露上级公司,模板需要「公司级落款」时可直接用
|
|
285
377
|
issuer_company: issuerCompany,
|
|
286
378
|
other_members: humanizeMembers(row.other_members),
|
|
@@ -304,8 +396,14 @@ export function buildResolver(row, extra, issuerOrg) {
|
|
|
304
396
|
const lookup = (key) => {
|
|
305
397
|
if (key in derived)
|
|
306
398
|
return derived[key];
|
|
307
|
-
if (key in standard)
|
|
308
|
-
|
|
399
|
+
if (key in standard) {
|
|
400
|
+
const standardValue = standard[key];
|
|
401
|
+
// ⛔ 会议里该字段**空着**时要继续往 extra 找(2026-10-31):
|
|
402
|
+
// 以前 `key in standard` 直接返回空串,导致「会议没填召集人 → extra.caller 也压不上」,
|
|
403
|
+
// 明明调用方补了值还是渲染成【待补充】。空值不算"有值",让 extra 有机会兜底。
|
|
404
|
+
if (String(standardValue ?? '').trim() !== '')
|
|
405
|
+
return standardValue;
|
|
406
|
+
}
|
|
309
407
|
if (key.startsWith('extra.') && key.slice(6) in extra)
|
|
310
408
|
return extra[key.slice(6)];
|
|
311
409
|
if (key in extra)
|
|
@@ -337,14 +435,27 @@ const meetingInvitationRender = {
|
|
|
337
435
|
resource: 'invitation',
|
|
338
436
|
method: 'render',
|
|
339
437
|
action: 'meeting.invitation.render',
|
|
340
|
-
summary: '
|
|
438
|
+
summary: '按会议真实数据生成邀请函(只读,不发送):**同时返回 docx 与对话内可展示的 Markdown 全文**(`text` 字段),'
|
|
439
|
+
+ '会话里应直接把 `text` 贴出来,不要只给文件名让人自己下载;'
|
|
440
|
+
+ '数据取 meeting.eb.meeting.list 单查;议程有议题就按议题自动填、没有则保留模板示例行;'
|
|
441
|
+
+ '取不到的字段渲染成【待补充】(docx 里是黄色高亮);租户自定义字段用 extra 传入',
|
|
341
442
|
capability: 'invitation',
|
|
342
443
|
risk: 'read',
|
|
343
444
|
inputSchema: {
|
|
344
445
|
type: 'object',
|
|
345
446
|
additionalProperties: false,
|
|
346
447
|
properties: {
|
|
347
|
-
mtId: { ...SCHEMA_ID, description: '会议 id(EB 版 mt_detail
|
|
448
|
+
mtId: { ...SCHEMA_ID, description: '会议 id(EB 版 mt_detail 主键)。**与下面的字段二选一**:有 mtId 走真实会议数据;没有则用传入字段直接生成(流程建会审批中也能出邀请函)' },
|
|
449
|
+
mt_name: { type: 'string', description: '会议名称(无 mtId 时必填)' },
|
|
450
|
+
begin_datetime: { type: 'string', description: '开始时间 `yyyy-MM-dd HH:mm`(无 mtId 时必填)' },
|
|
451
|
+
end_datetime: { type: 'string', description: '结束时间 `yyyy-MM-dd HH:mm`(无 mtId 时建议填,缺了结束时间会是【待补充】)' },
|
|
452
|
+
address: { type: 'string', description: '会议地点/会议室**名称**(无 mtId 时用;这里要传名称,不是 id)' },
|
|
453
|
+
hrm_members: { type: 'string', description: '参会人姓名(逗号/顿号分隔,无 mtId 时用)' },
|
|
454
|
+
mt_desc: { type: 'string', description: '会议内容/议题概要' },
|
|
455
|
+
org_members: { type: 'string', description: '落款单位(不传则尝试用发起人部门 / 留【待补充】)' },
|
|
456
|
+
caller: { type: 'string', description: '召集人' },
|
|
457
|
+
contacter: { type: 'string', description: '联系人' },
|
|
458
|
+
total_member: { type: 'string', description: '参会人数' },
|
|
348
459
|
extra: {
|
|
349
460
|
type: 'object',
|
|
350
461
|
additionalProperties: { type: 'string' },
|
|
@@ -355,13 +466,10 @@ const meetingInvitationRender = {
|
|
|
355
466
|
description: '输出文件完整路径(可选)。缺省写到 ~/.weaver-work-cli/meeting/invitations/邀请函-{会议名}-{日期}.docx',
|
|
356
467
|
},
|
|
357
468
|
},
|
|
358
|
-
required: [
|
|
469
|
+
required: [],
|
|
359
470
|
},
|
|
360
471
|
handler: async (args, env) => {
|
|
361
472
|
const mtId = String(args.mtId ?? '').trim();
|
|
362
|
-
if (!mtId) {
|
|
363
|
-
throw meetingValidationError('mt_id_required', '必须提供会议 id(mtId)');
|
|
364
|
-
}
|
|
365
473
|
const extra = {};
|
|
366
474
|
if (args.extra && typeof args.extra === 'object') {
|
|
367
475
|
for (const [key, value] of Object.entries(args.extra)) {
|
|
@@ -369,20 +477,68 @@ const meetingInvitationRender = {
|
|
|
369
477
|
extra[key] = String(value);
|
|
370
478
|
}
|
|
371
479
|
}
|
|
372
|
-
// 复用已实现的 EB 列表操作(内部先 count 再 list,筛选走 URL query)
|
|
373
|
-
const { getMeetingOperation } = await import('./registry.js');
|
|
374
|
-
const listOperation = getMeetingOperation('meeting.eb.meeting.list');
|
|
375
480
|
let row;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
481
|
+
/** meeting=按真实会议数据;payload=按调用方传入的申请单字段(会议可能还没生成)。 */
|
|
482
|
+
let dataSource = 'meeting';
|
|
483
|
+
/** 用 detail 补回来的字段名(列表单查会丢「参会人员」,见下方注释)。 */
|
|
484
|
+
let detailSupplementary = [];
|
|
485
|
+
if (!mtId) {
|
|
486
|
+
// ⛔ 邀请函本质是「预置函」:项目人员下载后自己改、自己发(2026-10-30 用户明确要求)。
|
|
487
|
+
// 所以不该卡在"会议必须已生成"——流程还在审批时,用申请单已知信息先生成一份,
|
|
488
|
+
// 取不到的字段渲染成【待补充】,让人工补。这也保证流程建会提交后立刻能拿到邀请函。
|
|
489
|
+
const mtName = String(args.mt_name ?? '').trim();
|
|
490
|
+
const beginText = String(args.begin_datetime ?? '').trim();
|
|
491
|
+
if (!mtName || !beginText) {
|
|
492
|
+
throw meetingValidationError('invitation_source_required', '要么传 mtId(按真实会议生成),要么传 mt_name + begin_datetime(按申请单信息预填生成)');
|
|
493
|
+
}
|
|
494
|
+
// 无 mtId 时落款单位取当前登录用户的部门(比让用户手填更省事),取不到再留【待补充】
|
|
495
|
+
const session = await env.host.getSession().catch(() => null);
|
|
496
|
+
const sessionUserId = String(session?.userId ?? '');
|
|
497
|
+
row = {
|
|
498
|
+
mt_name: mtName,
|
|
499
|
+
begin_datetime: beginText,
|
|
500
|
+
end_datetime: String(args.end_datetime ?? '').trim(),
|
|
501
|
+
address: String(args.address ?? '').trim(),
|
|
502
|
+
hrm_members: String(args.hrm_members ?? '').trim(),
|
|
503
|
+
mt_desc: String(args.mt_desc ?? '').trim(),
|
|
504
|
+
org_members: String(args.org_members ?? '').trim(),
|
|
505
|
+
caller: String(args.caller ?? '').trim(),
|
|
506
|
+
contacter: String(args.contacter ?? '').trim(),
|
|
507
|
+
total_member: String(args.total_member ?? '').trim(),
|
|
508
|
+
creator: sessionUserId,
|
|
509
|
+
};
|
|
510
|
+
dataSource = 'payload';
|
|
380
511
|
}
|
|
381
|
-
|
|
382
|
-
|
|
512
|
+
else {
|
|
513
|
+
// 复用已实现的 EB 列表操作(内部先 count 再 list,筛选走 URL query)
|
|
514
|
+
const { getMeetingOperation } = await import('./registry.js');
|
|
515
|
+
const listOperation = getMeetingOperation('meeting.eb.meeting.list');
|
|
516
|
+
try {
|
|
517
|
+
const listed = await listOperation.handler({ mt_id: mtId, pageNo: 1, pageSize: 1 }, env);
|
|
518
|
+
const rows = (listed.data?.rows ?? []);
|
|
519
|
+
row = rows[0]?.mainTable ?? rows[0];
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
522
|
+
throw meetingValidationError('invitation_source_unavailable', `读取会议数据失败(邀请函当前依赖 EB 版会议列表):${error instanceof Error ? error.message : String(error)}`, { mtId });
|
|
523
|
+
}
|
|
524
|
+
if (!row) {
|
|
525
|
+
throw meetingValidationError('not_found', `未找到会议 id=${mtId},或当前账号无权限查看`, { mtId });
|
|
526
|
+
}
|
|
527
|
+
// 列表单查在真机上会**丢「参会人员」**(2026-10-31 实测:同一会议 list 里 `hrm_members=""`,
|
|
528
|
+
// detail 里是「王丹丹」)⇒ 只对**空字段**用 detail 补,避免邀请函把明明有的人写成【待补充】。
|
|
529
|
+
const supplement = await supplementRowFromDetail(row, mtId, env);
|
|
530
|
+
row = supplement.row;
|
|
531
|
+
detailSupplementary = supplement.filled;
|
|
383
532
|
}
|
|
384
|
-
|
|
385
|
-
|
|
533
|
+
// 召集人再兜一层:会议没填 caller、也没有创建人名时,用**发起人本人**(当前登录人)。
|
|
534
|
+
// 否则邀请函开头「XXX 诚邀您参加…」整句就是【待补充】,对话里贴出来很难看。
|
|
535
|
+
let callerFromSession = false;
|
|
536
|
+
if (!String(row.caller ?? '').trim() && !String(row.mt_creator ?? '').trim()) {
|
|
537
|
+
const sessionName = await resolveSessionUserName(env);
|
|
538
|
+
if (sessionName) {
|
|
539
|
+
row = { ...row, caller: sessionName };
|
|
540
|
+
callerFromSession = true;
|
|
541
|
+
}
|
|
386
542
|
}
|
|
387
543
|
// 落款单位回落:会议 org_members 为空时,用「发起人(creator 人员 id)的所属组织」
|
|
388
544
|
const creatorId = String(row.creator ?? '').trim();
|
|
@@ -390,7 +546,7 @@ const meetingInvitationRender = {
|
|
|
390
546
|
const issuerOrg = needsIssuerOrg ? await resolveIssuerOrganization(env, creatorId) : null;
|
|
391
547
|
const { resolver, orgSource } = buildResolver(row, extra, issuerOrg);
|
|
392
548
|
// 议程:议题表 → extra.agenda → 保留模板里的示例行(用户口径:没有就这样)
|
|
393
|
-
let agendaRows = await loadAgendaFromTopics(env, mtId);
|
|
549
|
+
let agendaRows = dataSource === 'meeting' && mtId ? await loadAgendaFromTopics(env, mtId) : [];
|
|
394
550
|
let agendaSource = agendaRows.length > 0 ? 'meeting.topic' : 'template';
|
|
395
551
|
if (agendaRows.length === 0) {
|
|
396
552
|
const fromExtra = parseAgendaFromExtra(extra.agenda);
|
|
@@ -409,6 +565,10 @@ const meetingInvitationRender = {
|
|
|
409
565
|
const rendered = renderDocx(template, resolver, {
|
|
410
566
|
agenda: { headerKeyword: AGENDA_HEADER_KEYWORD, rows: agendaRows },
|
|
411
567
|
});
|
|
568
|
+
// 对话内文本版(2026-10-31 用户要求「直接展示在对话里」):
|
|
569
|
+
// docx 是给打印/外发的成品,但会话里用户看不见内容 ⇒ 同一份 resolver 再渲染一遍 Markdown,
|
|
570
|
+
// 上层(create.flow / 模型回复)可以直接把 text 贴出来,字段与 docx 完全一致。
|
|
571
|
+
const textVersion = renderInvitationMarkdown(resolver, agendaRows);
|
|
412
572
|
const begin = parseDateTime(row.begin_datetime);
|
|
413
573
|
const dateTag = begin ? `${begin.getFullYear()}${pad(begin.getMonth() + 1)}${pad(begin.getDate())}` : '未知日期';
|
|
414
574
|
const fileName = `邀请函-${safeFileName(String(row.mt_name ?? ''))}-${dateTag}.docx`;
|
|
@@ -418,6 +578,9 @@ const meetingInvitationRender = {
|
|
|
418
578
|
await mkdir(dirname(target), { recursive: true });
|
|
419
579
|
await writeFile(target, rendered.buffer);
|
|
420
580
|
const warnings = [];
|
|
581
|
+
if (dataSource === 'payload') {
|
|
582
|
+
warnings.push('本邀请函按**申请单已知信息**预生成(会议可能还在审批中、尚未生成),属于**预置函**:请下载后人工核对/修改再自行发送。会议正式生成后可用 mtId 重新生成一版拿到完整字段。');
|
|
583
|
+
}
|
|
421
584
|
if (rendered.pending.length > 0) {
|
|
422
585
|
warnings.push(`以下字段取不到值,已渲染成黄色高亮【待补充】,请人工补充:${rendered.pending.join('、')}`);
|
|
423
586
|
}
|
|
@@ -438,16 +601,25 @@ const meetingInvitationRender = {
|
|
|
438
601
|
filePath: target,
|
|
439
602
|
fileName,
|
|
440
603
|
bytes: rendered.buffer.length,
|
|
441
|
-
meetingId: mtId,
|
|
604
|
+
meetingId: mtId || null,
|
|
442
605
|
meetingName: String(row.mt_name ?? ''),
|
|
606
|
+
// meeting=按真实会议数据;payload=按申请单字段预生成(会议可能还没生成,属预置函)
|
|
607
|
+
dataSource,
|
|
608
|
+
// 列表单查丢字段时,用 detail 补回来的字段名(有值 = 邀请函用的是合并后的数据)
|
|
609
|
+
detailSupplemented: detailSupplementary,
|
|
610
|
+
// 对话内展示用的 Markdown 全文(与 docx 同源)。上层应直接把这段贴进回复正文,
|
|
611
|
+
// ⛔ 不要只丢一个 filePath 让用户自己去本地打开看内容。
|
|
612
|
+
text: textVersion.markdown,
|
|
443
613
|
filledFields: rendered.filled,
|
|
444
614
|
pendingFields: rendered.pending,
|
|
445
615
|
// 落款单位的实际来源,便于调用方判断是否需要人工改写
|
|
446
616
|
orgSource,
|
|
447
|
-
// 召集人来源:meeting.caller=会议自己填的 / meeting.mt_creator=回落到创建人 / none=都没有
|
|
448
|
-
callerSource:
|
|
449
|
-
? '
|
|
450
|
-
: (String(row.
|
|
617
|
+
// 召集人来源:meeting.caller=会议自己填的 / meeting.mt_creator=回落到创建人 / session.user=回落到发起人本人 / none=都没有
|
|
618
|
+
callerSource: callerFromSession
|
|
619
|
+
? 'session.user'
|
|
620
|
+
: (String(row.caller ?? '').trim()
|
|
621
|
+
? 'meeting.caller'
|
|
622
|
+
: (String(row.mt_creator ?? '').trim() ? 'meeting.mt_creator' : 'none')),
|
|
451
623
|
// 议程来源:meeting.topic=按议题自动填 / extra=调用方传入 / template=保留模板示例行
|
|
452
624
|
agendaSource,
|
|
453
625
|
agendaRows: agendaRows.length,
|
|
@@ -6,6 +6,7 @@ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js
|
|
|
6
6
|
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
7
7
|
import { requireE10Session } from '../../internal/e10/context.js';
|
|
8
8
|
import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
9
|
+
import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
|
|
9
10
|
import { okrStateRoot } from './continuation.js';
|
|
10
11
|
import { OkrCliError, okrAuthError, okrNetworkError, okrValidationError } from './errors.js';
|
|
11
12
|
function inferProfile(ctx) {
|
|
@@ -143,7 +144,7 @@ export function createOkrHost(ctx, dependencies = {}) {
|
|
|
143
144
|
catch (error) {
|
|
144
145
|
mapRuntimeNetworkError(error);
|
|
145
146
|
}
|
|
146
|
-
if (response
|
|
147
|
+
if (isE10SessionExpiredResponse(response)) {
|
|
147
148
|
throw okrAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
148
149
|
}
|
|
149
150
|
if (response.parseError) {
|
|
@@ -6,6 +6,7 @@ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js
|
|
|
6
6
|
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
7
7
|
import { requireE10Session } from '../../internal/e10/context.js';
|
|
8
8
|
import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
9
|
+
import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
|
|
9
10
|
import { planStateRoot } from './continuation.js';
|
|
10
11
|
import { PlanCliError, planAuthError, planNetworkError, planValidationError } from './errors.js';
|
|
11
12
|
function inferProfile(ctx) {
|
|
@@ -150,7 +151,7 @@ export function createPlanHost(ctx, dependencies = {}) {
|
|
|
150
151
|
catch (error) {
|
|
151
152
|
mapRuntimeNetworkError(error);
|
|
152
153
|
}
|
|
153
|
-
if (response
|
|
154
|
+
if (isE10SessionExpiredResponse(response)) {
|
|
154
155
|
throw planAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
155
156
|
}
|
|
156
157
|
if (response.parseError) {
|
|
@@ -2,6 +2,7 @@ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js
|
|
|
2
2
|
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
3
3
|
import { requireE10Session } from '../../internal/e10/context.js';
|
|
4
4
|
import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
5
|
+
import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
|
|
5
6
|
import { ProjectCliError, projectApiError, projectAuthError, projectNetworkError, projectValidationError, } from './errors.js';
|
|
6
7
|
const PROJECT_PATH_PREFIXES = [
|
|
7
8
|
'/api/ebuilder/form/formdata/v2/',
|
|
@@ -133,7 +134,7 @@ export function createProjectHost(ctx, dependencies = {}) {
|
|
|
133
134
|
catch (error) {
|
|
134
135
|
mapRuntimeNetworkError(error);
|
|
135
136
|
}
|
|
136
|
-
if (response
|
|
137
|
+
if (isE10SessionExpiredResponse(response)) {
|
|
137
138
|
throw projectAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, {
|
|
138
139
|
httpStatus: response.status,
|
|
139
140
|
});
|
|
@@ -161,11 +161,16 @@ export const AJAX_QUERY = { sf_request_type: 'ajax' };
|
|
|
161
161
|
* `/api/workflow/core/` 而原样返回,导致请求路径非法。
|
|
162
162
|
*
|
|
163
163
|
* - WEA_FORM(工作流表单)→ 保持 `FORM_ENDPOINTS` 默认地址
|
|
164
|
-
* - WEA_EBUILDER(建模表单)→ `/api/workflow/core/` 段替换为
|
|
164
|
+
* - WEA_EBUILDER(建模表单)→ `/api/workflow/core/` 段替换为 `ebPrefix`
|
|
165
|
+
*
|
|
166
|
+
* ⚠️ `ebPrefix` 默认 `/api/ebuilder/flow/`,但**建模表单内部前缀不统一**,必须按接口给
|
|
167
|
+
* (均为真机请求实证):
|
|
168
|
+
* - `getFormLayout` / `getFormDataAndLayout` / `saveFormData` → `/api/ebuilder/flow/`
|
|
169
|
+
* - `executeFdLinkage` → `/api/ebuilder/form/`
|
|
165
170
|
*/
|
|
166
|
-
export function formApiUrl(path, relateType) {
|
|
171
|
+
export function formApiUrl(path, relateType, ebPrefix = '/api/ebuilder/flow/') {
|
|
167
172
|
if (String(relateType || '').trim() === 'WEA_EBUILDER') {
|
|
168
|
-
return path.replace('/api/workflow/core/',
|
|
173
|
+
return path.replace('/api/workflow/core/', ebPrefix);
|
|
169
174
|
}
|
|
170
175
|
return path;
|
|
171
176
|
}
|