// =========================================== // 多班级版班级管理系统 - Go 后端 // // 开发者: Canglan // 联系方式: admin@sea-studio.top // 版权归属: Sea Network Technology Studio // 许可证: Apache License 2.0 // // 版权所有 © Sea Network Technology Studio // =========================================== package repository import ( "time" "gorm.io/gorm" "hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/model" ) // AttendanceRepo 考勤数据访问层 type AttendanceRepo struct { db *gorm.DB } // NewAttendanceRepo 创建考勤 Repository func NewAttendanceRepo(db *gorm.DB) *AttendanceRepo { return &AttendanceRepo{db: db} } // GetStudentRecords 获取学生考勤记录 func (r *AttendanceRepo) GetStudentRecords(studentID int, month string) ([]model.AttendanceRecord, error) { var records []model.AttendanceRecord query := r.db.Where("student_id = ?", studentID) if month != "" { query = query.Where("DATE_FORMAT(date, '%Y-%m') = ?", month) } if err := query.Order("date DESC").Find(&records).Error; err != nil { return nil, err } return records, nil } // GetClassRecords 获取班级考勤记录(支持多种过滤条件) func (r *AttendanceRepo) GetClassRecords(classID int, date string, studentID int, slot string) ([]model.AttendanceRecord, error) { var records []model.AttendanceRecord query := r.db.Table("attendance_records ar"). Select("ar.*, s.name as student_name, s.student_no"). Joins("JOIN students s ON ar.student_id = s.student_id"). Where("1 = 1") if classID > 0 { query = query.Where("s.class_id = ?", classID) } if date != "" { query = query.Where("ar.date = ?", date) } if studentID > 0 { query = query.Where("ar.student_id = ?", studentID) } if slot != "" { query = query.Where("ar.slot = ?", slot) } if err := query.Order("ar.date DESC, s.student_no").Find(&records).Error; err != nil { return nil, err } return records, nil } // CreateRecordResult 创建或更新考勤记录的结果 type CreateRecordResult struct { AttendanceID int IsUpdate bool OldDeductionApplied int8 OldDeductionRecordID *int64 } // CreateRecord 创建或更新考勤记录(存在则更新),使用事务+行锁防止并发竞态 func (r *AttendanceRepo) CreateRecord(record *model.AttendanceRecord) (*CreateRecordResult, error) { var result CreateRecordResult err := r.db.Transaction(func(tx *gorm.DB) error { // 使用 SELECT ... FOR UPDATE 锁定记录,防止并发请求同时判定为"不存在" var existing model.AttendanceRecord findErr := tx.Set("gorm:query_option", "FOR UPDATE"). Where("student_id = ? AND date = ? AND slot = ?", record.StudentID, record.Date, record.Slot). First(&existing).Error if findErr == nil { // 更新已有记录 if updateErr := tx.Model(&existing).Updates(map[string]interface{}{ "status": record.Status, "reason": record.Reason, "recorder_id": record.RecorderID, }).Error; updateErr != nil { return updateErr } result = CreateRecordResult{ AttendanceID: existing.AttendanceID, IsUpdate: true, OldDeductionApplied: existing.DeductionApplied, OldDeductionRecordID: existing.DeductionRecordID, } return nil } if findErr != gorm.ErrRecordNotFound { return findErr } // 插入新记录 if createErr := tx.Create(record).Error; createErr != nil { return createErr } result = CreateRecordResult{ AttendanceID: record.AttendanceID, IsUpdate: false, } return nil }) if err != nil { return nil, err } return &result, nil } // GetAttendanceStatsBySemester 批量查询学期内所有学生的考勤统计 func (r *AttendanceRepo) GetAttendanceStatsBySemester(semesterID int, startDate, endDate string) ([]struct { StudentID int Status string Count int64 }, error) { var stats []struct { StudentID int Status string Count int64 } err := r.db.Model(&model.AttendanceRecord{}). Select("student_id, status, COUNT(*) as count"). Where("semester_id = ? OR (semester_id IS NULL AND `date` BETWEEN ? AND ?)", semesterID, startDate, endDate). Group("student_id, status"). Find(&stats).Error if err != nil { return nil, err } return stats, nil } // GetAttendanceStatsByDateRange 通过日期范围查询学生考勤统计 func (r *AttendanceRepo) GetAttendanceStatsByDateRange(startDate, endDate time.Time, classID int) ([]struct { StudentID int Status string Count int64 }, error) { var stats []struct { StudentID int Status string Count int64 } query := r.db.Model(&model.AttendanceRecord{}). Select("student_id, status, COUNT(*) as count"). Where("date BETWEEN ? AND ?", startDate, endDate) if classID > 0 { query = query.Where("student_id IN (SELECT student_id FROM students WHERE class_id = ?)", classID) } err := query.Group("student_id, status").Find(&stats).Error if err != nil { return nil, err } return stats, nil } // AssociateSemester 将考勤记录关联到学期 func (r *AttendanceRepo) AssociateSemester(attendanceID int, semesterID int) error { return r.db.Model(&model.AttendanceRecord{}). Where("attendance_id = ? AND semester_id IS NULL", attendanceID). Update("semester_id", semesterID).Error }