forked from wangziqi/gongxue-base
feat: refine deposit room type workflows
This commit is contained in:
@@ -236,8 +236,10 @@ export class AttendanceController {
|
||||
{ header: '时段', key: 'session', width: 15 },
|
||||
{ header: '状态', key: 'status', width: 10 },
|
||||
{ header: '来源', key: 'source', width: 10 },
|
||||
{ header: '打卡设备', key: 'punchDevice', width: 30 },
|
||||
{ header: '打卡时间', key: 'punchTime', width: 20 },
|
||||
{ header: '备注', key: 'remark', width: 30 },
|
||||
{ header: '打卡时间', key: 'createdAt', width: 20 },
|
||||
{ header: '归档时间', key: 'createdAt', width: 20 },
|
||||
];
|
||||
ws.getRow(1).font = { bold: true };
|
||||
ws.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } };
|
||||
@@ -250,6 +252,10 @@ export class AttendanceController {
|
||||
session: record.session || '',
|
||||
status: record.status || '',
|
||||
source: record.source || '',
|
||||
punchDevice: record.punchDeviceName || record.punchDeviceId || '',
|
||||
punchTime: record.punchTime
|
||||
? record.punchTime.toISOString().replace('T', ' ').substring(0, 19)
|
||||
: '',
|
||||
remark: record.remark || '',
|
||||
createdAt: record.createdAt
|
||||
? record.createdAt.toISOString().replace('T', ' ').substring(0, 19)
|
||||
|
||||
@@ -234,6 +234,106 @@ describe('AttendanceService — DingTalk raw query', () => {
|
||||
});
|
||||
|
||||
|
||||
describe('AttendanceService — attendance device display mappings', () => {
|
||||
function createHistoryQueryBuilder(records: AttendanceRecord[]) {
|
||||
return {
|
||||
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
||||
andWhere: jest.fn().mockReturnThis(),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
addOrderBy: jest.fn().mockReturnThis(),
|
||||
skip: jest.fn().mockReturnThis(),
|
||||
take: jest.fn().mockReturnThis(),
|
||||
getManyAndCount: jest.fn().mockResolvedValue([records, records.length]),
|
||||
getMany: jest.fn().mockResolvedValue(records),
|
||||
};
|
||||
}
|
||||
|
||||
function createServiceWithRecords(records: AttendanceRecord[]) {
|
||||
const qb = createHistoryQueryBuilder(records);
|
||||
const attendanceRepo = { createQueryBuilder: jest.fn().mockReturnValue(qb) };
|
||||
const attendanceDeviceRepo = {
|
||||
find: jest.fn().mockImplementation(async (options: { where?: Record<string, unknown> }) => {
|
||||
if (options.where && 'deviceSn' in options.where) {
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
deviceSn: 'ATM-01',
|
||||
deviceName: '东门考勤机',
|
||||
classroomId: 8,
|
||||
classroom: { id: 8, name: '一号教室' },
|
||||
status: 'disabled',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}),
|
||||
};
|
||||
const service = new AttendanceService(
|
||||
attendanceRepo as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
attendanceDeviceRepo as never,
|
||||
{} as never,
|
||||
);
|
||||
return { service, qb, attendanceDeviceRepo };
|
||||
}
|
||||
|
||||
it('maps history list punch device ids to configured attendance device names', async () => {
|
||||
const record = {
|
||||
id: 1,
|
||||
classId: 8,
|
||||
status: 'present',
|
||||
source: 'dingtalk',
|
||||
punchSource: 'ATM',
|
||||
punchDeviceId: 'ATM-01',
|
||||
punchDeviceName: '钉钉原始设备名',
|
||||
} as AttendanceRecord;
|
||||
const { service, attendanceDeviceRepo } = createServiceWithRecords([record]);
|
||||
|
||||
await expect(service.findAll({}, [8])).resolves.toMatchObject({
|
||||
list: [
|
||||
{
|
||||
punchDeviceId: 'ATM-01',
|
||||
punchDeviceName: '东门考勤机 · 一号教室',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
});
|
||||
expect(attendanceDeviceRepo.find).toHaveBeenCalledWith({
|
||||
where: { deviceSn: expect.any(Object) },
|
||||
relations: ['classroom'],
|
||||
});
|
||||
});
|
||||
|
||||
it('maps exported punch device ids to configured attendance device names', async () => {
|
||||
const record = {
|
||||
id: 2,
|
||||
classId: 8,
|
||||
status: 'present',
|
||||
source: 'dingtalk',
|
||||
punchSource: 'ATM',
|
||||
punchDeviceId: 'ATM-01',
|
||||
punchDeviceName: '钉钉原始设备名',
|
||||
} as AttendanceRecord;
|
||||
const { service } = createServiceWithRecords([record]);
|
||||
|
||||
await expect(service.findAllForExport({}, [8])).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
punchDeviceId: 'ATM-01',
|
||||
punchDeviceName: '东门考勤机 · 一号教室',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// ── Session serialization tests ──
|
||||
function deferred<T>(): {
|
||||
promise: Promise<T>;
|
||||
|
||||
@@ -88,7 +88,7 @@ export class AttendanceService {
|
||||
const devicesBySn = new Map<string, AttendanceDevice>();
|
||||
if (sns.length > 0) {
|
||||
const devices = await this.attendanceDeviceRepo.find({
|
||||
where: { deviceSn: In(sns), status: 'active' },
|
||||
where: { deviceSn: In(sns) },
|
||||
relations: ['classroom'],
|
||||
});
|
||||
for (const device of devices) devicesBySn.set(device.deviceSn, device);
|
||||
@@ -907,7 +907,7 @@ export class AttendanceService {
|
||||
qb.skip((page - 1) * pageSize).take(pageSize);
|
||||
|
||||
const [list, total] = await qb.getManyAndCount();
|
||||
return { list, total, page, pageSize };
|
||||
return { list: await this.attachAttendanceDeviceMappings(list), total, page, pageSize };
|
||||
}
|
||||
|
||||
// ── Get distinct classes with attendance records ──
|
||||
@@ -1053,7 +1053,8 @@ export class AttendanceService {
|
||||
|
||||
qb.orderBy('ar.attendanceDate', 'DESC').addOrderBy('ar.createdAt', 'DESC');
|
||||
|
||||
return qb.getMany();
|
||||
const records = await qb.getMany();
|
||||
return this.attachAttendanceDeviceMappings(records);
|
||||
}
|
||||
|
||||
async findAttendanceRecord(id: number) {
|
||||
|
||||
Reference in New Issue
Block a user