feat: show DingTalk punch device details
This commit is contained in:
@@ -93,6 +93,31 @@ describe('AttendanceImportService', () => {
|
||||
expect(entity.userName).toBe('张三');
|
||||
});
|
||||
|
||||
it('stores DingTalk punch source and attendance machine metadata', async () => {
|
||||
const entity = await (service as any).mapToEntity({
|
||||
userId: 'ding-1',
|
||||
userName: '张三',
|
||||
workDate: '2026-07-01',
|
||||
timeResult: 'Normal',
|
||||
locationResult: '',
|
||||
planCheckTime: '',
|
||||
actualCheckTime: '2026-07-01T08:00:00.000Z',
|
||||
checkId: 'check-1',
|
||||
checkType: 'OnDuty',
|
||||
sourceType: 'ATM',
|
||||
deviceName: '东门考勤机',
|
||||
deviceId: 'ATM-01',
|
||||
});
|
||||
|
||||
expect(entity).toEqual(
|
||||
expect.objectContaining({
|
||||
punchSource: 'ATM',
|
||||
punchDeviceName: '东门考勤机',
|
||||
punchDeviceId: 'ATM-01',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('fills the student name from the DingTalk mapping when saving an imported record', async () => {
|
||||
dingTalkService.fetchAttendanceResults.mockResolvedValue([
|
||||
{
|
||||
@@ -138,9 +163,19 @@ describe('AttendanceImportService', () => {
|
||||
actualCheckTime: '2026-07-01T08:00:00.000Z',
|
||||
checkId: 'check-1',
|
||||
checkType: 'OnDuty',
|
||||
sourceType: 'ATM',
|
||||
deviceName: '东门考勤机',
|
||||
deviceId: 'ATM-01',
|
||||
},
|
||||
]);
|
||||
dingRawRepo.find.mockResolvedValue([{ dingId: 'check-1' }]);
|
||||
dingRawRepo.find.mockResolvedValue([{
|
||||
dingId: 'check-1',
|
||||
punchSource: null,
|
||||
punchDeviceName: null,
|
||||
punchDeviceId: null,
|
||||
rawData: '',
|
||||
}]);
|
||||
dingRawRepo.save.mockImplementation(async (entities) => entities);
|
||||
attendanceService.autoMatchDingRecords.mockResolvedValue({ matched: 1, total: 1 });
|
||||
|
||||
const result = await service.importFromDingTalk({
|
||||
@@ -150,10 +185,57 @@ describe('AttendanceImportService', () => {
|
||||
autoMatch: true,
|
||||
});
|
||||
|
||||
expect(dingRawRepo.save).toHaveBeenCalledWith(
|
||||
[expect.objectContaining({
|
||||
dingId: 'check-1',
|
||||
punchSource: 'ATM',
|
||||
punchDeviceName: '东门考勤机',
|
||||
punchDeviceId: 'ATM-01',
|
||||
})],
|
||||
{ chunk: 50 },
|
||||
);
|
||||
expect(attendanceService.autoMatchDingRecords).toHaveBeenCalled();
|
||||
expect(result.matched).toBe(1);
|
||||
});
|
||||
|
||||
it('preserves existing device metadata when a duplicate response omits it', async () => {
|
||||
const existing = {
|
||||
dingId: 'check-keep-device',
|
||||
punchSource: 'ATM',
|
||||
punchDeviceName: '东门考勤机',
|
||||
punchDeviceId: 'ATM-01',
|
||||
rawData: '{}',
|
||||
};
|
||||
dingTalkService.fetchAttendanceResults.mockResolvedValue([{
|
||||
userId: 'ding-1',
|
||||
userName: '张三',
|
||||
workDate: '2026-07-01',
|
||||
timeResult: 'Normal',
|
||||
locationResult: '',
|
||||
planCheckTime: '',
|
||||
actualCheckTime: '2026-07-01T08:00:00.000Z',
|
||||
checkId: 'check-keep-device',
|
||||
checkType: 'OnDuty',
|
||||
sourceType: '',
|
||||
}]);
|
||||
dingRawRepo.find.mockResolvedValue([existing]);
|
||||
attendanceService.autoMatchDingRecords.mockResolvedValue({ matched: 0, total: 1 });
|
||||
|
||||
await service.importFromDingTalk({
|
||||
startDate: '2026-07-01',
|
||||
endDate: '2026-07-01',
|
||||
userIds: ['ding-1'],
|
||||
autoMatch: true,
|
||||
});
|
||||
|
||||
expect(existing).toEqual(expect.objectContaining({
|
||||
punchSource: 'ATM',
|
||||
punchDeviceName: '东门考勤机',
|
||||
punchDeviceId: 'ATM-01',
|
||||
}));
|
||||
expect(dingRawRepo.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('scopes SSE progress events to the importing user', async () => {
|
||||
dingTalkService.fetchAttendanceResults.mockResolvedValue([
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user