fix: UI improvements and DingTalk attendance refresh for completed sessions
All checks were successful
CI / check (pull_request) Successful in 1m33s

- DatePicker editors open immediately on double-click
- Student archive drawer hides empty studentNo parentheses
- Student add/edit form uses 2-column grid layout
- Table horizontal scrollbars show only on hover/focus
- Remove fake 近7日趋势 stats from attendance student detail
- Allow DingTalk refresh to update completed attendance sessions
  (late-arriving punches can now change absent → present)
- Switch dev command from concurrently to turbo run dev
- Remove concurrently/wait-on dependencies
This commit is contained in:
2026-07-22 17:50:59 +08:00
parent d33c37b4b0
commit 90cc321221
14 changed files with 581 additions and 546 deletions

View File

@@ -391,23 +391,21 @@ export class AttendanceService {
});
if (existing) {
if (existing.status === 'completed') {
const records = await this.attendanceRepo.find({
where: { attendanceSessionId: existing.id },
relations: ['student'],
order: { studentId: 'ASC' },
});
return { schedule, session: existing, records: await this.attachAttendanceDeviceMappings(records, schedule.classId) };
}
if (existing.status !== 'in_progress' && !(finalize && existing.status === 'settling')) {
if (
existing.status !== 'in_progress' &&
existing.status !== 'completed' &&
!(finalize && existing.status === 'settling')
) {
throw new BadRequestException('课程考勤正在结算');
}
// Refresh in_progress session from latest DingTalk data
// Refresh latest DingTalk data even after automatic settlement; late-arriving punches
// may legitimately change a DingTalk-generated absence to present.
return this.dataSource.transaction(async (manager) => {
const sessionRepo = manager.getRepository(AttendanceSession);
const recordRepo = manager.getRepository(AttendanceRecord);
const rawByStudent = await this.fetchDingTalkRawByStudent(schedule.classId!, schedule, lessonDate);
const effectiveFinalize = finalize || existing.status === 'completed';
const existingRecords = await recordRepo.find({
where: { attendanceSessionId: existing.id },
order: { studentId: 'ASC' },
@@ -433,7 +431,7 @@ export class AttendanceService {
schedule,
lessonDate,
);
record.status = this.mapDingTalkStatus(raw, finalize);
record.status = this.mapDingTalkStatus(raw, effectiveFinalize);
Object.assign(record, this.getLessonPunchMetadata(
raw,
lessonDate,
@@ -441,7 +439,7 @@ export class AttendanceService {
));
record.remark = raw.some((item) => item.checkInTime || item.checkOutTime)
? null
: finalize
: effectiveFinalize
? '课程截止仍未打卡'
: '未获取到钉钉打卡结果';
return record;
@@ -462,7 +460,7 @@ export class AttendanceService {
attendanceSessionId: existing.id,
attendanceDate: lessonDate,
session: lessonSessionKey,
status: this.mapDingTalkStatus(raw, finalize),
status: this.mapDingTalkStatus(raw, effectiveFinalize),
source: 'dingtalk',
...this.getLessonPunchMetadata(
raw,
@@ -471,7 +469,7 @@ export class AttendanceService {
),
remark: raw.some((item) => item.checkInTime || item.checkOutTime)
? undefined
: finalize
: effectiveFinalize
? '课程截止仍未打卡'
: '未获取到钉钉打卡结果',
}),