feat(attendance): add single-record edit and delete APIs
- Add PUT /attendance-records/:id to update status and remark - Add DELETE /attendance-records/:id to remove a record - Enforce campus scope on both operations - Log edit/delete actions via OperationLogsService
This commit is contained in:
@@ -2,6 +2,8 @@ import {
|
|||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
Post,
|
Post,
|
||||||
|
Put,
|
||||||
|
Delete,
|
||||||
Body,
|
Body,
|
||||||
Param,
|
Param,
|
||||||
Query,
|
Query,
|
||||||
@@ -19,6 +21,7 @@ import {
|
|||||||
QueryDingRawDto,
|
QueryDingRawDto,
|
||||||
MatchDingRecordDto,
|
MatchDingRecordDto,
|
||||||
AttendanceReportQueryDto,
|
AttendanceReportQueryDto,
|
||||||
|
UpdateAttendanceRecordDto,
|
||||||
} from './dto/attendance.dto';
|
} from './dto/attendance.dto';
|
||||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||||
import { OperationLogsService } from '../operation-logs/operation-logs.service';
|
import { OperationLogsService } from '../operation-logs/operation-logs.service';
|
||||||
@@ -117,6 +120,53 @@ export class AttendanceController {
|
|||||||
return this.service.findAll(query);
|
return this.service.findAll(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Update a single attendance record ──
|
||||||
|
@Put('attendance-records/:id')
|
||||||
|
@RequirePermission('attendance:edit')
|
||||||
|
async update(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() dto: UpdateAttendanceRecordDto,
|
||||||
|
@Request() req: any,
|
||||||
|
) {
|
||||||
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||||
|
const result = await this.service.update(+id, dto);
|
||||||
|
await this.logService.log({
|
||||||
|
userId: req.user?.id,
|
||||||
|
username: req.user?.username,
|
||||||
|
module: '考勤管理',
|
||||||
|
action: '编辑考勤记录',
|
||||||
|
targetId: +id,
|
||||||
|
targetType: 'attendanceRecord',
|
||||||
|
detail: `状态=${result.status}, 备注=${result.remark || ''}`,
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Delete a single attendance record ──
|
||||||
|
@Delete('attendance-records/:id')
|
||||||
|
@RequirePermission('attendance:edit')
|
||||||
|
async remove(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Request() req: any,
|
||||||
|
) {
|
||||||
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||||
|
const result = await this.service.remove(+id);
|
||||||
|
await this.logService.log({
|
||||||
|
userId: req.user?.id,
|
||||||
|
username: req.user?.username,
|
||||||
|
module: '考勤管理',
|
||||||
|
action: '删除考勤记录',
|
||||||
|
targetId: +id,
|
||||||
|
targetType: 'attendanceRecord',
|
||||||
|
detail: `删除考勤记录 ${id}`,
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Get distinct classes with attendance records ──
|
// ── Get distinct classes with attendance records ──
|
||||||
@Get('attendance-records/classes')
|
@Get('attendance-records/classes')
|
||||||
@RequirePermission('attendance:view')
|
@RequirePermission('attendance:view')
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
QueryDingRawDto,
|
QueryDingRawDto,
|
||||||
MatchDingRecordDto,
|
MatchDingRecordDto,
|
||||||
AttendanceReportQueryDto,
|
AttendanceReportQueryDto,
|
||||||
|
UpdateAttendanceRecordDto,
|
||||||
} from './dto/attendance.dto';
|
} from './dto/attendance.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -314,6 +315,44 @@ export class AttendanceService {
|
|||||||
return qb.getMany();
|
return qb.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Update a single attendance record ──
|
||||||
|
async update(id: number, dto: UpdateAttendanceRecordDto) {
|
||||||
|
const record = await this.attendanceRepo.findOne({ where: { id } });
|
||||||
|
if (!record) {
|
||||||
|
throw new NotFoundException(`AttendanceRecord ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scopeIds = await this.scope.getScopeDepartmentIds();
|
||||||
|
if (scopeIds && !scopeIds.includes(record.departmentId)) {
|
||||||
|
throw new NotFoundException(`AttendanceRecord ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dto.status !== undefined) {
|
||||||
|
record.status = dto.status;
|
||||||
|
}
|
||||||
|
if (dto.remark !== undefined) {
|
||||||
|
record.remark = dto.remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.attendanceRepo.save(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Delete a single attendance record ──
|
||||||
|
async remove(id: number) {
|
||||||
|
const record = await this.attendanceRepo.findOne({ where: { id } });
|
||||||
|
if (!record) {
|
||||||
|
throw new NotFoundException(`AttendanceRecord ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scopeIds = await this.scope.getScopeDepartmentIds();
|
||||||
|
if (scopeIds && !scopeIds.includes(record.departmentId)) {
|
||||||
|
throw new NotFoundException(`AttendanceRecord ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.attendanceRepo.remove(record);
|
||||||
|
return { deleted: true };
|
||||||
|
}
|
||||||
|
|
||||||
// ── Class-based attendance report ──
|
// ── Class-based attendance report ──
|
||||||
async getReport(query: AttendanceReportQueryDto) {
|
async getReport(query: AttendanceReportQueryDto) {
|
||||||
const qb = this.attendanceRepo.createQueryBuilder('ar');
|
const qb = this.attendanceRepo.createQueryBuilder('ar');
|
||||||
|
|||||||
@@ -126,6 +126,17 @@ export class MatchDingRecordDto {
|
|||||||
studentId: number;
|
studentId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdateAttendanceRecordDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@IsIn(['present', 'late', 'absent', 'leave'])
|
||||||
|
status?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
remark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class AttendanceReportQueryDto {
|
export class AttendanceReportQueryDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
|
|||||||
Reference in New Issue
Block a user