weaver-work-cli 0.1.39 → 0.1.42
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/README.md +4 -2
- package/dist/chunks/{archive-ENZYBBME.js → archive-J4I6MCKF.js} +2 -2
- package/dist/chunks/{archive-oa-YWLEC5V4.js → archive-oa-BRZKKTO4.js} +2 -2
- package/dist/chunks/{asset-OOV6DWPA.js → asset-L7D4FAOZ.js} +2 -2
- package/dist/chunks/{asset-idle-RE632MY5.js → asset-idle-DADLLV75.js} +2 -2
- package/dist/chunks/{attachment-content-RWN2FCLL.js → attachment-content-K4C7VLWQ.js} +3 -3
- package/dist/chunks/{blog-PEPOJAL7.js → blog-BVGQ7NZL.js} +2 -2
- package/dist/chunks/{caizhilian-WYIQDVPJ.js → caizhilian-SAADNFD6.js} +2 -2
- package/dist/chunks/{calendar-CIARBAOX.js → calendar-Q7NQY5Z2.js} +61 -23
- package/dist/chunks/{chunk-F2EQXUEE.js → chunk-3KTFS4IA.js} +397 -159
- package/dist/chunks/{chunk-G2JSIUWF.js → chunk-4PD5NWAX.js} +89 -0
- package/dist/chunks/{chunk-HJYVHXUQ.js → chunk-4RAE6JXP.js} +1 -1
- package/dist/chunks/{chunk-CQHGNCK2.js → chunk-72CKAUAG.js} +41 -8
- package/dist/chunks/{chunk-K3RUEEOL.js → chunk-APVFJUQH.js} +2 -2
- package/dist/chunks/{chunk-BJGXMFGD.js → chunk-BBKDXQEC.js} +2 -2
- package/dist/chunks/chunk-BIPYLLVS.js +501 -0
- package/dist/chunks/{chunk-GVPJZXJ2.js → chunk-EKWAQSLU.js} +92 -50
- package/dist/chunks/{chunk-XXJOSZTQ.js → chunk-FOAA27U5.js} +381 -57
- package/dist/chunks/{chunk-I2HB737U.js → chunk-G4UR6NXN.js} +2 -2
- package/dist/chunks/{chunk-YS5DPN4T.js → chunk-GEWYMQME.js} +1 -1
- package/dist/chunks/{chunk-P2DAISYW.js → chunk-GHXCDPTM.js} +1 -1
- package/dist/chunks/{chunk-L4N7B7ZD.js → chunk-IZ7S7QUS.js} +4 -4
- package/dist/chunks/{chunk-NLK2RZKG.js → chunk-JPRO6ER2.js} +122 -2214
- package/dist/chunks/{chunk-36DIFZAH.js → chunk-LADPYOWC.js} +2 -2
- package/dist/chunks/chunk-LDZQPUIA.js +1238 -0
- package/dist/chunks/chunk-NKZRQXST.js +2546 -0
- package/dist/chunks/{chunk-EINRJHQ3.js → chunk-QKNKR4RH.js} +3 -3
- package/dist/chunks/{chunk-T7JDJJ7H.js → chunk-R4CHUSKC.js} +126 -19
- package/dist/chunks/{chunk-EZGFHJFW.js → chunk-SA6VBIHY.js} +88 -15
- package/dist/chunks/{chunk-ZXRNHZTE.js → chunk-TTPJJJC4.js} +2 -2
- package/dist/chunks/{chunk-KTCB5R5H.js → chunk-W7BEHOSR.js} +2 -2
- package/dist/chunks/{chunk-MUQUHKPG.js → chunk-YRSZQUD2.js} +2 -2
- package/dist/chunks/{conflict-DRMDOL56.js → conflict-F2OMZRTO.js} +2 -2
- package/dist/chunks/{contract-73MERZWN.js → contract-5BETHRSG.js} +2 -2
- package/dist/chunks/{cowork-HNGBPD7A.js → cowork-3MN6EQQO.js} +27 -10
- package/dist/chunks/create-flow-5FQNNMVO.js +55 -0
- package/dist/chunks/{ebuilder-form-OY6DVNRK.js → ebuilder-form-SBHOYBUD.js} +3 -3
- package/dist/chunks/{ehr-RAQNEQO7.js → ehr-KMOJULKG.js} +432 -1234
- package/dist/chunks/env-LB7NBL2O.js +19 -0
- package/dist/chunks/{esb-6XV7C7YV.js → esb-W2PJCFCX.js} +4 -4
- package/dist/chunks/factory-6ZFQXQ46.js +30 -0
- package/dist/chunks/{fna-XTSRB7G7.js → fna-ZQPCUV4R.js} +2 -2
- package/dist/chunks/group-chat-2HHAJN46.js +20 -0
- package/dist/chunks/{host-YMBXWFWX.js → host-4CBHGOHT.js} +3 -3
- package/dist/chunks/{host-A7J6GDUP.js → host-ATKVNZAT.js} +3 -3
- package/dist/chunks/{host-3F5UM5GN.js → host-BQH22NTQ.js} +3 -3
- package/dist/chunks/{host-YAQAPYNP.js → host-X2CSLFM6.js} +3 -3
- package/dist/chunks/{host-VGPVP7YF.js → host-YTODKMSL.js} +3 -3
- package/dist/chunks/{hrm-TLHKC545.js → hrm-FFM4UJFS.js} +4 -4
- package/dist/chunks/{im-YOUCFMUK.js → im-VEHKDQPB.js} +8 -8
- package/dist/chunks/{invoice-WNBS2B4C.js → invoice-LVIKSIIF.js} +3 -3
- package/dist/chunks/{jiuchuanhui-XABWS5RN.js → jiuchuanhui-X6DINO5P.js} +2 -2
- package/dist/chunks/{lazy-dispatch-NLZ5UQQI.js → lazy-dispatch-KSWTN5L6.js} +4 -4
- package/dist/chunks/{mail-LRYMBFNL.js → mail-VDKRGUD3.js} +9 -4
- package/dist/chunks/{meeting-ESXL6MFI.js → meeting-CLC6HP3Z.js} +12 -10
- package/dist/chunks/{okr-EOV65SSF.js → okr-CHG4OO3U.js} +2 -2
- package/dist/chunks/{plan-YFPVKW6Y.js → plan-XWR75667.js} +2 -2
- package/dist/chunks/{project-MDVZUYQW.js → project-5BXOOKMG.js} +40 -23
- package/dist/chunks/recruit-stage-DGMFNNDE.js +4507 -0
- package/dist/chunks/{registry-VT5Y2HLA.js → registry-7FFOXIAW.js} +4 -4
- package/dist/chunks/{registry-QCLZEDAP.js → registry-BKBZPBAJ.js} +10 -8
- package/dist/chunks/{registry-YX4CSEMD.js → registry-RHMW6ANW.js} +2 -2
- package/dist/chunks/{registry-NYPYMZXG.js → registry-Z43434VG.js} +1 -1
- package/dist/chunks/{room-recommend-L3MWH4JW.js → room-recommend-62H7ZQQ6.js} +4 -4
- package/dist/chunks/{schedule-UASAVDVD.js → schedule-FNXKH3DQ.js} +4 -4
- package/dist/chunks/{workflow-KDGIUCNW.js → workflow-ADHKCAJK.js} +4 -4
- package/dist/index.js +33 -28
- package/dist/internal/e10/auth/commands.js +110 -56
- package/dist/internal/e10/auth/messages.js +6 -0
- package/dist/internal/e10/auth/session.js +28 -1
- package/dist/internal/e10/auth/xiaoe.js +315 -48
- package/dist/internal/e10/auth-recovery.js +36 -5
- package/dist/internal/e10/client.js +13 -0
- package/dist/internal/e10/context.js +61 -9
- package/dist/internal/e10/index.js +1 -1
- package/dist/internal/e10/network-error.js +93 -0
- package/dist/internal/e10/request-runtime.js +15 -0
- package/dist/shortcuts/calendar/operations/read.js +31 -22
- package/dist/shortcuts/calendar/operations/write.js +45 -1
- package/dist/shortcuts/cowork/operations/discuss-write.js +5 -1
- package/dist/shortcuts/cowork/operations/item-write.js +5 -3
- package/dist/shortcuts/cowork/operations/member-write.js +3 -1
- package/dist/shortcuts/cowork/operations/permission-write.js +5 -1
- package/dist/shortcuts/cowork/operations/shared.js +7 -0
- package/dist/shortcuts/ehr/operations/attend-write.js +43 -1
- package/dist/shortcuts/ehr/operations/hr-contact.js +7 -4
- package/dist/shortcuts/ehr/operations/hr-report.js +13 -7
- package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +134 -59
- package/dist/shortcuts/ehr/operations/recruit-write.js +262 -17
- package/dist/shortcuts/ehr/operations/recruit.js +10 -4
- package/dist/shortcuts/ehr/operations/shared.js +47 -0
- package/dist/shortcuts/hrm/operations/org.js +118 -0
- package/dist/shortcuts/index.js +5 -0
- package/dist/shortcuts/mail/operations/mail-send.js +8 -2
- package/dist/shortcuts/meeting/operations/eb/catalog.js +9 -2
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +461 -127
- package/dist/shortcuts/meeting/operations/eb/crm-search.js +87 -5
- package/dist/shortcuts/meeting/operations/eb/factory.js +33 -0
- package/dist/shortcuts/meeting/operations/eb/flow-memory.js +49 -0
- package/dist/shortcuts/meeting/operations/eb/group-chat.js +605 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/person-cache.js +96 -0
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +44 -6
- package/dist/shortcuts/meeting/operations/eb/room-summary.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/usage-cache.js +99 -0
- package/dist/shortcuts/meeting/operations/from-group-chat.js +35 -12
- package/dist/shortcuts/project/operations/read.js +6 -25
- package/dist/shortcuts/project/operations/shared.js +22 -0
- package/dist/shortcuts/project/operations/write.js +28 -2
- package/dist/shortcuts/recruit-stage/context.js +119 -0
- package/dist/shortcuts/recruit-stage/continuation.js +73 -0
- package/dist/shortcuts/recruit-stage/errors.js +65 -0
- package/dist/shortcuts/recruit-stage/host.js +109 -0
- package/dist/shortcuts/recruit-stage/index.js +115 -0
- package/dist/shortcuts/recruit-stage/manifest.js +29 -0
- package/dist/shortcuts/recruit-stage/operations/interview-arrange.js +722 -0
- package/dist/shortcuts/recruit-stage/operations/interview-feedback.js +269 -0
- package/dist/shortcuts/recruit-stage/operations/jd-create.js +630 -0
- package/dist/shortcuts/recruit-stage/operations/list.js +431 -0
- package/dist/shortcuts/recruit-stage/operations/registry.js +38 -0
- package/dist/shortcuts/recruit-stage/operations/resume-import.js +1198 -0
- package/dist/shortcuts/recruit-stage/operations/search.js +158 -0
- package/dist/shortcuts/recruit-stage/operations/types.js +25 -0
- package/dist/shortcuts/recruit-stage/school-tiers.json +318 -0
- package/dist/shortcuts/recruit-stage/shared.js +608 -0
- package/dist/shortcuts/workflow/operations/form.js +21 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +52 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +4 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +19 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +17 -24
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +14 -9
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +78 -10
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +3 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +17 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +16 -40
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +3 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +52 -15
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +16 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +151 -63
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.subGroup.js +56 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +53 -16
- package/docs/e10-auth.md +22 -1
- package/docs/operation-manual.md +8 -0
- package/package.json +1 -1
- package/scripts/install-skill-to-xiaoe.mjs +105 -0
- package/scripts/mcp-meeting.mjs +234 -0
- package/scripts/package-skill.mjs +4 -2
- package/skill-template/business-info.json +24 -0
- package/skill-template/domains/recruit-stage.md +29 -0
- package/skills/weaver-e10-calendar/SKILL.md +2 -1
- package/skills/weaver-e10-calendar/references/calendar-write.md +54 -0
- package/skills/weaver-e10-cowork/SKILL.md +1 -1
- package/skills/weaver-e10-cowork/references/cowork-write.md +3 -1
- package/skills/weaver-e10-jucailin/SKILL.md +1 -1
- package/skills/weaver-e10-jucailin/references/attend-write.md +8 -0
- package/skills/weaver-e10-jucailin/references/hr-contact.md +8 -0
- package/skills/weaver-e10-jucailin/references/hr-report.md +8 -0
- package/skills/weaver-e10-jucailin/references/recruit-create.md +2 -2
- package/skills/weaver-e10-jucailin/references/recruit-read.md +1 -1
- package/skills/weaver-e10-jucailin/references/recruit-write.md +4 -3
- package/skills/weaver-e10-mail/SKILL.md +1 -1
- package/skills/weaver-e10-mail/references/mail-send.md +2 -0
- package/skills/weaver-e10-meeting/SKILL.md +119 -26
- package/skills/weaver-e10-meeting/references/meeting-minute-file.md +25 -11
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-recruit-stage/SKILL.md +226 -0
- package/skills/weaver-e10-recruit-stage/agents/openai.yaml +119 -0
- package/skills/weaver-e10-recruit-stage/lib/recruit-xiaoe-file.mjs +188 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-arrange-interview.md +322 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-capability-check.md +127 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-check-install.md +65 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-detail-process.md +63 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-edit-interview-feedback.md +142 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-jd-create.md +73 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-jd-template.md +56 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-list-candidate.md +101 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-list-interview.md +148 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-match-candidates.md +77 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-output-links.md +59 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resolve-creator.md +69 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resolve-metadata.md +241 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resume-import.md +438 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resume-parse.md +128 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-search-candidate.md +146 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-urge-feedback.md +69 -0
- package/skills/weaver-e10-shared/SKILL.md +2 -2
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -2
- package/skills/weaver-e10-shijingran/SKILL.md +3 -2
- package/skills/weaver-e10-shijingran/references/operations.md +3 -1
- package/skills/weaver-e10-shijingran/references/source-manifest.json +41 -40
- package/skills/weaver-e10-shijingran/references/write-operations.md +2 -0
- package/skills/weaver-e10-workflow/SKILL.md +7 -1
- package/skills/weaver-e10-workflow/aigroup/default.json +23 -9
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +37 -21
- package/skills/weaver-e10-workflow/tools/todo-summary.cjs +135 -43
- package/skills/weaver-e10-yepiaotong/SKILL.md +9 -10
- package/skills/weaver-e10-yepiaotong/references/invoice-agent-entry.md +6 -3
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { readCache, writeCache, cacheScopeSuffix } from './cache.js';
|
|
2
|
+
const PERSON_TTL_MS = 24 * 60 * 60 * 1000;
|
|
3
|
+
const BATCH_MAX = 100;
|
|
4
|
+
function personKey(scope) {
|
|
5
|
+
return `person-v1-${scope}`;
|
|
6
|
+
}
|
|
7
|
+
/** 读缓存里的全部人员(id → 记录),过期的条目会被丢弃。 */
|
|
8
|
+
async function readAll(env) {
|
|
9
|
+
const scope = await cacheScopeSuffix(env);
|
|
10
|
+
const hit = await readCache(env, personKey(scope), { ttlMs: PERSON_TTL_MS * 30 });
|
|
11
|
+
if (!hit)
|
|
12
|
+
return {};
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
const fresh = {};
|
|
15
|
+
for (const [id, record] of Object.entries(hit.data ?? {})) {
|
|
16
|
+
if (record && now - Number(record.savedAt || 0) <= PERSON_TTL_MS)
|
|
17
|
+
fresh[id] = record;
|
|
18
|
+
}
|
|
19
|
+
return fresh;
|
|
20
|
+
}
|
|
21
|
+
async function writeAll(env, all) {
|
|
22
|
+
const scope = await cacheScopeSuffix(env);
|
|
23
|
+
await writeCache(env, personKey(scope), all);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 批量取人员信息(缓存优先,未命中的才打 `hrm.employee.batch`)。
|
|
27
|
+
* 返回 id → 记录;查不到的人不会出现在结果里。
|
|
28
|
+
*/
|
|
29
|
+
export async function resolvePersonsCached(env, ids, options = {}) {
|
|
30
|
+
const wanted = [...new Set(ids.map((id) => String(id ?? '').trim()).filter((id) => id !== ''))];
|
|
31
|
+
const persons = new Map();
|
|
32
|
+
if (wanted.length === 0)
|
|
33
|
+
return { persons, requests: 0, fromCache: 0 };
|
|
34
|
+
const all = options.refresh === true ? {} : await readAll(env);
|
|
35
|
+
const missing = [];
|
|
36
|
+
let fromCache = 0;
|
|
37
|
+
for (const id of wanted) {
|
|
38
|
+
const hit = all[id];
|
|
39
|
+
if (hit) {
|
|
40
|
+
persons.set(id, hit);
|
|
41
|
+
fromCache += 1;
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
missing.push(id);
|
|
45
|
+
}
|
|
46
|
+
if (missing.length === 0)
|
|
47
|
+
return { persons, requests: 0, fromCache };
|
|
48
|
+
let requests = 0;
|
|
49
|
+
for (let index = 0; index < missing.length; index += BATCH_MAX) {
|
|
50
|
+
const chunk = missing.slice(index, index + BATCH_MAX);
|
|
51
|
+
try {
|
|
52
|
+
const { hrmEmployeeOperations } = await import('../../../hrm/operations/employee.js');
|
|
53
|
+
const batch = hrmEmployeeOperations.find((operation) => operation.name === 'hrm.employee.batch');
|
|
54
|
+
if (!batch)
|
|
55
|
+
break;
|
|
56
|
+
const result = await batch.handler({ ids: chunk }, env);
|
|
57
|
+
requests += 1;
|
|
58
|
+
const rows = (result?.data?.rows ?? result?.data?.userList ?? []);
|
|
59
|
+
for (const row of rows) {
|
|
60
|
+
const id = String(row?.id ?? '').trim();
|
|
61
|
+
if (!id)
|
|
62
|
+
continue;
|
|
63
|
+
const record = {
|
|
64
|
+
id,
|
|
65
|
+
name: String(row?.name ?? '').trim(),
|
|
66
|
+
...(row?.department ? { department: String(row.department) } : {}),
|
|
67
|
+
...(row?.departmentId ? { departmentId: String(row.departmentId) } : {}),
|
|
68
|
+
...(row?.subCompany ? { subCompany: String(row.subCompany) } : {}),
|
|
69
|
+
...(row?.position ? { position: String(row.position) } : {}),
|
|
70
|
+
...(row?.mobile ? { mobile: String(row.mobile) } : {}),
|
|
71
|
+
...(row?.jobNum ? { jobNum: String(row.jobNum) } : {}),
|
|
72
|
+
...(row?.imCid ? { imCid: String(row.imCid) } : {}),
|
|
73
|
+
savedAt: Date.now(),
|
|
74
|
+
};
|
|
75
|
+
persons.set(id, record);
|
|
76
|
+
all[id] = record;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// 查失败不影响业务:调用方拿到的是缺项,自行降级
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (requests > 0)
|
|
84
|
+
await writeAll(env, all);
|
|
85
|
+
return { persons, requests, fromCache };
|
|
86
|
+
}
|
|
87
|
+
/** 兼容老用法:只要 id → 姓名。 */
|
|
88
|
+
export async function resolveNamesCached(env, ids) {
|
|
89
|
+
const { persons } = await resolvePersonsCached(env, ids);
|
|
90
|
+
const map = new Map();
|
|
91
|
+
for (const [id, person] of persons) {
|
|
92
|
+
if (person.name)
|
|
93
|
+
map.set(id, person.name);
|
|
94
|
+
}
|
|
95
|
+
return map;
|
|
96
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { meetingSuccess, schemaOf, SCHEMA_DATETIME_MINUTE } from '../types.js';
|
|
2
2
|
import { EB_BASE, ebBusinessSuccess, extractEbRows, queryBody } from './factory.js';
|
|
3
3
|
import { readCache, writeCache, cacheScopeSuffix } from './cache.js';
|
|
4
|
+
import { busyRoomIds } from './usage-cache.js';
|
|
4
5
|
// 会议室台账缓存 24h(用户 2026-10-30 明确要求:不要为了新鲜度把 TTL 缩到 15 分钟,太频繁)。
|
|
5
6
|
// 「刚导入/新建的会议室看不到」靠两层兜底解决,而不是靠缩短 TTL:
|
|
6
7
|
// ① **建会链路强制拉最新**(create.flow.prepare / quick / create.suggest 传 refreshRooms: true)——
|
|
@@ -65,11 +66,15 @@ function normalizeRoom(row) {
|
|
|
65
66
|
place: toText(main.place).trim(),
|
|
66
67
|
equipments: toText(main.equipments).trim(),
|
|
67
68
|
mrType: toText(main.mr_type).trim(),
|
|
69
|
+
// ⛔ 2026-11-04 真机发现:以前没映射 room_desc,`roomDesc` 永远是 undefined,
|
|
70
|
+
// 于是「带客户优先接待类」里**按描述文案判定**那一半从来没生效过(只有名称含「接待」的能命中)。
|
|
71
|
+
// 例:广州会议室四 的描述「用来接待重要来宾…」明明写着,却被当普通房间。
|
|
72
|
+
roomDesc: toText(main.room_desc).trim(),
|
|
68
73
|
};
|
|
69
74
|
}
|
|
70
75
|
/** 全量会议室:优先缓存(24h),`refresh:true` 强制重拉。 */
|
|
71
76
|
export async function loadRooms(env, options = {}) {
|
|
72
|
-
const key = `rooms-${await cacheScopeSuffix(env)}`;
|
|
77
|
+
const key = `rooms-v3-${await cacheScopeSuffix(env)}`;
|
|
73
78
|
const warnings = [];
|
|
74
79
|
if (!options.refresh) {
|
|
75
80
|
const cached = await readCache(env, key, { ttlMs: ROOMS_TTL_MS });
|
|
@@ -123,10 +128,19 @@ export async function loadFrequentRooms(env, options = {}) {
|
|
|
123
128
|
try {
|
|
124
129
|
const { getMeetingOperation } = await import('../registry.js');
|
|
125
130
|
const listOp = getMeetingOperation('meeting.eb.meeting.list');
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
// ⛔ 2026-11-04 实测:会议列表接口**每页固定 10 条**(pageSize=200 也无效),
|
|
132
|
+
// 以前只扫 5 页 = 最近 50 场,而用户 180 天有 130+ 场 —— 统计被截断,
|
|
133
|
+
// 结果全被最近的测试会议带偏(「测试会议室1」排第一就是这么来的)。
|
|
134
|
+
// 现在按返回的 total 翻到底(封顶 40 页 / 400 场,避免异常数据打爆)。
|
|
135
|
+
let seen = 0;
|
|
136
|
+
let total = 0;
|
|
137
|
+
for (let pageNo = 1; pageNo <= 40; pageNo += 1) {
|
|
138
|
+
const page = await listOp.handler({ creator: userId, begindatetime: sinceText, pageNo, pageSize: 200 }, env);
|
|
128
139
|
requests += 1;
|
|
129
140
|
const rows = (page?.data?.rows ?? []);
|
|
141
|
+
if (rows.length === 0)
|
|
142
|
+
break;
|
|
143
|
+
total = Number(page?.data?.total ?? total) || total;
|
|
130
144
|
for (const row of rows) {
|
|
131
145
|
const main = row?.mainTable ?? row ?? {};
|
|
132
146
|
const name = toText(main.address).trim();
|
|
@@ -134,7 +148,8 @@ export async function loadFrequentRooms(env, options = {}) {
|
|
|
134
148
|
continue;
|
|
135
149
|
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
136
150
|
}
|
|
137
|
-
|
|
151
|
+
seen += rows.length;
|
|
152
|
+
if (total > 0 && seen >= total)
|
|
138
153
|
break;
|
|
139
154
|
}
|
|
140
155
|
}
|
|
@@ -190,13 +205,19 @@ export async function recommendRooms(env, input) {
|
|
|
190
205
|
}
|
|
191
206
|
const frequent = await loadFrequentRooms(env, { refresh: input.refreshFrequent === true });
|
|
192
207
|
warnings.push(...frequent.warnings);
|
|
193
|
-
const busy =
|
|
208
|
+
const busy = input.dayUsages
|
|
209
|
+
? { ids: busyRoomIds(input.dayUsages, input.beginDatetime, input.endDatetime), requests: 0 }
|
|
210
|
+
: await fetchBusyRoomIds(env, input.beginDatetime, input.endDatetime);
|
|
194
211
|
const frequentRank = new Map();
|
|
195
212
|
frequent.order.forEach((item, index) => {
|
|
196
213
|
frequentRank.set(item.name, { rank: index, count: item.count });
|
|
197
214
|
});
|
|
198
215
|
const memberCount = input.memberCount && input.memberCount > 0 ? input.memberCount : null;
|
|
199
216
|
const requirement = input.requirement ?? {};
|
|
217
|
+
// 兼容顶层 hasCustomer(建会链路以前就是传顶层,打分却只读 requirement.hasCustomer,
|
|
218
|
+
// 导致「带客户优先接待类」从来没生效过 —— 2026-11-04 用户实测 3 个候选全是普通会议室)。
|
|
219
|
+
if (input.hasCustomer === true)
|
|
220
|
+
requirement.hasCustomer = true;
|
|
200
221
|
const usable = roomData.rooms.filter((room) => room.status === '0');
|
|
201
222
|
let busyCount = 0;
|
|
202
223
|
const candidates = [];
|
|
@@ -255,6 +276,19 @@ export async function recommendRooms(env, input) {
|
|
|
255
276
|
reasons.push(`设备含「${requirement.equipmentsKeyword}」`);
|
|
256
277
|
score += 10;
|
|
257
278
|
}
|
|
279
|
+
// 带客户时优先接待类:会议室描述(room_desc)或名称里带「接待/招待/贵宾/商务」的加分。
|
|
280
|
+
// ⛔ 不要把「客户」单独当关键词:`e-message(建议客户培训)` 这种培训室会被误判成接待类,
|
|
281
|
+
// 直接加 35 分排到第一(2026-11-04 真机实测)。真正的接待房名称/描述里一定带「接待」。
|
|
282
|
+
if (requirement.hasCustomer === true) {
|
|
283
|
+
const desc = String(room.roomDesc ?? '').trim();
|
|
284
|
+
const nameHit = room.name.match(/(接待|招待|贵宾|商务|vip)/iu);
|
|
285
|
+
const descHit = desc.match(/(接待|招待|贵宾|商务|vip)/iu);
|
|
286
|
+
const hostHit = nameHit ?? descHit;
|
|
287
|
+
if (hostHit) {
|
|
288
|
+
reasons.push(`带客户优先:${descHit ? `描述含「${desc.slice(0, 30)}」` : `名称含「${hostHit[1]}」`}`);
|
|
289
|
+
score += 35;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
258
292
|
if (isBusy) {
|
|
259
293
|
reasons.push('该时段已被占用(按 includeBusy 一并返回)');
|
|
260
294
|
score -= 100;
|
|
@@ -269,6 +303,7 @@ export async function recommendRooms(env, input) {
|
|
|
269
303
|
place: room.place,
|
|
270
304
|
equipments: room.equipments,
|
|
271
305
|
roomAttrText: room.roomAttrText,
|
|
306
|
+
...(String(room.roomDesc ?? '').trim() !== '' ? { roomDesc: String(room.roomDesc).trim() } : {}),
|
|
272
307
|
frequentCount: freq?.count ?? 0,
|
|
273
308
|
score,
|
|
274
309
|
reasons,
|
|
@@ -354,6 +389,7 @@ export const ebRoomRecommendOperation = {
|
|
|
354
389
|
mrType: { type: 'string', description: '会议室类型 id(mr_type)' },
|
|
355
390
|
roomAttr: { type: 'string', enum: ['0', '1', '2'], description: '会议室属性:0 实体 / 1 虚拟 / 2 组合' },
|
|
356
391
|
equipmentsKeyword: { type: 'string', description: '设备关键词(模糊匹配 equipments,如 投影/视频)' },
|
|
392
|
+
hasCustomer: { type: 'boolean', description: '本次会议是否带客户:true 时优先推荐接待类会议室(描述/名称含 接待/招待/客户/商务/贵宾 的加分)' },
|
|
357
393
|
topN: { type: 'number', description: '返回候选数量(默认 3,最多 10)' },
|
|
358
394
|
refresh: { type: 'boolean', description: 'true=刷新会议室全量与常用记录缓存' },
|
|
359
395
|
includeBusy: { type: 'boolean', description: 'true=把该时段已被占用的会议室也返回(排在最后,带说明)' },
|
|
@@ -374,6 +410,8 @@ export const ebRoomRecommendOperation = {
|
|
|
374
410
|
...(args.mrType ? { mrType: String(args.mrType) } : {}),
|
|
375
411
|
...(args.roomAttr ? { roomAttr: String(args.roomAttr) } : {}),
|
|
376
412
|
...(args.equipmentsKeyword ? { equipmentsKeyword: String(args.equipmentsKeyword) } : {}),
|
|
413
|
+
// 2026-11-04 修:以前漏了它 —— 用户传了 hasCustomer,打分却读 requirement.hasCustomer,永远 false
|
|
414
|
+
...(args.hasCustomer === true ? { hasCustomer: true } : {}),
|
|
377
415
|
},
|
|
378
416
|
...(args.topN !== undefined ? { topN: Number(args.topN) } : {}),
|
|
379
417
|
...(args.refresh === true ? { refreshRooms: true, refreshFrequent: true } : {}),
|
|
@@ -401,7 +439,7 @@ export async function rememberFrequentRoom(env, roomId) {
|
|
|
401
439
|
if (!id)
|
|
402
440
|
return false;
|
|
403
441
|
const scope = await cacheScopeSuffix(env);
|
|
404
|
-
const roomsCached = await readCache(env, `rooms-${scope}`, { ttlMs: ROOMS_TTL_MS });
|
|
442
|
+
const roomsCached = await readCache(env, `rooms-v3-${scope}`, { ttlMs: ROOMS_TTL_MS });
|
|
405
443
|
const name = roomsCached?.data?.find((room) => room.id === id)?.name ?? '';
|
|
406
444
|
if (!name)
|
|
407
445
|
return false;
|
|
@@ -64,6 +64,8 @@ export function summarizeRoomRows(rows) {
|
|
|
64
64
|
allowMaxNum: toText(main.allow_maxnum),
|
|
65
65
|
place: toText(main.place),
|
|
66
66
|
equipments: toText(main.equipments),
|
|
67
|
+
// 会议室描述:列表接口本来就返回 `room_desc`(2026-11-02 核实),之前漏映射导致推荐时用不上
|
|
68
|
+
roomDesc: toText(main.room_desc),
|
|
67
69
|
};
|
|
68
70
|
});
|
|
69
71
|
return {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { EB_BASE, ebBusinessSuccess, extractEbRows, queryBody } from './factory.js';
|
|
2
|
+
import { readCache, writeCache, cacheScopeSuffix } from './cache.js';
|
|
3
|
+
/** 占用缓存 TTL:太长会放行别人刚订的房间,太短失去缓存意义 —— 5 分钟 + 提交即作废。 */
|
|
4
|
+
const USAGE_TTL_MS = 5 * 60 * 1000;
|
|
5
|
+
const MAX_PAGES = 10;
|
|
6
|
+
const PAGE_SIZE = 200;
|
|
7
|
+
/** 取 'yyyy-MM-dd HH:mm' 的日期部分。 */
|
|
8
|
+
export function dayOf(datetime) {
|
|
9
|
+
return String(datetime ?? '').trim().slice(0, 10);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 时间重叠判定:占用开始 < 我的结束 且 占用结束 > 我的开始。
|
|
13
|
+
* 字符串直接比较成立的前提是 'yyyy-MM-dd HH:mm' 定宽格式(EB 出参即如此)。
|
|
14
|
+
*/
|
|
15
|
+
export function usageOverlaps(row, begin, end) {
|
|
16
|
+
return row.start < end && row.end > begin;
|
|
17
|
+
}
|
|
18
|
+
/** 从全天占用里筛出「某会议室在某时段」的占用记录(可排除自身会议)。 */
|
|
19
|
+
export function filterUsages(rows, options) {
|
|
20
|
+
return rows.filter((row) => {
|
|
21
|
+
if (options.roomId && row.roomId !== options.roomId)
|
|
22
|
+
return false;
|
|
23
|
+
if (options.excludeMtId && row.mtId && row.mtId === options.excludeMtId)
|
|
24
|
+
return false;
|
|
25
|
+
return usageOverlaps(row, options.begin, options.end);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** 从全天占用算出「某时段被占用的会议室 id 集合」(替代按时段的 query_roomusage_list)。 */
|
|
29
|
+
export function busyRoomIds(rows, begin, end) {
|
|
30
|
+
const ids = new Set();
|
|
31
|
+
for (const row of rows) {
|
|
32
|
+
if (row.roomId && usageOverlaps(row, begin, end))
|
|
33
|
+
ids.add(row.roomId);
|
|
34
|
+
}
|
|
35
|
+
return ids;
|
|
36
|
+
}
|
|
37
|
+
function usageKey(scope, day) {
|
|
38
|
+
return `usage-v1-${scope}-${day}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 加载某天的全部会议室占用(缓存优先)。
|
|
42
|
+
* 返回 rows 供本地过滤;fromCache=true 表示没打网络(调用方可用于提示/计时)。
|
|
43
|
+
*/
|
|
44
|
+
export async function loadDayUsages(env, day, options = {}) {
|
|
45
|
+
const scope = await cacheScopeSuffix(env);
|
|
46
|
+
const key = usageKey(scope, day);
|
|
47
|
+
if (options.refresh !== true) {
|
|
48
|
+
const cached = await readCache(env, key, { ttlMs: USAGE_TTL_MS });
|
|
49
|
+
if (cached) {
|
|
50
|
+
return { rows: cached.data, fromCache: true, ageMs: cached.ageMs, requests: 0 };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const session = await env.host.getSession();
|
|
54
|
+
const rows = [];
|
|
55
|
+
let requests = 0;
|
|
56
|
+
for (let pageNo = 1; pageNo <= MAX_PAGES; pageNo += 1) {
|
|
57
|
+
const response = await env.host.request({
|
|
58
|
+
method: 'POST',
|
|
59
|
+
path: `${EB_BASE}/getFormDataList/query_roomusage_list`,
|
|
60
|
+
operation: 'meeting.eb.roomUsage.list',
|
|
61
|
+
// 与 fetchBusyRoomIds 同一套算子语义:enddatetime lt 当天末尾、begindatetime gt 当天开头
|
|
62
|
+
query: { enddatetime: `${day} 23:59`, begindatetime: `${day} 00:00` },
|
|
63
|
+
body: queryBody(session.userId, { pageNo, pageSize: PAGE_SIZE }),
|
|
64
|
+
acceptBusiness: ebBusinessSuccess,
|
|
65
|
+
});
|
|
66
|
+
requests += 1;
|
|
67
|
+
const { rows: pageRows } = extractEbRows(response);
|
|
68
|
+
for (const row of pageRows) {
|
|
69
|
+
const main = row?.mainTable ?? row ?? {};
|
|
70
|
+
const roomId = String(main.address ?? main.room_id ?? '').trim();
|
|
71
|
+
const start = String(main.startdate ?? '').trim();
|
|
72
|
+
const end = String(main.enddate ?? '').trim();
|
|
73
|
+
if (roomId && start && end) {
|
|
74
|
+
rows.push({ roomId, mtId: String(main.mt_id ?? '').trim(), start, end });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (pageRows.length < PAGE_SIZE)
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
await writeCache(env, key, rows);
|
|
81
|
+
return { rows, fromCache: false, ageMs: 0, requests };
|
|
82
|
+
}
|
|
83
|
+
/** 作废某天的占用缓存(提交成功后必须调,否则 5 分钟内新订的房间会被旧台账掩盖)。 */
|
|
84
|
+
export async function invalidateDayUsages(env, day) {
|
|
85
|
+
const scope = await cacheScopeSuffix(env);
|
|
86
|
+
await clearCache(env, usageKey(scope, day));
|
|
87
|
+
}
|
|
88
|
+
/** 直接删缓存文件(写空记录会把"刚被订满"伪装成"全天空闲",必须真删)。 */
|
|
89
|
+
async function clearCache(env, key) {
|
|
90
|
+
try {
|
|
91
|
+
const { rm } = await import('node:fs/promises');
|
|
92
|
+
const { join } = await import('node:path');
|
|
93
|
+
const { meetingStateDir } = await import('../env.js');
|
|
94
|
+
await rm(join(meetingStateDir(env.ctx), 'cache', `${key}.json`), { force: true });
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// 删失败不影响业务:TTL 5 分钟兜底
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -23,9 +23,24 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { meetingValidationError } from '../errors.js';
|
|
25
25
|
import { meetingSuccess } from './types.js';
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
/**
|
|
27
|
+
* ESB 动作流标识:本租户 EB 后台的 **primary_key**(32 位十六进制)。
|
|
28
|
+
*
|
|
29
|
+
* ⛔ 触发时必须走「唯一值触发」入参 `uniqueIndent`:
|
|
30
|
+
* `esbFlowId` 只认 19 位数字 id,传 GUID 服务端直接报
|
|
31
|
+
* `can not cast to long, value : 7204…`(查模板)或 `{"code":400,"msg":"系统错误"}`(触发)。
|
|
32
|
+
* 2026-11-03 实测:同一组 id 用 `uniqueIndent` 触发,`resultCode=200 执行成功`。
|
|
33
|
+
*
|
|
34
|
+
* 换租户请用入参 `flowGenerateMinutes` / `flowGenerateDocument` 覆盖。
|
|
35
|
+
*/
|
|
36
|
+
const FLOW_GENERATE_MINUTES = '7204936B2ACC4B12A1CECD1DAB60DD97';
|
|
37
|
+
const FLOW_GENERATE_DOCUMENT = '1F638AE31BC746C28A1D9DCF26D91037';
|
|
38
|
+
/** ESB「解析纪要文档/音频附件」(primary_key)。附件解析入口,当前由后台解析任务触发,此处仅登记备查。 */
|
|
39
|
+
const FLOW_PARSE_ATTACHMENT = 'AD8EE1B345FD4570AE1D0882FD1B5927';
|
|
40
|
+
/** ESB「获取解析纪要音频结果」(primary_key)。音视频附件提取完成后由调用方按需触发。 */
|
|
41
|
+
const FLOW_AUDIO_RESULT = '1DB38C10699A47E89DD7CEFDAA76B8A1';
|
|
42
|
+
/** 智能纪要页面 id(租户级,没有接口能查;反查不到时用它兜底)。 */
|
|
43
|
+
const DEFAULT_AI_MINUTE_PAGE = '1250830691712172036';
|
|
29
44
|
function textOf(value) {
|
|
30
45
|
return value === undefined || value === null ? '' : String(value);
|
|
31
46
|
}
|
|
@@ -285,14 +300,14 @@ const meetingMinuteFromGroupChat = {
|
|
|
285
300
|
},
|
|
286
301
|
flowGenerateMinutes: {
|
|
287
302
|
type: 'string',
|
|
288
|
-
pattern: '
|
|
289
|
-
description: 'ESB
|
|
290
|
-
+ '
|
|
303
|
+
pattern: '^(\\d+|[0-9A-Fa-f]{32})$',
|
|
304
|
+
description: 'ESB「根据解析文本生成智能纪要文本」的 id。可传 19 位数字 id 或 32 位十六进制 primary_key'
|
|
305
|
+
+ '(GUID 会自动走唯一值触发 uniqueIndent)。省略时用内置默认值;换租户必须覆盖',
|
|
291
306
|
},
|
|
292
307
|
flowGenerateDocument: {
|
|
293
308
|
type: 'string',
|
|
294
|
-
pattern: '
|
|
295
|
-
description: 'ESB
|
|
309
|
+
pattern: '^(\\d+|[0-9A-Fa-f]{32})$',
|
|
310
|
+
description: 'ESB「保存解析并生成文档」的 id,支持数字 id 或 32 位 primary_key;省略时用内置默认值',
|
|
296
311
|
},
|
|
297
312
|
},
|
|
298
313
|
required: ['mt_id'],
|
|
@@ -472,7 +487,8 @@ const meetingMinuteFromGroupChat = {
|
|
|
472
487
|
// 反查失败不影响主流程
|
|
473
488
|
}
|
|
474
489
|
if (!aiPageId) {
|
|
475
|
-
|
|
490
|
+
aiPageId = DEFAULT_AI_MINUTE_PAGE;
|
|
491
|
+
warnings.push(`未从已有纪要取到 mt_minutes_ai_page,已用本租户默认值 ${DEFAULT_AI_MINUTE_PAGE}`);
|
|
476
492
|
}
|
|
477
493
|
}
|
|
478
494
|
// ⑥ 建纪要并写原文
|
|
@@ -512,6 +528,7 @@ const meetingMinuteFromGroupChat = {
|
|
|
512
528
|
pipeline,
|
|
513
529
|
aiPageId: aiPageId || null,
|
|
514
530
|
};
|
|
531
|
+
let documentIdFromFlow = '';
|
|
515
532
|
if (pipeline === 'text') {
|
|
516
533
|
result.timings = { ...timings, total: Date.now() - startedAt };
|
|
517
534
|
return meetingSuccess('meeting.minute.fromGroupChat', result, warnings.length > 0 ? { warnings } : undefined);
|
|
@@ -534,7 +551,11 @@ const meetingMinuteFromGroupChat = {
|
|
|
534
551
|
const prepareOp = getEsbOperation('esb.trigger.prepare');
|
|
535
552
|
const applyOp = getEsbOperation('esb.trigger.apply');
|
|
536
553
|
const trigger = async (flowId, mainTable) => {
|
|
537
|
-
|
|
554
|
+
// 数字 id → 普通触发 esbFlowId;32 位十六进制 primary_key → 唯一值触发 uniqueIndent
|
|
555
|
+
const target = /^\d+$/.test(flowId)
|
|
556
|
+
? { esbFlowId: flowId }
|
|
557
|
+
: { uniqueIndent: flowId };
|
|
558
|
+
const pre = await prepareOp.handler({ employeeId, ...target, customParams: { mainTable } }, esbEnv);
|
|
538
559
|
return applyOp.handler({ continuation: pre?.data?.continuation, confirm: true }, esbEnv);
|
|
539
560
|
};
|
|
540
561
|
try {
|
|
@@ -554,7 +575,9 @@ const meetingMinuteFromGroupChat = {
|
|
|
554
575
|
}
|
|
555
576
|
else {
|
|
556
577
|
try {
|
|
557
|
-
await trigger(flowDocument, { parse_content: parseContent, mt_minute_id: minuteId });
|
|
578
|
+
const docResult = await trigger(flowDocument, { parse_content: parseContent, mt_minute_id: minuteId });
|
|
579
|
+
// 628 的返回里带 `customData.mainTable.wdID`(文档 id);回读为空时用它兜底
|
|
580
|
+
documentIdFromFlow = textOf(docResult?.data?.actionData?.responseData?.customData?.mainTable?.wdID);
|
|
558
581
|
mark('esbDocument', stepDocStart);
|
|
559
582
|
}
|
|
560
583
|
catch (error) {
|
|
@@ -568,7 +591,7 @@ const meetingMinuteFromGroupChat = {
|
|
|
568
591
|
const row = final?.data?.data ?? {};
|
|
569
592
|
result.finalStatus = textOf(row.minutes_status);
|
|
570
593
|
result.parseContentLength = textOf(row.parse_content).length;
|
|
571
|
-
result.documentId = textOf(row.mt_minutes) || null;
|
|
594
|
+
result.documentId = textOf(row.mt_minutes) || documentIdFromFlow || null;
|
|
572
595
|
result.timings = { ...timings, total: Date.now() - startedAt };
|
|
573
596
|
return meetingSuccess('meeting.minute.fromGroupChat', result, warnings.length > 0 ? { warnings } : undefined);
|
|
574
597
|
},
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { assertDatajsonSuccess, assertWeaverSuccess, } from '../host.js';
|
|
2
2
|
import { projectValidationError } from '../errors.js';
|
|
3
3
|
import { SCHEMA_FILTERS, SCHEMA_FIELDS, SCHEMA_ID_STRING, SCHEMA_PAGE_NO, SCHEMA_PAGE_SIZE, projectSuccess, schemaOf, } from './types.js';
|
|
4
|
-
import { compactFilters, idString, operatorId, positiveInt, queryText, } from './shared.js';
|
|
4
|
+
import { buildCardUrl, compactFilters, idString, operatorId, positiveInt, queryText, resolveObjectId, } from './shared.js';
|
|
5
5
|
const APP_TAG = 'weaver-project-eb';
|
|
6
|
-
const DEFAULT_OBJ_IDS = { task: '1020270486210527232', project: '1020275562853482532' };
|
|
7
6
|
const MIN_VERSION = [20, 6, 11];
|
|
8
|
-
function buildCardUrl(baseUrl, objId, dataId) {
|
|
9
|
-
if (!baseUrl || !objId || !dataId || /[\s/\\?#]/u.test(dataId))
|
|
10
|
-
return null;
|
|
11
|
-
return `${baseUrl.replace(/\/+$/u, '')}/sp/ebdfpage/card/0/${objId}/${dataId}`;
|
|
12
|
-
}
|
|
13
7
|
const CURRENT_USER_FILTERS = new Set([
|
|
14
8
|
'manager',
|
|
15
9
|
'participant',
|
|
@@ -114,7 +108,7 @@ async function decorateDetailUrls(host, operation, datajson) {
|
|
|
114
108
|
if (targets.length === 0)
|
|
115
109
|
return datajson;
|
|
116
110
|
try {
|
|
117
|
-
const objId = await
|
|
111
|
+
const objId = await resolveObjectId(host, operationTable(operation));
|
|
118
112
|
const session = await host.getSession();
|
|
119
113
|
for (const row of targets) {
|
|
120
114
|
const url = buildCardUrl(session.baseUrl, objId, String(row.id));
|
|
@@ -161,19 +155,6 @@ async function getAppId(host) {
|
|
|
161
155
|
throw new Error('当前环境未安装事井然项目管理应用');
|
|
162
156
|
return appId;
|
|
163
157
|
}
|
|
164
|
-
async function getObject(host, value) {
|
|
165
|
-
const response = await host.request({
|
|
166
|
-
method: 'GET',
|
|
167
|
-
path: '/api/ebproject/backendConsole/getCurrentObjId',
|
|
168
|
-
query: { objId: DEFAULT_OBJ_IDS[value] },
|
|
169
|
-
operation: 'project.object.id',
|
|
170
|
-
});
|
|
171
|
-
assertWeaverSuccess(response, 'project.object.id');
|
|
172
|
-
const object = String(response?.data ?? '');
|
|
173
|
-
if (!object)
|
|
174
|
-
throw new Error('接口未返回当前环境对象 ID');
|
|
175
|
-
return object;
|
|
176
|
-
}
|
|
177
158
|
async function datajsonRequest(host, operation, path, payload, query) {
|
|
178
159
|
const response = await host.request({
|
|
179
160
|
method: 'POST',
|
|
@@ -383,7 +364,7 @@ export const projectReadOperations = [
|
|
|
383
364
|
inputSchema: schemaOf({ table: { type: 'string', enum: ['task', 'project'] } }, { required: ['table'] }),
|
|
384
365
|
handler: async (args, { host }) => {
|
|
385
366
|
const value = table(args.table);
|
|
386
|
-
return projectSuccess('project.object.id', { table: value, objId: await
|
|
367
|
+
return projectSuccess('project.object.id', { table: value, objId: await resolveObjectId(host, value) });
|
|
387
368
|
},
|
|
388
369
|
},
|
|
389
370
|
{
|
|
@@ -417,7 +398,7 @@ export const projectReadOperations = [
|
|
|
417
398
|
handler: async (args, { host }) => {
|
|
418
399
|
const value = table(args.table);
|
|
419
400
|
const groupId = args.groupId ? String(args.groupId) : await getAppId(host);
|
|
420
|
-
const sourceId = args.sourceId ? String(args.sourceId) : await
|
|
401
|
+
const sourceId = args.sourceId ? String(args.sourceId) : await resolveObjectId(host, value);
|
|
421
402
|
const response = await host.request({
|
|
422
403
|
method: 'POST',
|
|
423
404
|
path: '/api/ebuilder/common/ds/fields',
|
|
@@ -456,7 +437,7 @@ export const projectReadOperations = [
|
|
|
456
437
|
handler: async (args, { host }) => {
|
|
457
438
|
const value = table(args.table);
|
|
458
439
|
const groupId = args.groupId ? String(args.groupId) : await getAppId(host);
|
|
459
|
-
const sourceId = args.sourceId ? String(args.sourceId) : await
|
|
440
|
+
const sourceId = args.sourceId ? String(args.sourceId) : await resolveObjectId(host, value);
|
|
460
441
|
const response = await host.request({
|
|
461
442
|
method: 'GET',
|
|
462
443
|
path: '/api/ebuilder/common/ds/options',
|
|
@@ -549,4 +530,4 @@ export const projectReadOperations = [
|
|
|
549
530
|
hrmOperation('project.department.resolve', '/api/hrm/common/browser/data/department', 'organization', '按名称精确或模糊解析部门 ID'),
|
|
550
531
|
hrmOperation('project.subcompany.resolve', '/api/hrm/common/browser/data/subcompany', 'organization', '按名称精确或模糊解析分部 ID'),
|
|
551
532
|
];
|
|
552
|
-
export { getAppId, getObject, SCHEMA_FIELDS };
|
|
533
|
+
export { getAppId, resolveObjectId as getObject, SCHEMA_FIELDS };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { projectValidationError } from '../errors.js';
|
|
2
|
+
import { assertWeaverSuccess } from '../host.js';
|
|
2
3
|
export function asObject(value, field = 'input') {
|
|
3
4
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
4
5
|
throw projectValidationError('field_invalid', `${field} 必须是 JSON 对象`);
|
|
@@ -66,3 +67,24 @@ export function stableFingerprint(value) {
|
|
|
66
67
|
}
|
|
67
68
|
return `${text.length}:${hash.toString(16)}`;
|
|
68
69
|
}
|
|
70
|
+
export const DEFAULT_OBJ_IDS = { task: '1020270486210527232', project: '1020275562853482532' };
|
|
71
|
+
/** 详情卡片地址:`{baseUrl}/sp/ebdfpage/card/0/{objId}/{dataId}`。参数不全或 dataId 含分隔符时返回 null。 */
|
|
72
|
+
export function buildCardUrl(baseUrl, objId, dataId) {
|
|
73
|
+
if (!baseUrl || !objId || !dataId || /[\s/\\?#]/u.test(dataId))
|
|
74
|
+
return null;
|
|
75
|
+
return `${baseUrl.replace(/\/+$/u, '')}/sp/ebdfpage/card/0/${objId}/${dataId}`;
|
|
76
|
+
}
|
|
77
|
+
/** 解析当前环境下的表单对象 ID(列表/详情/新建共用)。 */
|
|
78
|
+
export async function resolveObjectId(host, value) {
|
|
79
|
+
const response = await host.request({
|
|
80
|
+
method: 'GET',
|
|
81
|
+
path: '/api/ebproject/backendConsole/getCurrentObjId',
|
|
82
|
+
query: { objId: DEFAULT_OBJ_IDS[value] },
|
|
83
|
+
operation: 'project.object.id',
|
|
84
|
+
});
|
|
85
|
+
assertWeaverSuccess(response, 'project.object.id');
|
|
86
|
+
const object = String(response?.data ?? '');
|
|
87
|
+
if (!object)
|
|
88
|
+
throw new Error('接口未返回当前环境对象 ID');
|
|
89
|
+
return object;
|
|
90
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { projectPartialError, projectValidationError } from '../errors.js';
|
|
2
2
|
import { assertDatajsonSuccess } from '../host.js';
|
|
3
3
|
import { SCHEMA_CONFIRM, SCHEMA_CONTINUATION, SCHEMA_FIELDS, SCHEMA_ID_STRING, SCHEMA_RECORDS, projectSuccess, schemaOf, } from './types.js';
|
|
4
|
-
import { asObject, idString, operatorId, stableFingerprint } from './shared.js';
|
|
4
|
+
import { asObject, buildCardUrl, idString, operatorId, resolveObjectId, stableFingerprint } from './shared.js';
|
|
5
5
|
const WRITE_TARGETS = {
|
|
6
6
|
taskCreate: { entity: 'task', action: 'create', listPath: '', detailPath: '/api/ebuilder/form/formdata/v2/getFormDataByPk/queryTaskDetail', writePath: '/api/ebuilder/form/formdata/v2/saveFormData/createTask' },
|
|
7
7
|
taskUpdate: { entity: 'task', action: 'update', listPath: '', detailPath: '/api/ebuilder/form/formdata/v2/getFormDataByPk/queryTaskDetail', writePath: '/api/ebuilder/form/formdata/v2/updateFormData/updateTask' },
|
|
@@ -62,6 +62,31 @@ async function sendWrite(host, operation, path, datajsonObject) {
|
|
|
62
62
|
assertDatajsonSuccess(response, operation);
|
|
63
63
|
return response?.datajson ?? null;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* 新建成功后,把返回的 dataIds 映射成详情页地址,供展示层把记录名称渲染为可点击链接。
|
|
67
|
+
* 链接解析失败不影响创建结果(写入已成功),只附带 detail_url_warning。
|
|
68
|
+
*/
|
|
69
|
+
async function decorateCreatedRecords(host, entity, data) {
|
|
70
|
+
if (!data || typeof data !== 'object')
|
|
71
|
+
return data;
|
|
72
|
+
const value = data;
|
|
73
|
+
const ids = Array.isArray(value.dataIds)
|
|
74
|
+
? value.dataIds.map((item) => String(item)).filter((item) => item !== '')
|
|
75
|
+
: [];
|
|
76
|
+
if (ids.length === 0)
|
|
77
|
+
return data;
|
|
78
|
+
try {
|
|
79
|
+
const [objId, session] = await Promise.all([resolveObjectId(host, entity), host.getSession()]);
|
|
80
|
+
const urls = ids.map((id) => buildCardUrl(session.baseUrl, objId, id));
|
|
81
|
+
return urls.some((url) => url !== null) ? { ...value, detail_urls: urls } : data;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
...value,
|
|
86
|
+
detail_url_warning: `详情链接暂不可用:${error instanceof Error ? error.message : String(error)};创建已成功,不要因此重试。`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
65
90
|
async function verifySession(env, continuation) {
|
|
66
91
|
if (continuation.userId !== await operatorId(env.host)) {
|
|
67
92
|
throw projectValidationError('continuation_mismatch', 'continuation 与当前登录会话不匹配');
|
|
@@ -119,7 +144,8 @@ function createOperations(namePrefix, target) {
|
|
|
119
144
|
await verifySession(env, continuation);
|
|
120
145
|
const datajson = asObject(continuation.datajson, 'continuation.datajson');
|
|
121
146
|
const response = await sendWrite(env.host, applyName, target.writePath, datajson);
|
|
122
|
-
|
|
147
|
+
const data = await decorateCreatedRecords(env.host, target.entity, response);
|
|
148
|
+
return projectSuccess(applyName, data, { targetType: target.entity, writeAction: 'create' });
|
|
123
149
|
},
|
|
124
150
|
},
|
|
125
151
|
];
|