Files
gongxue-base/apps/server/src/integration/dingtalk.leave.ts

96 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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<DingTalkLeaveResult[]> {
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;
}
}