feat: 企微通讯录选人替换手填 userid(项目负责人/总群成员/群主合作方)
- wecom-client 新增 listInternalContacts:department/list + 逐部门 user/simplelist(cursor 翻页、去重、部门名映射) - 新 action wecom_list_internal_contacts(仅返回应用可见范围内成员) - admin 新增 WecomUserPickerModal:单选点行填入/多选勾选合并,姓名+userid 搜索,5 分钟共享缓存 - 三处接线:项目负责人(单选)、项目总群成员(多选合并去重)、新建群主合作方(单选) - 拉取失败(60020 等)提示手填兜底,手填路径不变
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
getAppChat,
|
||||
listCustomerGroupChats,
|
||||
listExternalContacts,
|
||||
listInternalContacts,
|
||||
resolveWecomConfig,
|
||||
sendAppChatMessage,
|
||||
sendWecomAppMessage,
|
||||
@@ -57,6 +58,56 @@ function fakeWecom(options = {}) {
|
||||
invaliduser: invalid.join("|"),
|
||||
});
|
||||
}
|
||||
if (url.pathname.endsWith("/cgi-bin/department/list")) {
|
||||
if (options.departmentErrcode) {
|
||||
return Response.json({
|
||||
errcode: options.departmentErrcode,
|
||||
errmsg: "not allow to access from your ip",
|
||||
});
|
||||
}
|
||||
return Response.json({
|
||||
errcode: 0,
|
||||
errmsg: "ok",
|
||||
department: options.departments ?? [
|
||||
{ id: 1, name: "总办" },
|
||||
{ id: 2, name: "运营部" },
|
||||
],
|
||||
});
|
||||
}
|
||||
if (url.pathname.endsWith("/cgi-bin/user/simplelist")) {
|
||||
const departmentId = url.searchParams.get("department_id") ?? "";
|
||||
const cursor = url.searchParams.get("cursor") ?? "";
|
||||
const table = options.departmentUsers ?? {
|
||||
"1": [
|
||||
{ userid: "zhangsan", name: "张三", department: [1, 2] },
|
||||
{ userid: "lisi", name: "李四", department: [1] },
|
||||
],
|
||||
"2": [
|
||||
{ userid: "zhangsan", name: "张三", department: [1, 2] },
|
||||
{ userid: "wangwu", name: "王五", department: [2] },
|
||||
],
|
||||
};
|
||||
if (options.paginateSimplelist && departmentId === "1") {
|
||||
if (!cursor) {
|
||||
return Response.json({
|
||||
errcode: 0,
|
||||
errmsg: "ok",
|
||||
userlist: table[departmentId] ?? [],
|
||||
next_cursor: "cursor-2",
|
||||
});
|
||||
}
|
||||
return Response.json({
|
||||
errcode: 0,
|
||||
errmsg: "ok",
|
||||
userlist: [{ userid: "pagetwo", name: "第二页", department: [1] }],
|
||||
});
|
||||
}
|
||||
return Response.json({
|
||||
errcode: 0,
|
||||
errmsg: "ok",
|
||||
userlist: table[departmentId] ?? [],
|
||||
});
|
||||
}
|
||||
return new Response("not found", { status: 404 });
|
||||
};
|
||||
return { calls, fetchImpl };
|
||||
@@ -947,3 +998,61 @@ test("sendAppChatMessage throws WecomClientError when provider errors", async ()
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("listInternalContacts merges departments, dedupes and maps dept names", async () => {
|
||||
const { calls, fetchImpl } = fakeWecom();
|
||||
const contacts = await listInternalContacts(
|
||||
resolveWecomConfig(baseBindings),
|
||||
fetchImpl,
|
||||
);
|
||||
assert.deepEqual(
|
||||
contacts.map((contact) => `${contact.name}:${contact.userid}`),
|
||||
["李四:lisi", "王五:wangwu", "张三:zhangsan"],
|
||||
"deduped and sorted by name (zh)",
|
||||
);
|
||||
const zhangsan = contacts.find((contact) => contact.userid === "zhangsan");
|
||||
assert.deepEqual(zhangsan.departmentNames.sort(), ["总办", "运营部"]);
|
||||
const simplelistCalls = calls.filter((call) =>
|
||||
call.url.pathname.endsWith("/cgi-bin/user/simplelist"),
|
||||
);
|
||||
assert.deepEqual(
|
||||
simplelistCalls
|
||||
.map((call) => call.url.searchParams.get("department_id"))
|
||||
.sort(),
|
||||
["1", "2"],
|
||||
"one simplelist call per department",
|
||||
);
|
||||
assert.ok(
|
||||
simplelistCalls.every((call) => call.url.searchParams.get("fetch_child") === "0"),
|
||||
);
|
||||
});
|
||||
|
||||
test("listInternalContacts follows simplelist cursor pages", async () => {
|
||||
const { fetchImpl } = fakeWecom({ paginateSimplelist: true });
|
||||
const contacts = await listInternalContacts(
|
||||
resolveWecomConfig(baseBindings),
|
||||
fetchImpl,
|
||||
);
|
||||
assert.ok(contacts.some((contact) => contact.userid === "pagetwo"));
|
||||
});
|
||||
|
||||
test("listInternalContacts throws WecomClientError on department errcode", async () => {
|
||||
const { fetchImpl } = fakeWecom({ departmentErrcode: 60020 });
|
||||
await assert.rejects(
|
||||
() => listInternalContacts(resolveWecomConfig(baseBindings), fetchImpl),
|
||||
(error) =>
|
||||
error instanceof WecomClientError && /部门列表/.test(error.message),
|
||||
);
|
||||
});
|
||||
|
||||
test("listInternalContacts rejects without app credentials", async () => {
|
||||
const { fetchImpl } = fakeWecom();
|
||||
await assert.rejects(
|
||||
() =>
|
||||
listInternalContacts(
|
||||
resolveWecomConfig({ ...baseBindings, WECOM_SECRET: "" }),
|
||||
fetchImpl,
|
||||
),
|
||||
/未配置/,
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user