weaver-work-cli 0.1.40 → 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/dist/chunks/{archive-JHTONRST.js → archive-J4I6MCKF.js} +2 -2
- package/dist/chunks/{archive-oa-FCHLCPHC.js → archive-oa-BRZKKTO4.js} +2 -2
- package/dist/chunks/{asset-RLIHC7DB.js → asset-L7D4FAOZ.js} +2 -2
- package/dist/chunks/{asset-idle-KVOFTLCP.js → asset-idle-DADLLV75.js} +2 -2
- package/dist/chunks/{attachment-content-RZYDB3MB.js → attachment-content-K4C7VLWQ.js} +3 -3
- package/dist/chunks/{blog-42OZTJNP.js → blog-BVGQ7NZL.js} +2 -2
- package/dist/chunks/{caizhilian-KRK547K5.js → caizhilian-SAADNFD6.js} +2 -2
- package/dist/chunks/{calendar-RTWAMKHD.js → calendar-Q7NQY5Z2.js} +61 -23
- package/dist/chunks/{chunk-LFMSDBZN.js → chunk-3KTFS4IA.js} +308 -153
- package/dist/chunks/{chunk-G2JSIUWF.js → chunk-4PD5NWAX.js} +89 -0
- package/dist/chunks/{chunk-T6EGVBKU.js → chunk-4RAE6JXP.js} +1 -1
- package/dist/chunks/{chunk-7BRU4EFH.js → chunk-72CKAUAG.js} +29 -2
- package/dist/chunks/{chunk-NETCCARD.js → chunk-APVFJUQH.js} +2 -2
- package/dist/chunks/{chunk-CAQNBBQA.js → chunk-BBKDXQEC.js} +2 -2
- package/dist/chunks/{chunk-57IECMWX.js → chunk-BIPYLLVS.js} +26 -12
- package/dist/chunks/{chunk-LN447PY2.js → chunk-EKWAQSLU.js} +3 -2
- package/dist/chunks/{chunk-4TEDNCON.js → chunk-FOAA27U5.js} +338 -52
- package/dist/chunks/{chunk-7U577Q7U.js → chunk-G4UR6NXN.js} +2 -2
- package/dist/chunks/{chunk-NJKREDA3.js → chunk-GEWYMQME.js} +1 -1
- package/dist/chunks/{chunk-I566ZYNF.js → chunk-GHXCDPTM.js} +1 -1
- package/dist/chunks/{chunk-4CTESSKI.js → chunk-IZ7S7QUS.js} +4 -4
- package/dist/chunks/{chunk-VNBU55SL.js → chunk-JPRO6ER2.js} +57 -2432
- package/dist/chunks/{chunk-IZSSVYAG.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-JWRZM2H2.js → chunk-QKNKR4RH.js} +3 -3
- package/dist/chunks/{chunk-FLS4DPGP.js → chunk-R4CHUSKC.js} +28 -15
- package/dist/chunks/{chunk-WFFNM65B.js → chunk-SA6VBIHY.js} +73 -19
- package/dist/chunks/{chunk-SPRHHSVC.js → chunk-TTPJJJC4.js} +2 -2
- package/dist/chunks/{chunk-7E6MUOEK.js → chunk-W7BEHOSR.js} +2 -2
- package/dist/chunks/{chunk-CXRQWOZ5.js → chunk-YRSZQUD2.js} +2 -2
- package/dist/chunks/{conflict-VR55AOOO.js → conflict-F2OMZRTO.js} +2 -2
- package/dist/chunks/{contract-HI4FNPYE.js → contract-5BETHRSG.js} +2 -2
- package/dist/chunks/{cowork-HJLI353D.js → cowork-3MN6EQQO.js} +22 -12
- package/dist/chunks/create-flow-5FQNNMVO.js +55 -0
- package/dist/chunks/{ebuilder-form-HTGI63UU.js → ebuilder-form-SBHOYBUD.js} +3 -3
- package/dist/chunks/{ehr-GYXIYH3A.js → ehr-KMOJULKG.js} +432 -1234
- package/dist/chunks/{env-DW3G4HUO.js → env-LB7NBL2O.js} +2 -2
- package/dist/chunks/{esb-I5INCEFE.js → esb-W2PJCFCX.js} +4 -4
- package/dist/chunks/{factory-TKXGXS6L.js → factory-6ZFQXQ46.js} +1 -1
- package/dist/chunks/{fna-K2MHTQ24.js → fna-ZQPCUV4R.js} +2 -2
- package/dist/chunks/{group-chat-VYBNMJLG.js → group-chat-2HHAJN46.js} +5 -3
- package/dist/chunks/{host-ZUQTJYYK.js → host-4CBHGOHT.js} +3 -3
- package/dist/chunks/{host-D5SERMAJ.js → host-ATKVNZAT.js} +3 -3
- package/dist/chunks/{host-JM4YVHFM.js → host-BQH22NTQ.js} +3 -3
- package/dist/chunks/{host-L3VLITMT.js → host-X2CSLFM6.js} +3 -3
- package/dist/chunks/{host-256GVCAH.js → host-YTODKMSL.js} +3 -3
- package/dist/chunks/{hrm-VLRBKTZB.js → hrm-FFM4UJFS.js} +4 -4
- package/dist/chunks/{im-QK3VTLXK.js → im-VEHKDQPB.js} +7 -7
- package/dist/chunks/{invoice-T5JTA32H.js → invoice-LVIKSIIF.js} +3 -3
- package/dist/chunks/{jiuchuanhui-P2FNEQVZ.js → jiuchuanhui-X6DINO5P.js} +2 -2
- package/dist/chunks/{lazy-dispatch-XRRI46M6.js → lazy-dispatch-KSWTN5L6.js} +4 -4
- package/dist/chunks/{mail-3Y7MJ6PS.js → mail-VDKRGUD3.js} +2 -2
- package/dist/chunks/{meeting-M7LTXGJE.js → meeting-CLC6HP3Z.js} +12 -11
- package/dist/chunks/{okr-OBC6S67L.js → okr-CHG4OO3U.js} +2 -2
- package/dist/chunks/{plan-5H6I53SK.js → plan-XWR75667.js} +2 -2
- package/dist/chunks/{project-L75QZV4S.js → project-5BXOOKMG.js} +40 -23
- package/dist/chunks/recruit-stage-DGMFNNDE.js +4507 -0
- package/dist/chunks/{registry-RXPN6BBT.js → registry-7FFOXIAW.js} +4 -4
- package/dist/chunks/{registry-KMHCPAAD.js → registry-BKBZPBAJ.js} +10 -9
- package/dist/chunks/{registry-QGHFNUWS.js → registry-OZSMDVPP.js} +2 -2
- package/dist/chunks/{registry-UGRBCBHO.js → registry-RHMW6ANW.js} +2 -2
- package/dist/chunks/{registry-NYPYMZXG.js → registry-Z43434VG.js} +1 -1
- package/dist/chunks/{room-recommend-A3UDC3CA.js → room-recommend-62H7ZQQ6.js} +4 -4
- package/dist/chunks/{schedule-5GCACCK7.js → schedule-FNXKH3DQ.js} +4 -4
- package/dist/chunks/{workflow-M5ZJ7DWF.js → workflow-ADHKCAJK.js} +6 -6
- package/dist/index.js +33 -28
- package/dist/internal/e10/auth/commands.js +1 -0
- package/dist/internal/e10/auth/messages.js +6 -0
- package/dist/internal/e10/auth/session.js +17 -0
- package/dist/internal/e10/auth/xiaoe.js +286 -44
- package/dist/internal/e10/auth-recovery.js +36 -5
- package/dist/internal/e10/context.js +56 -12
- package/dist/internal/e10/index.js +1 -1
- package/dist/internal/e10/network-error.js +93 -0
- package/dist/internal/e10/request-runtime.js +6 -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 +1 -7
- 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/meeting/operations/eb/catalog.js +4 -1
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +85 -28
- package/dist/shortcuts/meeting/operations/eb/factory.js +33 -0
- package/dist/shortcuts/meeting/operations/eb/group-chat.js +25 -3
- package/dist/shortcuts/meeting/operations/eb/person-cache.js +96 -0
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +33 -11
- 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/todoSmartConvert/detailFieldRules.js +4 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +13 -6
- 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 +50 -8
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +2 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +17 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +7 -39
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +3 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +51 -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 +39 -14
- package/docs/e10-auth.md +7 -1
- package/docs/operation-manual.md +8 -0
- package/package.json +1 -1
- package/scripts/install-skill-to-xiaoe.mjs +105 -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-meeting/SKILL.md +32 -2
- 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 +1 -1
- 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 +4 -2
- package/skills/weaver-e10-workflow/aigroup/default.json +11 -9
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +23 -13
- package/skills/weaver-e10-workflow/tools/todo-summary.cjs +62 -20
- package/skills/weaver-e10-yepiaotong/SKILL.md +9 -10
- package/skills/weaver-e10-yepiaotong/references/invoice-agent-entry.md +6 -3
- package/dist/chunks/{chunk-E7OK5WD5.js → chunk-2LV74IWP.js} +5 -5
|
@@ -244,3 +244,50 @@ export function assertEnumArray(value, allowed, field) {
|
|
|
244
244
|
throw ehrValidationError('enum_invalid', `${field} 必须是数组`);
|
|
245
245
|
return value.map((item) => assertEnum(item, allowed, field));
|
|
246
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* EHR 页面路由表(**集中一处,禁止各 operation 自行拼写**)。
|
|
249
|
+
* 来源:weaver-interface/business-apis/ehr/references/attend-appeal-save.md、
|
|
250
|
+
* hr-all-module.md(2026-11-04 真实环境取证)。
|
|
251
|
+
* 🔴 未取证的路由不在此表、不得臆造;某环境打不开时以实际地址为准并反馈维护者。
|
|
252
|
+
*/
|
|
253
|
+
export const EHR_PAGES = {
|
|
254
|
+
/** 考勤异常/申诉页面(创建的申诉在此可查) */
|
|
255
|
+
attendAbnormal: '/sp/attend/abnormal',
|
|
256
|
+
/** 在职员工名册(原通讯录) */
|
|
257
|
+
hrEmployeeManager: '/sp/hrm/hr/employeeManager',
|
|
258
|
+
/** 生日名单 */
|
|
259
|
+
hrBirthdayList: '/sp/hrm/hr/birthday/list',
|
|
260
|
+
/** 人员状态变更记录 */
|
|
261
|
+
hrPersonalStateChange: '/sp/hrm/hr/personalStateChange/record',
|
|
262
|
+
/** 时点员工名册 */
|
|
263
|
+
hrTpReportTp: '/sp/hrm/hr/tpReport/tp',
|
|
264
|
+
/** 历史曾在职名册 */
|
|
265
|
+
hrTpReportHw: '/sp/hrm/hr/tpReport/hw',
|
|
266
|
+
/** 离职员工名册 */
|
|
267
|
+
hrTpReportDismiss: '/sp/hrm/hr/tpReport/dismiss',
|
|
268
|
+
/** 人员流动分析表 */
|
|
269
|
+
hrTpReportTurn: '/sp/hrm/hr/tpReport/turn',
|
|
270
|
+
/** 基本状况分析 */
|
|
271
|
+
hrReportBasePerson: '/sp/hrm/hr/reportForm/basePerson',
|
|
272
|
+
/** 部门人数统计 */
|
|
273
|
+
hrReportDeptPerson: '/sp/hrm/hr/reportForm/deptPerson',
|
|
274
|
+
/** 岗位人数统计 */
|
|
275
|
+
hrReportPosiPerson: '/sp/hrm/hr/reportForm/posiPerson',
|
|
276
|
+
/** 职务状况统计 */
|
|
277
|
+
hrReportJobPerson: '/sp/hrm/hr/reportForm/jobPerson',
|
|
278
|
+
};
|
|
279
|
+
/** 拼接 EHR 页面地址(去尾斜杠;path 必须来自 EHR_PAGES)。 */
|
|
280
|
+
export function ehrPageUrl(baseUrl, path) {
|
|
281
|
+
return String(baseUrl || '').replace(/\/+$/, '') + path;
|
|
282
|
+
}
|
|
283
|
+
/** 人员详情卡片地址模板:替换 {人员id} 即为可点击地址(该接口无独立文档,查询到人员 id 后即附)。 */
|
|
284
|
+
export function hrProfileUrl(baseUrl, employeeId) {
|
|
285
|
+
return String(baseUrl || '').replace(/\/+$/, '') + '/sp/hrm/profileInfo/' + encodeURIComponent(String(employeeId));
|
|
286
|
+
}
|
|
287
|
+
/** 日期加减(YYYY-MM-DD,UTC 语义,避免时区跨天)。 */
|
|
288
|
+
export function addDays(date, delta) {
|
|
289
|
+
const [y, m, d] = String(date).split('-').map(Number);
|
|
290
|
+
const t = new Date(Date.UTC(y, m - 1, d + delta));
|
|
291
|
+
const f = (n) => String(n).padStart(2, '0');
|
|
292
|
+
return `${t.getUTCFullYear()}-${f(t.getUTCMonth() + 1)}-${f(t.getUTCDate())}`;
|
|
293
|
+
}
|
|
@@ -118,7 +118,125 @@ const orgDetail = {
|
|
|
118
118
|
});
|
|
119
119
|
},
|
|
120
120
|
};
|
|
121
|
+
/**
|
|
122
|
+
* 组织**模糊查找**(部门 + 分部一次查完,按相似度排序)。
|
|
123
|
+
*
|
|
124
|
+
* 2026-11-04 用户反馈:说部门/分部时关键字常常不完整(「会议管理」vs「会议管理部门」),
|
|
125
|
+
* 模型只能一遍遍调 CLI 试,每次 2 秒。这个接口一次调用返回所有近似结果 + 完整路径,
|
|
126
|
+
* 让模型直接选,不再反复试错。
|
|
127
|
+
*/
|
|
128
|
+
const orgLookup = {
|
|
129
|
+
name: 'hrm.org.lookup',
|
|
130
|
+
resource: 'org',
|
|
131
|
+
method: 'org.lookup',
|
|
132
|
+
action: 'hrm.org.lookup',
|
|
133
|
+
summary: '组织模糊查找:关键词**不用写全**,一次返回所有近似的部门/分部(含完整路径、按相似度排序)',
|
|
134
|
+
risk: READ,
|
|
135
|
+
inputSchema: schemaOf({
|
|
136
|
+
keyword: { type: 'string', minLength: 1, description: '部门/分部名称关键词(可只写一部分,如「会议管理」「研发」)' },
|
|
137
|
+
type: { type: 'string', enum: ['department', 'subcompany', 'both'], description: '默认 both(部门+分部都查)' },
|
|
138
|
+
limit: { type: 'number', minimum: 1, maximum: 50, description: '最多返回条数,默认 10' },
|
|
139
|
+
}, { required: ['keyword'] }),
|
|
140
|
+
handler: async (args, { host }) => {
|
|
141
|
+
const keyword = String(args.keyword ?? '').trim();
|
|
142
|
+
if (!keyword)
|
|
143
|
+
throw hrmValidationError('keyword_required', 'keyword 必填');
|
|
144
|
+
const scope = args.type === 'department' || args.type === 'subcompany' ? String(args.type) : 'both';
|
|
145
|
+
const limit = Math.max(1, Math.min(50, Number(args.limit) || 10));
|
|
146
|
+
const fetchOrg = async (type, text) => {
|
|
147
|
+
const body = {
|
|
148
|
+
...browserBaseBody(),
|
|
149
|
+
virtualType: 'ALL',
|
|
150
|
+
cmd: 'list',
|
|
151
|
+
formDatas: { name: [{ nameCondition: { id: 'in', content: '包含', disabled: false }, name: text }] },
|
|
152
|
+
quickSearchDatas: {},
|
|
153
|
+
type: 'and',
|
|
154
|
+
stopDepartment: false,
|
|
155
|
+
current: 1,
|
|
156
|
+
pageSize: 200,
|
|
157
|
+
};
|
|
158
|
+
const path = type === 'subcompany'
|
|
159
|
+
? '/api/hrm/common/browser/data/subcompany'
|
|
160
|
+
: '/api/hrm/common/browser/data/department';
|
|
161
|
+
const resp = await host.request({ method: 'POST', path, body });
|
|
162
|
+
const rows = (resp?.data?.data ?? []);
|
|
163
|
+
return rows.map((r) => ({
|
|
164
|
+
id: String(r.id ?? ''),
|
|
165
|
+
name: String(r.name ?? ''),
|
|
166
|
+
type,
|
|
167
|
+
fullPath: String(r.fullPath ?? r.path ?? ''),
|
|
168
|
+
...(r.subcompanyName ? { subcompanyName: String(r.subcompanyName) } : {}),
|
|
169
|
+
...(r.parentName ? { parentName: String(r.parentName) } : {}),
|
|
170
|
+
})).filter((r) => r.id);
|
|
171
|
+
};
|
|
172
|
+
const types = scope === 'both'
|
|
173
|
+
? ['department', 'subcompany']
|
|
174
|
+
: [scope];
|
|
175
|
+
const collected = [];
|
|
176
|
+
let requests = 0;
|
|
177
|
+
const stripSuffix = (text) => text.replace(/(部门|事业部|中心|科室|组|部)$/u, '').trim();
|
|
178
|
+
for (const type of types) {
|
|
179
|
+
requests += 1;
|
|
180
|
+
collected.push(...await fetchOrg(type, keyword));
|
|
181
|
+
// 关键词带了「部门/中心/组/部」后缀但没命中 → 去掉后缀再查一次(用户常说全称,库里可能不带后缀)
|
|
182
|
+
const stripped = stripSuffix(keyword);
|
|
183
|
+
if (collected.length === 0 && stripped !== '' && stripped !== keyword) {
|
|
184
|
+
const raws = await fetchOrg(type, stripped);
|
|
185
|
+
// 二次命中把分数降一档(不是精确关键词命中)
|
|
186
|
+
collected.push(...raws.map((row) => ({ ...row, stripped: true })));
|
|
187
|
+
requests += 1;
|
|
188
|
+
}
|
|
189
|
+
// 仍然没有 → **逐字缩短**再试(「会议管理部门」库里叫「会议模块组」,只能靠「会议」命中)
|
|
190
|
+
if (collected.length === 0) {
|
|
191
|
+
for (let len = Math.min(keyword.length, 6) - 1; len >= 2; len -= 1) {
|
|
192
|
+
const short = keyword.slice(0, len);
|
|
193
|
+
const raws = await fetchOrg(type, short);
|
|
194
|
+
requests += 1;
|
|
195
|
+
if (raws.length > 0) {
|
|
196
|
+
collected.push(...raws.map((row) => ({ ...row, shortened: short })));
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const scoreOf = (row) => {
|
|
203
|
+
const name = String(row.name);
|
|
204
|
+
const pathText = String(row.fullPath ?? '');
|
|
205
|
+
let score = 0;
|
|
206
|
+
if (name === keyword)
|
|
207
|
+
score = 100;
|
|
208
|
+
else if (name.startsWith(keyword))
|
|
209
|
+
score = 85;
|
|
210
|
+
else if (name.includes(keyword))
|
|
211
|
+
score = 70;
|
|
212
|
+
else if (pathText.includes(keyword))
|
|
213
|
+
score = 50;
|
|
214
|
+
else
|
|
215
|
+
score = 35; // 去后缀后命中
|
|
216
|
+
if (row.type === 'department')
|
|
217
|
+
score += 5; // 同分时部门优先(会议按部门找人的场景更多)
|
|
218
|
+
if (row.stripped)
|
|
219
|
+
score -= 5;
|
|
220
|
+
if (row.shortened)
|
|
221
|
+
score -= 15; // 逐字缩短命中的,可信度最低
|
|
222
|
+
return score;
|
|
223
|
+
};
|
|
224
|
+
const rows = collected
|
|
225
|
+
.map((row) => ({ ...row, score: scoreOf(row) }))
|
|
226
|
+
.sort((left, right) => Number(right.score) - Number(left.score)
|
|
227
|
+
|| String(left.name).localeCompare(String(right.name), 'zh-Hans-CN'))
|
|
228
|
+
.slice(0, limit);
|
|
229
|
+
return hrmSuccess('hrm.org.lookup', {
|
|
230
|
+
rows,
|
|
231
|
+
total: rows.length,
|
|
232
|
+
...(rows.length === 0
|
|
233
|
+
? { hint: `没有名称含「${keyword}」的部门或分部。换更短的关键词(如两个字)再试,或用 hrm.org.query 按 parent/subcompany 逐级找。` }
|
|
234
|
+
: {}),
|
|
235
|
+
}, { requests, keyword });
|
|
236
|
+
},
|
|
237
|
+
};
|
|
121
238
|
export const hrmOrgOperations = [
|
|
122
239
|
orgQuery,
|
|
240
|
+
orgLookup,
|
|
123
241
|
orgDetail,
|
|
124
242
|
];
|
package/dist/shortcuts/index.js
CHANGED
|
@@ -35,6 +35,11 @@ export const builtinShortcuts = [
|
|
|
35
35
|
description: '泛微E10聚才林(人事/出勤/薪酬)CLI',
|
|
36
36
|
load: async () => (await import('./ehr/index.js')).ehrShortcut,
|
|
37
37
|
}),
|
|
38
|
+
lazyShortcut({
|
|
39
|
+
name: 'recruit-stage',
|
|
40
|
+
description: '泛微E10招聘助手(JD生成/简历建档/AI面试邀约)CLI',
|
|
41
|
+
load: async () => (await import('./recruit-stage/index.js')).recruitStageShortcut,
|
|
42
|
+
}),
|
|
38
43
|
lazyShortcut({
|
|
39
44
|
name: 'cowork',
|
|
40
45
|
description: '泛微E10协作区(协作事项/讨论/审批/共享权限)CLI',
|
|
@@ -276,7 +276,10 @@ export const EB_ENDPOINTS = [
|
|
|
276
276
|
interfacePk: 'query_roomusage_list',
|
|
277
277
|
countPk: 'query_roomusage_count',
|
|
278
278
|
queryParams: ['mt_id', 'address', 'enddatetime', 'begindatetime'],
|
|
279
|
-
|
|
279
|
+
dateFilter: true,
|
|
280
|
+
paramAliases: { roomId: 'address', room_id: 'address', day: 'date' },
|
|
281
|
+
summary: 'EB 版会议室占用列表(query_roomusage_list + count)。按日期过滤传 date(yyyy-MM-dd)'
|
|
282
|
+
+ '或 begindatetime/enddatetime;**不传任何时间条件 = 返回全量历史**,不是接口不支持过滤',
|
|
280
283
|
},
|
|
281
284
|
{
|
|
282
285
|
domain: 'roomUsage',
|
|
@@ -9,6 +9,7 @@ import { EB_BASE, ebBusinessSuccess, writeBody } from './factory.js';
|
|
|
9
9
|
import { loadRooms, recommendRooms } from './room-recommend.js';
|
|
10
10
|
import { dayOf, filterUsages, invalidateDayUsages, loadDayUsages } from './usage-cache.js';
|
|
11
11
|
import { pickRememberedFlow, readFlowPick, rememberFlowPick } from './flow-memory.js';
|
|
12
|
+
import { resolveNamesCached } from './person-cache.js';
|
|
12
13
|
/**
|
|
13
14
|
* 通过**流程**新建 EB 会议(`meeting.eb.meeting.create.flow`)。
|
|
14
15
|
*
|
|
@@ -75,30 +76,17 @@ async function loadCreateForm(env, workflowId) {
|
|
|
75
76
|
workflowName: String(wf.name ?? wf.workflowName ?? '').trim() || undefined,
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* 取员工姓名(**缓存优先**:命中直接返回,未命中才打 hrm.employee.batch)。
|
|
81
|
+
*
|
|
82
|
+
* 2026-11-04 用户要求:一次建会会查 3~5 次同一批人,每次 ~2s,必须缓存。
|
|
83
|
+
* 缓存位置 `person-v1-<租户>.json`,TTL 24h(见 person-cache.ts)。
|
|
84
|
+
*/
|
|
79
85
|
async function resolveEmployeeNames(env, ids) {
|
|
80
|
-
const map = new Map();
|
|
81
86
|
const unique = [...new Set(ids.map((id) => String(id).trim()).filter((id) => id !== ''))];
|
|
82
87
|
if (unique.length === 0)
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
const { hrmEmployeeOperations } = await import('../../../hrm/operations/employee.js');
|
|
86
|
-
const batch = hrmEmployeeOperations.find((operation) => operation.name === 'hrm.employee.batch');
|
|
87
|
-
if (!batch)
|
|
88
|
-
return map;
|
|
89
|
-
const result = await batch.handler({ ids: unique }, env);
|
|
90
|
-
const rows = (result?.data?.rows ?? result?.data?.userList ?? []);
|
|
91
|
-
for (const row of rows) {
|
|
92
|
-
const id = String(row.id ?? '').trim();
|
|
93
|
-
const name = String(row.name ?? '').trim();
|
|
94
|
-
if (id && name)
|
|
95
|
-
map.set(id, name);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
// 查不到姓名就退回「以 id 当名称」,工作流多数能按 id 直接命中
|
|
100
|
-
}
|
|
101
|
-
return map;
|
|
88
|
+
return new Map();
|
|
89
|
+
return resolveNamesCached(env, unique);
|
|
102
90
|
}
|
|
103
91
|
/**
|
|
104
92
|
* 把「员工 id 或姓名」解析成 `{ id, name }`。
|
|
@@ -1038,6 +1026,36 @@ export function shouldAutoPickFlow(args) {
|
|
|
1038
1026
|
return true;
|
|
1039
1027
|
return String(process.env.WEAVER_MEETING_FLOW_CHOICE ?? '').trim().toLowerCase() === 'auto';
|
|
1040
1028
|
}
|
|
1029
|
+
/**
|
|
1030
|
+
* 把邀请函 docx 发到会议群(IM 两段式:prepare → apply)。
|
|
1031
|
+
*
|
|
1032
|
+
* ⛔ 只传 file、**不带 txt**:实测「txt + file」同传时服务端只落文本、文件被丢弃
|
|
1033
|
+
* (2026-11-04 用户截图:群里只有一行「会议邀请函:xxx」)。
|
|
1034
|
+
* 失败只记 warning —— 会议和邀请函都已生成,发群失败不该影响建会结果。
|
|
1035
|
+
*/
|
|
1036
|
+
async function sendInvitationToGroup(env, groupId, filePath, warnings) {
|
|
1037
|
+
const started = Date.now();
|
|
1038
|
+
try {
|
|
1039
|
+
const { buildImEnv } = await import('./group-chat.js');
|
|
1040
|
+
const { getImOperation } = await import('../../../im/operations/registry.js');
|
|
1041
|
+
const imEnv = await buildImEnv(env);
|
|
1042
|
+
const prepared = await getImOperation('im.msg.send.group.prepare').handler({ groupId, file: filePath }, imEnv);
|
|
1043
|
+
const continuation = String(prepared?.data?.continuation ?? '');
|
|
1044
|
+
const applied = continuation
|
|
1045
|
+
? await getImOperation('im.msg.send.group.apply').handler({ continuation, confirm: true }, imEnv)
|
|
1046
|
+
: null;
|
|
1047
|
+
const ok = applied?.ok === true;
|
|
1048
|
+
if (!ok) {
|
|
1049
|
+
warnings.push(`邀请函没能发到群里(${String(applied?.error?.message ?? prepared?.error?.message ?? '未知原因').slice(0, 150)});`
|
|
1050
|
+
+ '文件已生成,可手动转发。');
|
|
1051
|
+
}
|
|
1052
|
+
return { ok, ms: Date.now() - started };
|
|
1053
|
+
}
|
|
1054
|
+
catch (error) {
|
|
1055
|
+
warnings.push(`邀请函发群异常(不影响建会):${String(error?.message ?? error).slice(0, 150)}`);
|
|
1056
|
+
return { ok: false, ms: Date.now() - started };
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1041
1059
|
/** 解析要用的建会流程 id:入参优先(可多个),其次固化列表,最后才是应用设置里的 mt_new_wf。 */
|
|
1042
1060
|
export async function resolveWorkflowId(env, args) {
|
|
1043
1061
|
const explicitList = normalizeWorkflowIds(args.workflowId);
|
|
@@ -1493,9 +1511,16 @@ const ebMeetingCreateFlowPrepare = {
|
|
|
1493
1511
|
const roomMemberCount = declaredTotal
|
|
1494
1512
|
?? (memberIds.length > 0 ? memberIds.length + (callerInMembers ? 0 : 1) : undefined);
|
|
1495
1513
|
// 有没有客户:决定要不要优先推接待类会议室(crm_members 可能是数组、"id,id" 或名称)
|
|
1496
|
-
|
|
1514
|
+
// 2026-11-04 补:用户只在话里说了「接待某某客户」、没传 crm_members 时,也要识别出来 ——
|
|
1515
|
+
// 否则"带客户优先接待类"这条规则永远不生效(用户实测推荐了 3 间普通会议室)。
|
|
1516
|
+
const crmMentionedInText = detectCrmMentionInText(String(args.mt_name ?? ''), String(args.mt_desc ?? ''), String(args.other_remark ?? ''));
|
|
1517
|
+
// 「接待湖北省政府客户」这种说法不含「客户是/为」句式,上面的正则接不住 —— 再用关键词兜一层。
|
|
1518
|
+
const customerWords = /(接待|招待|客户|贵宾|来访)/u.test(`${String(args.mt_name ?? '')} ${String(args.mt_desc ?? '')} ${String(args.other_remark ?? '')}`);
|
|
1519
|
+
const hasCustomer = (Array.isArray(args.crm_members)
|
|
1497
1520
|
? args.crm_members.length > 0
|
|
1498
|
-
: String(args.crm_members ?? '').trim() !== ''
|
|
1521
|
+
: String(args.crm_members ?? '').trim() !== '')
|
|
1522
|
+
|| Boolean(crmMentionedInText)
|
|
1523
|
+
|| customerWords;
|
|
1499
1524
|
// 全天占用缓存(2026-11-03 用户方案):不带 address 条件一次拉当天全部会议室占用(~2s,
|
|
1500
1525
|
// 5 分钟 TTL),推荐 busy 判定与指定房间的占用检测**共用同一份**;同一天内改时间/换房间
|
|
1501
1526
|
// 不再打占用接口。提交成功后当天缓存立即作废(见提交成功处)。
|
|
@@ -2251,9 +2276,20 @@ const ebMeetingCreateFlowPrepare = {
|
|
|
2251
2276
|
// 建群要素:apply 拿到会议 id 后先建群、再把群 id 回写 imchat_groupid
|
|
2252
2277
|
groupChat: {
|
|
2253
2278
|
memberIds: groupMemberIds,
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2279
|
+
// ⛔ 2026-11-04 修:用户只说"明天上午"(time_slot)时,begin_datetime 是 CLI 展开出来的,
|
|
2280
|
+
// `shaped.values` 里**没有**这个键 —— 直接取会得到空串,群消息里"开始时间/结束时间"就空着。
|
|
2281
|
+
// 这里统一用展开后的 beginText/endText。
|
|
2282
|
+
beginDatetime: beginText,
|
|
2283
|
+
endDatetime: endText,
|
|
2284
|
+
// ⛔ 2026-11-04 修:shaped.address 在这之前已被解析成 `{id,name}` 对象,
|
|
2285
|
+
// 直接 String() 会变成 "[object Object]" 打进群消息(用户实测看到的就是这个)。
|
|
2286
|
+
// 群消息要的是人话名称,这里优先取 name。
|
|
2287
|
+
address: (() => {
|
|
2288
|
+
const value = shaped.values.address;
|
|
2289
|
+
if (value && typeof value === 'object')
|
|
2290
|
+
return String(value.name ?? value.id ?? '');
|
|
2291
|
+
return String(value ?? '');
|
|
2292
|
+
})(),
|
|
2257
2293
|
},
|
|
2258
2294
|
},
|
|
2259
2295
|
fingerprint: JSON.stringify({ workflowId, main, ...(detailPayload ? { details: detailPayload } : {}) }),
|
|
@@ -2526,6 +2562,16 @@ const ebMeetingCreateFlowApply = {
|
|
|
2526
2562
|
// 落盘失败不影响输出表单;补绑时可改传 requestId
|
|
2527
2563
|
}
|
|
2528
2564
|
}
|
|
2565
|
+
// 邀请函也要发群(2026-11-04 用户实测:浏览器填单这一档建了群却没收不到邀请函)。
|
|
2566
|
+
// 这里发的是**预置函**(按申请单信息生成,字段可能有【待补充】),审批通过后可用 meetingId 重新生成。
|
|
2567
|
+
const browserWarnings = [];
|
|
2568
|
+
let browserInvitation = pre.invitation;
|
|
2569
|
+
const browserGroupId = String(groupChat?.groupId ?? '').trim();
|
|
2570
|
+
const browserInvitePath = String(pre.invitation?.filePath ?? '').trim();
|
|
2571
|
+
if (browserGroupId && browserInvitePath) {
|
|
2572
|
+
const sent = await sendInvitationToGroup(env, browserGroupId, browserInvitePath, browserWarnings);
|
|
2573
|
+
browserInvitation = { ...pre.invitation, sentToGroup: { groupId: browserGroupId, ok: sent.ok } };
|
|
2574
|
+
}
|
|
2529
2575
|
return meetingSuccess('meeting.eb.meeting.create.flow.apply', {
|
|
2530
2576
|
status: 'BROWSER_FILL',
|
|
2531
2577
|
workflowId: payload.workflowId,
|
|
@@ -2539,7 +2585,7 @@ const ebMeetingCreateFlowApply = {
|
|
|
2539
2585
|
finalMarkdownHint: '把 finalMarkdown 原样贴在回复最后(从 <browsertool> 到 </browsertool>,不包代码块);'
|
|
2540
2586
|
+ '不贴这段=用户看不到表单,也不能说"表单已打开"。',
|
|
2541
2587
|
message: String(submittedData.message ?? ''),
|
|
2542
|
-
invitation:
|
|
2588
|
+
invitation: browserInvitation,
|
|
2543
2589
|
...(groupChat ? { groupChat } : {}),
|
|
2544
2590
|
timings: { submitMs: Date.now() - submitStartedAt, invitationMs: pre.ms },
|
|
2545
2591
|
}, {
|
|
@@ -2554,8 +2600,11 @@ const ebMeetingCreateFlowApply = {
|
|
|
2554
2600
|
? [`群聊没建成功(${String(groupChat.error ?? '未知原因')}):会议照常可以在页面里提交,群需要手工建或稍后重试。`]
|
|
2555
2601
|
: []),
|
|
2556
2602
|
`邀请函已按申请单信息**预生成**(预置函,${String(pre.invitation.filePath ?? '见 filePath')}):`
|
|
2557
|
-
+
|
|
2603
|
+
+ (browserInvitation.sentToGroup?.ok
|
|
2604
|
+
? '已把 docx 发到会议群;字段可能含【待补充】,审批通过、会议生成后可用 mtId 再跑一次 meeting.invitation.render 拿完整版本。'
|
|
2605
|
+
: '请下载后人工核对、自行发送;流程审批通过、会议生成后可用 mtId 再跑一次 meeting.invitation.render 拿完整字段版本。'),
|
|
2558
2606
|
'⛔ 不要因为"没有 requestId / 没有会议 id"重复 apply —— 填单要在那个浏览器页面里点提交。',
|
|
2607
|
+
...browserWarnings,
|
|
2559
2608
|
],
|
|
2560
2609
|
});
|
|
2561
2610
|
}
|
|
@@ -2712,6 +2761,14 @@ const ebMeetingCreateFlowApply = {
|
|
|
2712
2761
|
+ '审批通过、会议生成后可用 mtId 再跑一次 meeting.invitation.render 拿到完整字段版本。'
|
|
2713
2762
|
+ '⛔ 本 operation 不重复提交流程 —— 不要因为"没有会议 id"就重复 apply。');
|
|
2714
2763
|
}
|
|
2764
|
+
// 邀请函自动发群(2026-11-04 用户要求):群已建好 + 邀请函文件已生成 → 把 docx 直接发进群里。
|
|
2765
|
+
const inviteGroupId = String(groupChat?.groupId ?? '').trim();
|
|
2766
|
+
const inviteFilePath = String(invitation?.filePath ?? '').trim();
|
|
2767
|
+
if (inviteGroupId && inviteFilePath) {
|
|
2768
|
+
const sent = await sendInvitationToGroup(env, inviteGroupId, inviteFilePath, warnings);
|
|
2769
|
+
invitation.sentToGroup = { groupId: inviteGroupId, ok: sent.ok };
|
|
2770
|
+
timings.invitationSendMs = sent.ms;
|
|
2771
|
+
}
|
|
2715
2772
|
return meetingSuccess('meeting.eb.meeting.create.flow.apply', {
|
|
2716
2773
|
status: 'COMPLETE',
|
|
2717
2774
|
timings,
|
|
@@ -212,6 +212,18 @@ function buildReadInputSchema(endpoint) {
|
|
|
212
212
|
for (const param of endpoint.queryParams ?? []) {
|
|
213
213
|
properties[param] = { type: 'string', description: `EB 筛选参数(URL 占位符名 ${param})` };
|
|
214
214
|
}
|
|
215
|
+
// 便捷日期参数(2026-11-04):模型最常传的就是 date,别让它因为"没这个参数"去拿全量再下错结论。
|
|
216
|
+
if (endpoint.dateFilter) {
|
|
217
|
+
properties.date = {
|
|
218
|
+
type: 'string',
|
|
219
|
+
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
|
|
220
|
+
description: '按日期过滤(yyyy-MM-dd):等价于 begindatetime=当天 00:00 + enddatetime=当天 23:59。'
|
|
221
|
+
+ '⛔ 不传 date 且不传起止时间 = 返回**全量历史**(不是接口不支持按日期过滤)',
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
for (const [alias, target] of Object.entries(endpoint.paramAliases ?? {})) {
|
|
225
|
+
properties[alias] = { type: 'string', description: `入参别名,等价于 ${target}` };
|
|
226
|
+
}
|
|
215
227
|
for (const param of endpoint.mainTableParams ?? []) {
|
|
216
228
|
properties[param] = {
|
|
217
229
|
type: 'string',
|
|
@@ -297,6 +309,27 @@ function buildReadOperation(endpoint) {
|
|
|
297
309
|
if (value !== undefined && value !== null && value !== '')
|
|
298
310
|
query[param] = value;
|
|
299
311
|
}
|
|
312
|
+
// 别名落地(roomId→address 之类);只在目标参数没传时生效,不覆盖显式值
|
|
313
|
+
for (const [alias, target] of Object.entries(endpoint.paramAliases ?? {})) {
|
|
314
|
+
const value = args[alias];
|
|
315
|
+
if (value === undefined || value === null || value === '')
|
|
316
|
+
continue;
|
|
317
|
+
if (query[target] === undefined) {
|
|
318
|
+
if (target === 'date')
|
|
319
|
+
continue; // date 不是 query 参数,下面单独展开
|
|
320
|
+
query[target] = value;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// 便捷日期:date(或别名 day)→ 当天的 begindatetime/enddatetime
|
|
324
|
+
if (endpoint.dateFilter) {
|
|
325
|
+
const day = String(args.date ?? args.day ?? '').trim().slice(0, 10);
|
|
326
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(day)) {
|
|
327
|
+
if (query.begindatetime === undefined)
|
|
328
|
+
query.begindatetime = `${day} 00:00`;
|
|
329
|
+
if (query.enddatetime === undefined)
|
|
330
|
+
query.enddatetime = `${day} 23:59`;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
300
333
|
if (endpoint.kind === 'get') {
|
|
301
334
|
const response = await env.host.request({
|
|
302
335
|
method: 'POST',
|
|
@@ -33,7 +33,7 @@ async function buildEsbEnv(env) {
|
|
|
33
33
|
const { createEsbContinuationSigner } = await import('../../../esb/continuation.js');
|
|
34
34
|
return { ctx: env.ctx, host: createEsbHost(env.ctx), continuation: createEsbContinuationSigner(env.ctx) };
|
|
35
35
|
}
|
|
36
|
-
async function buildImEnv(env) {
|
|
36
|
+
export async function buildImEnv(env) {
|
|
37
37
|
const { createImHost } = await import('../../../im/host.js');
|
|
38
38
|
const { createImContinuationSigner } = await import('../../../im/continuation.js');
|
|
39
39
|
return { ctx: env.ctx, host: createImHost(env.ctx), continuation: createImContinuationSigner(env.ctx) };
|
|
@@ -203,8 +203,30 @@ async function triggerGroupFlow(env, input, meetingName, memberIds) {
|
|
|
203
203
|
enddatetime: String(input.endDatetime ?? ''),
|
|
204
204
|
hrmmembers: memberIds.join(','),
|
|
205
205
|
};
|
|
206
|
-
if (input.address)
|
|
207
|
-
|
|
206
|
+
if (input.address) {
|
|
207
|
+
// ⛔ 2026-11-04 用户反馈:建群动作流把 `address` **原样打进群消息**(「会议地点:100502820000000226」)。
|
|
208
|
+
// ESB 模板改不了,所以这里先把它解析成会议室名称再传;解析不到才退回原值。
|
|
209
|
+
// 另外入参可能是 `{id,name}` 对象或被 String() 过的 "[object Object]",一并兜住。
|
|
210
|
+
const raw = input.address;
|
|
211
|
+
let addressText = raw && typeof raw === 'object'
|
|
212
|
+
? String(raw.name ?? raw.id ?? '')
|
|
213
|
+
: String(raw).trim();
|
|
214
|
+
if (addressText === '[object Object]')
|
|
215
|
+
addressText = '';
|
|
216
|
+
if (addressText) {
|
|
217
|
+
try {
|
|
218
|
+
const { resolveAddressRoom } = await import('./create-flow.js');
|
|
219
|
+
const room = await resolveAddressRoom(env, addressText);
|
|
220
|
+
if (room?.name)
|
|
221
|
+
addressText = room.name;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// 台账读不到不影响建群,退回原值
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (addressText)
|
|
228
|
+
mainTable.address = addressText;
|
|
229
|
+
}
|
|
208
230
|
try {
|
|
209
231
|
const { getEsbOperation } = await import('../../../esb/operations/registry.js');
|
|
210
232
|
const esbEnv = await buildEsbEnv(env);
|
|
@@ -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
|
+
}
|