From 782b43ef0aeb6382d759d706f897a27beec91842 Mon Sep 17 00:00:00 2001 From: wangziqi Date: Sat, 8 Aug 2026 17:56:00 +0800 Subject: [PATCH] =?UTF-8?q?fix(attendance):=20=E9=BB=98=E8=AE=A4=E5=91=A8?= =?UTF-8?q?=E8=8C=83=E5=9B=B4=E6=94=B9=E4=B8=BA=E4=B8=AD=E5=9B=BD=E6=97=A5?= =?UTF-8?q?=E6=9C=9F=E5=91=A8=E4=B8=80=E8=B5=B7=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit generateFromSchedules / getCalendar 原来用本地周一零点转 UTC 日期, 在上海时区会得到前一天的日期,导致默认范围变成周日~周日(8 天)。 改为 dayjs().utcOffset(8) 按中国日期计算,与 getTodayDateOnly 语义一致。 新增测试锁定:默认周 = 本周一~本周日,周日晚上仍归本周,显式传参原样透传。 --- .../attendance-calendar.service.spec.ts | 44 +++++++++++++ .../attendance/attendance-calendar.service.ts | 12 ++-- .../attendance-generation.service.spec.ts | 65 +++++++++++++++++++ .../attendance-generation.service.ts | 19 ++---- 4 files changed, 121 insertions(+), 19 deletions(-) create mode 100644 apps/server/src/attendance/attendance-calendar.service.spec.ts create mode 100644 apps/server/src/attendance/attendance-generation.service.spec.ts diff --git a/apps/server/src/attendance/attendance-calendar.service.spec.ts b/apps/server/src/attendance/attendance-calendar.service.spec.ts new file mode 100644 index 0000000..c60cd86 --- /dev/null +++ b/apps/server/src/attendance/attendance-calendar.service.spec.ts @@ -0,0 +1,44 @@ +import { AttendanceCalendarService } from './attendance-calendar.service'; + +describe('AttendanceCalendarService — 默认周起点', () => { + function createService() { + return new AttendanceCalendarService({} as never, {} as never); + } + + afterEach(() => jest.useRealTimers()); + + it('未传 weekStart 时默认本周一(中国日期)', async () => { + jest.useFakeTimers().setSystemTime(new Date('2026-08-05T10:00:00+08:00')); // 周三 + const service = createService(); + const spy = jest + .spyOn(service as never, 'buildCalendar' as never) + .mockResolvedValue([] as never); + + await service.getCalendar({ classId: 1 } as never); + + expect(spy).toHaveBeenCalledWith(1, '2026-08-03'); + }); + + it('周日晚间仍归入本周一', async () => { + jest.useFakeTimers().setSystemTime(new Date('2026-08-09T22:00:00+08:00')); // 周日晚上 + const service = createService(); + const spy = jest + .spyOn(service as never, 'buildCalendar' as never) + .mockResolvedValue([] as never); + + await service.getCalendar({ classId: 1 } as never); + + expect(spy).toHaveBeenCalledWith(1, '2026-08-03'); + }); + + it('传入 weekStart 时原样透传', async () => { + const service = createService(); + const spy = jest + .spyOn(service as never, 'buildCalendar' as never) + .mockResolvedValue([] as never); + + await service.getCalendar({ classId: 1, weekStart: '2026-08-10' } as never); + + expect(spy).toHaveBeenCalledWith(1, '2026-08-10'); + }); +}); diff --git a/apps/server/src/attendance/attendance-calendar.service.ts b/apps/server/src/attendance/attendance-calendar.service.ts index a359b84..3e73a5a 100644 --- a/apps/server/src/attendance/attendance-calendar.service.ts +++ b/apps/server/src/attendance/attendance-calendar.service.ts @@ -18,13 +18,11 @@ export class AttendanceCalendarService { const { classId, weekStart } = query; if (!weekStart) { - // Default to the Monday of the current week - const now = new Date(); - const day = now.getDay(); - const diff = day === 0 ? -6 : 1 - day; // Monday offset - const monday = new Date(now); - monday.setDate(now.getDate() + diff); - const mondayStr = dayjs(monday).utc().format('YYYY-MM-DD'); + // Default to the Monday of the current week (China calendar) + const now = dayjs().utcOffset(8); + const day = now.day(); + const monday = now.subtract(day === 0 ? 6 : day - 1, 'day'); + const mondayStr = monday.format('YYYY-MM-DD'); return this.buildCalendar(classId, mondayStr); } diff --git a/apps/server/src/attendance/attendance-generation.service.spec.ts b/apps/server/src/attendance/attendance-generation.service.spec.ts new file mode 100644 index 0000000..ac08d13 --- /dev/null +++ b/apps/server/src/attendance/attendance-generation.service.spec.ts @@ -0,0 +1,65 @@ +import { AttendanceGenerationService } from './attendance-generation.service'; + +describe('AttendanceGenerationService — 默认周范围', () => { + function createService() { + return new AttendanceGenerationService( + {} as never, + {} as never, + {} as never, + {} as never, + {} as never, + {} as never, + {} as never, + ); + } + + afterEach(() => jest.useRealTimers()); + + it('未传 startDate/endDate 时默认生成本周一~本周日(中国日期)', async () => { + jest.useFakeTimers().setSystemTime(new Date('2026-08-05T10:00:00+08:00')); // 周三 + const service = createService(); + const spy = jest + .spyOn(service as never, 'generateAttendanceFromSchedules' as never) + .mockResolvedValue({ count: 0, records: [] } as never); + + await service.generateFromSchedules({ classId: 1 } as never); + + expect(spy).toHaveBeenCalledWith({ + classId: 1, + dateFrom: '2026-08-03', + dateTo: '2026-08-09', + }); + }); + + it('周日晚间仍归入本周(周一起点)', async () => { + jest.useFakeTimers().setSystemTime(new Date('2026-08-09T22:00:00+08:00')); // 周日晚上 + const service = createService(); + const spy = jest + .spyOn(service as never, 'generateAttendanceFromSchedules' as never) + .mockResolvedValue({ count: 0, records: [] } as never); + + await service.generateFromSchedules({ classId: 1 } as never); + + expect(spy).toHaveBeenCalledWith({ + classId: 1, + dateFrom: '2026-08-03', + dateTo: '2026-08-09', + }); + }); + + it('传入 startDate/endDate 时原样透传', async () => { + jest.useFakeTimers().setSystemTime(new Date('2026-08-05T10:00:00+08:00')); + const service = createService(); + const spy = jest + .spyOn(service as never, 'generateAttendanceFromSchedules' as never) + .mockResolvedValue({ count: 0, records: [] } as never); + + await service.generateFromSchedules({ classId: 1, startDate: '2026-08-01', endDate: '2026-08-31' } as never); + + expect(spy).toHaveBeenCalledWith({ + classId: 1, + dateFrom: '2026-08-01', + dateTo: '2026-08-31', + }); + }); +}); diff --git a/apps/server/src/attendance/attendance-generation.service.ts b/apps/server/src/attendance/attendance-generation.service.ts index cbfc285..b1dd7cf 100644 --- a/apps/server/src/attendance/attendance-generation.service.ts +++ b/apps/server/src/attendance/attendance-generation.service.ts @@ -136,19 +136,14 @@ export class AttendanceGenerationService { ): Promise<{ count: number; records: AttendanceRecord[] }> { const { classId, startDate, endDate } = dto; - // Default to current week (Monday–Sunday) - const now = new Date(); - const dayOfWeek = now.getDay(); - const mondayOffset = dayOfWeek === 0 ? -6 : 1 - dayOfWeek; - const monday = new Date(now); - monday.setDate(now.getDate() + mondayOffset); - monday.setHours(0, 0, 0, 0); - const sunday = new Date(monday); - sunday.setDate(monday.getDate() + 6); - sunday.setHours(23, 59, 59, 999); + // Default to current week (Monday–Sunday), China calendar + const now = dayjs().utcOffset(8); + const day = now.day(); + const monday = now.subtract(day === 0 ? 6 : day - 1, 'day').startOf('day'); + const sunday = monday.add(6, 'day').endOf('day'); - const dateFrom = startDate ?? dayjs(monday).utc().format('YYYY-MM-DD'); - const dateTo = endDate ?? dayjs(sunday).utc().format('YYYY-MM-DD'); + const dateFrom = startDate ?? monday.format('YYYY-MM-DD'); + const dateTo = endDate ?? sunday.format('YYYY-MM-DD'); return this.generateAttendanceFromSchedules({ classId,