diff --git a/apps/server/src/integration/dingtalk.service.ts b/apps/server/src/integration/dingtalk.service.ts index 2dd9810..19bf595 100644 --- a/apps/server/src/integration/dingtalk.service.ts +++ b/apps/server/src/integration/dingtalk.service.ts @@ -72,6 +72,15 @@ export interface DingOrgTreeNode { children: DingOrgTreeNode[]; } +/** 钉钉部门树节点(含用户),供同步用户选择器使用 */ +export interface DingOrgTreeNodeWithUsers { + id: number; + name: string; + parentId: number; + children: DingOrgTreeNodeWithUsers[]; + users: Array<{ userid: string; name: string; mobile: string }>; +} + // ── 考勤排班 API 类型 ── /** 班次卡段打卡时间 */ @@ -455,6 +464,65 @@ export class DingTalkService { return roots; } + /** + * 获取钉钉组织部门树(含用户),供前端同步用户选择器使用。 + * 返回从指定 rootDeptId 开始的树,每个部门节点含 users 数组。 + */ + async fetchOrgTreeWithUsers(rootDeptId = 1): Promise { + if (!this.configured) { + throw new ServiceUnavailableException('钉钉未配置'); + } + const token = await this.getAccessToken(); + const deptIds = await this.getAllDeptIds(token, rootDeptId); + + // 拉每个部门详情 + const nodes: DingOrgTreeNodeWithUsers[] = []; + for (let i = 0; i < deptIds.length; i++) { + if (i > 0) await this.delay(i); + const detail = await this.getDeptDetail(token, deptIds[i]); + if (!detail) continue; + + // 拉该部门下的用户 + const dingUsers = await this.getDeptUsers(token, deptIds[i]); + + nodes.push({ + id: detail.dept_id, + name: detail.name, + parentId: detail.parent_id, + children: [], + users: dingUsers.map((u) => ({ + userid: u.userid, + name: u.name, + mobile: u.mobile, + })), + }); + } + + // 全局去重:同一个 dingUserId 可能在多个部门出现 + const seenUserIds = new Set(); + for (const node of nodes) { + node.users = node.users.filter((u) => { + if (seenUserIds.has(u.userid)) return false; + seenUserIds.add(u.userid); + return true; + }); + } + + // 组装成树 + const map = new Map(); + nodes.forEach((n) => map.set(n.id, n)); + const roots: DingOrgTreeNodeWithUsers[] = []; + for (const node of nodes) { + const parent = map.get(node.parentId); + if (parent && node.id !== rootDeptId) { + parent.children.push(node); + } else { + roots.push(node); + } + } + return roots; + } + // ═══════════════════════════════════════════ // Sync one user (with mapping) // ═══════════════════════════════════════════