// aislop-ignore-file: duplicate-block -- 钉钉 API 调用块结构相似(端点/参数不同) import type { DingTalkLeaveResult, DingTalkServiceContext, } from './dingtalk.types'; interface DingTalkGetUpdateDataResponse { errcode: number; errmsg: string; result?: { userid?: string; work_date?: string; approve_list?: Array<{ procInst_id?: string; tag_name?: string; sub_type?: string; biz_type?: number; begin_time?: string; end_time?: string; gmt_finished?: string; duration?: string; duration_unit?: string; }>; }; } /** * 钉钉请假数据客户端。 * * 使用「获取用户考勤数据」接口(topapi/attendance/getupdatedata),按用户+工作日 * 返回当天打卡结果与审批单列表;这里只取 biz_type=3(请假)且已审批完成 * (gmt_finished 非空)的记录,保证结算时不会把审批中的请假误判为请假。 */ export class DingTalkLeaveClient { constructor(private readonly context: DingTalkServiceContext) {} async fetchDailyLeaveStatus( userId: string, workDate: string, ): Promise { if (!(await this.context.isConfigured())) throw new Error('DingTalk not configured'); if (!userId) throw new Error('钉钉请假查询 userId 不能为空'); const token = await this.context.getAccessToken(); await this.context.rateLimit(); const res = await fetch( `https://oapi.dingtalk.com/topapi/attendance/getupdatedata?access_token=${token}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userid: userId, work_date: workDate.includes(' ') ? workDate : `${workDate} 00:00:00`, }), }, ); const data = (await res.json()) as DingTalkGetUpdateDataResponse; if (data.errcode !== 0) { throw new Error(`钉钉请假数据获取失败: ${data.errmsg}`); } const result = data.result; if (!result) return []; const approveList = result.approve_list ?? []; return approveList .filter( (approval) => approval.biz_type === 3 && approval.gmt_finished && approval.procInst_id && approval.begin_time && approval.end_time, ) .map((approval) => ({ userId: result.userid ?? userId, workDate, procInstId: approval.procInst_id!, tagName: approval.tag_name ?? '请假', leaveType: approval.sub_type ?? '', beginTime: this.parseDingDate(approval.begin_time!), endTime: this.parseDingDate(approval.end_time!), approvedAt: this.parseDingDate(approval.gmt_finished!), duration: approval.duration ?? '', durationUnit: approval.duration_unit ?? '', })); } /** 钉钉返回的日期可能是 '2026-08-01' 或 '2026-08-01 09:00:00',统一按东八区解析。 */ private parseDingDate(value: string): Date { const normalized = value.includes(' ') ? value.replace(' ', 'T') : `${value}T00:00:00`; const date = new Date(`${normalized}+08:00`); return Number.isNaN(date.getTime()) ? new Date(normalized) : date; } }