forked from wangziqi/gongxue-base
feat: implement fetchOrgTree and fetchOrgTreeWithUsers via dingtalk API
This commit is contained in:
@@ -47,6 +47,29 @@ export interface DingTalkAttendanceResult {
|
|||||||
checkType: string;
|
checkType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 组织架构 API 类型 ──
|
||||||
|
|
||||||
|
interface DingTalkDeptListResponse {
|
||||||
|
errcode: number;
|
||||||
|
result?: Array<{ dept_id: number; name: string; parent_id: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DingTalkDeptGetResponse {
|
||||||
|
errcode: number;
|
||||||
|
result?: { name: string; parent_id: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrgDeptNode {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
parentId: number;
|
||||||
|
children: OrgDeptNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrgDeptNodeWithUsers extends OrgDeptNode {
|
||||||
|
users: Array<{ userid: string; name: string; mobile: string; deptIds: number[] }>;
|
||||||
|
}
|
||||||
|
|
||||||
// ── 考勤排班 API 类型 ──
|
// ── 考勤排班 API 类型 ──
|
||||||
|
|
||||||
/** 班次卡段打卡时间 */
|
/** 班次卡段打卡时间 */
|
||||||
@@ -237,7 +260,6 @@ export class DingTalkService {
|
|||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
const token = await this.getAccessToken();
|
const token = await this.getAccessToken();
|
||||||
|
|
||||||
// ── Sync users from root department ──
|
|
||||||
let userCount = 0;
|
let userCount = 0;
|
||||||
const seenUserIds = new Set<string>();
|
const seenUserIds = new Set<string>();
|
||||||
|
|
||||||
@@ -256,20 +278,79 @@ export class DingTalkService {
|
|||||||
return { deptCount: 0, userCount };
|
return { deptCount: 0, userCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ═══════════════════════════════════════════
|
||||||
* 获取钉钉组织部门树(只含部门,不含用户)。
|
|
||||||
* ponytail: Department entity removed; returns empty array.
|
/** 递归获取子部门列表(含名称) */
|
||||||
*/
|
private async getSubDepts(token: string, deptId: number): Promise<Array<{ dept_id: number; name: string; parent_id: number }>> {
|
||||||
async fetchOrgTree(_rootDeptId = 1): Promise<[]> {
|
await this.rateLimit();
|
||||||
return [];
|
const res = await fetch(
|
||||||
|
`https://oapi.dingtalk.com/topapi/v2/department/listsub?access_token=${token}`,
|
||||||
|
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ dept_id: deptId }) },
|
||||||
|
);
|
||||||
|
const body = await res.json() as DingTalkDeptListResponse;
|
||||||
|
return body.errcode === 0 ? (body.result ?? []) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 获取单个部门详情 */
|
||||||
* 获取钉钉组织部门树(含用户)。
|
private async getDeptInfo(token: string, deptId: number): Promise<{ name: string; parent_id: number } | null> {
|
||||||
* ponytail: Department entity removed; returns empty array.
|
await this.rateLimit();
|
||||||
*/
|
const res = await fetch(
|
||||||
async fetchOrgTreeWithUsers(_rootDeptId = 1): Promise<[]> {
|
`https://oapi.dingtalk.com/topapi/v2/department/get?access_token=${token}`,
|
||||||
return [];
|
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ dept_id: deptId }) },
|
||||||
|
);
|
||||||
|
const body = await res.json() as DingTalkDeptGetResponse;
|
||||||
|
return body.errcode === 0 && body.result ? body.result : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 递归构建部门树节点 */
|
||||||
|
private async buildDeptNode(token: string, deptId: number, name: string, parentId: number): Promise<OrgDeptNode> {
|
||||||
|
const subDepts = await this.getSubDepts(token, deptId);
|
||||||
|
const children = await Promise.all(
|
||||||
|
subDepts.map(sd => this.buildDeptNode(token, sd.dept_id, sd.name, sd.parent_id)),
|
||||||
|
);
|
||||||
|
return { id: deptId, name, parentId, children };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取钉钉组织部门树(只含部门) */
|
||||||
|
async fetchOrgTree(rootDeptId = 1): Promise<OrgDeptNode[]> {
|
||||||
|
if (!this.configured) return [];
|
||||||
|
const token = await this.getAccessToken();
|
||||||
|
const rootInfo = await this.getDeptInfo(token, rootDeptId);
|
||||||
|
if (!rootInfo) return [];
|
||||||
|
const node = await this.buildDeptNode(token, rootDeptId, rootInfo.name, rootInfo.parent_id);
|
||||||
|
return [node];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取钉钉组织部门树(含用户) */
|
||||||
|
async fetchOrgTreeWithUsers(rootDeptId = 1): Promise<OrgDeptNodeWithUsers[]> {
|
||||||
|
if (!this.configured) return [];
|
||||||
|
const token = await this.getAccessToken();
|
||||||
|
const rootInfo = await this.getDeptInfo(token, rootDeptId);
|
||||||
|
if (!rootInfo) return [];
|
||||||
|
|
||||||
|
// 从根部门拉全部用户(翻页),按 dept_id_list 分配到各部门
|
||||||
|
const allUsers = await this.getDeptUsers(token, rootDeptId);
|
||||||
|
const usersByDept = new Map<number, Array<{ userid: string; name: string; mobile: string; deptIds: number[] }>>();
|
||||||
|
for (const u of allUsers) {
|
||||||
|
for (const did of u.dept_id_list) {
|
||||||
|
if (!usersByDept.has(did)) usersByDept.set(did, []);
|
||||||
|
usersByDept.get(did)!.push({ userid: u.userid, name: u.name, mobile: u.mobile, deptIds: u.dept_id_list });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildWithUsers = async (deptId: number, name: string, parentId: number): Promise<OrgDeptNodeWithUsers> => {
|
||||||
|
const subDepts = await this.getSubDepts(token, deptId);
|
||||||
|
const children = await Promise.all(
|
||||||
|
subDepts.map(sd => buildWithUsers(sd.dept_id, sd.name, sd.parent_id)),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
id: deptId, name, parentId, children,
|
||||||
|
users: usersByDept.get(deptId) ?? [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const node = await buildWithUsers(rootDeptId, rootInfo.name, rootInfo.parent_id);
|
||||||
|
return [node];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════
|
// ═══════════════════════════════════════════
|
||||||
|
|||||||
Reference in New Issue
Block a user