feat: 结算时同步钉钉已审批请假并标记为请假
This commit is contained in:
95
apps/server/src/integration/dingtalk.leave.ts
Normal file
95
apps/server/src/integration/dingtalk.leave.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user