// =========================================== // 多班级版班级管理系统 - Go 后端 // // 开发者: Canglan // 联系方式: admin@sea-studio.top // 版权归属: Sea Network Technology Studio // 许可证: Apache License 2.0 // // 版权所有 © Sea Network Technology Studio // =========================================== package repository import ( "fmt" "time" "gorm.io/gorm" "hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/model" ) // SemesterRepo 学期数据访问层 type SemesterRepo struct { db *gorm.DB } // NewSemesterRepo 创建学期 Repository func NewSemesterRepo(db *gorm.DB) *SemesterRepo { return &SemesterRepo{db: db} } // GetDB 获取底层数据库连接(用于事务操作) func (r *SemesterRepo) GetDB() *gorm.DB { return r.db } // Create 创建学期 func (r *SemesterRepo) Create(semester *model.Semester) (int, error) { if err := r.db.Create(semester).Error; err != nil { return 0, err } return semester.SemesterID, nil } // GetByID 根据ID获取学期信息 func (r *SemesterRepo) GetByID(semesterID int) (*model.Semester, error) { var semester model.Semester if err := r.db.Where("semester_id = ?", semesterID).First(&semester).Error; err != nil { return nil, err } return &semester, nil } // GetAll 获取所有学期列表 func (r *SemesterRepo) GetAll() ([]model.Semester, error) { var semesters []model.Semester if err := r.db.Order("created_at DESC").Find(&semesters).Error; err != nil { return nil, err } return semesters, nil } // GetActive 获取当前活跃学期(优先 is_active 标记,降级为日期范围匹配) func (r *SemesterRepo) GetActive() (*model.Semester, error) { var semester model.Semester // 第一优先级:is_active 标记 if err := r.db.Where("is_active = 1 AND is_archived = 0"). Limit(1).First(&semester).Error; err == nil { return &semester, nil } // 第二优先级:日期范围匹配 today := time.Now().Format("2006-01-02") if err := r.db.Where("is_archived = 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)", today, today). Limit(1).First(&semester).Error; err != nil { return nil, err } return &semester, nil } // DeactivateAll 将所有学期设为非活跃 func (r *SemesterRepo) DeactivateAll() error { return r.db.Model(&model.Semester{}). Where("is_active = 1"). Update("is_active", 0).Error } // Activate 设为当前活跃学期 func (r *SemesterRepo) Activate(semesterID int) error { return r.db.Model(&model.Semester{}). Where("semester_id = ? AND is_archived = 0", semesterID). Update("is_active", 1).Error } // Archive 归档学期 func (r *SemesterRepo) Archive(semesterID int) error { return r.db.Model(&model.Semester{}). Where("semester_id = ? AND is_archived = 0", semesterID). Updates(map[string]interface{}{ "is_archived": 1, "is_active": 0, }).Error } // Update 编辑学期信息(仅未归档) func (r *SemesterRepo) Update(semesterID int, updates map[string]interface{}) error { if len(updates) == 0 { return nil } return r.db.Model(&model.Semester{}). Where("semester_id = ? AND is_archived = 0", semesterID). Updates(updates).Error } // Delete 删除学期 func (r *SemesterRepo) Delete(semesterID int) error { return r.db.Where("semester_id = ?", semesterID).Delete(&model.Semester{}).Error } // CountArchives 统计学期归档数据数量 func (r *SemesterRepo) CountArchives(semesterID int) (int64, error) { var count int64 if err := r.db.Model(&model.SemesterArchive{}). Where("semester_id = ?", semesterID). Count(&count).Error; err != nil { return 0, err } return count, nil } // CountRecordsBySemester 统计学期关联的记录数 func (r *SemesterRepo) CountRecordsBySemester(semesterID int) (conductCount, attendanceCount int64, err error) { if err = r.db.Model(&model.ConductRecord{}). Where("semester_id = ?", semesterID). Count(&conductCount).Error; err != nil { return 0, 0, err } if err = r.db.Model(&model.AttendanceRecord{}). Where("semester_id = ?", semesterID). Count(&attendanceCount).Error; err != nil { return 0, 0, err } return conductCount, attendanceCount, nil } // AssociateRecordsByDateRange 按日期范围关联记录到学期 func (r *SemesterRepo) AssociateRecordsByDateRange(semesterID int, startDate, endDate string) (conductCount, attendanceCount int64, err error) { if startDate == "" || endDate == "" { return 0, 0, fmt.Errorf("日期范围不能为空") } // 关联操行分记录 result := r.db.Model(&model.ConductRecord{}). Where("semester_id IS NULL AND created_at BETWEEN ? AND CONCAT(?, ' 23:59:59')", startDate, endDate). Update("semester_id", semesterID) if result.Error != nil { return 0, 0, result.Error } conductCount = result.RowsAffected // 关联考勤记录 result = r.db.Model(&model.AttendanceRecord{}). Where("semester_id IS NULL AND `date` BETWEEN ? AND ?", startDate, endDate). Update("semester_id", semesterID) if result.Error != nil { return conductCount, 0, result.Error } attendanceCount = result.RowsAffected return conductCount, attendanceCount, nil } // GetConductRecordSemesterID 获取操行分记录所属的学期ID func (r *SemesterRepo) GetConductRecordSemesterID(recordID int64) (*int, error) { var record model.ConductRecord if err := r.db.Where("record_id = ?", recordID).First(&record).Error; err != nil { return nil, err } return record.SemesterID, nil } // ========== 学期归档操作 ========== // BatchCreateArchives 批量创建归档快照 func (r *SemesterRepo) BatchCreateArchives(archives []model.SemesterArchive) error { if len(archives) == 0 { return nil } return r.db.Create(&archives).Error } // DeleteArchivesBySemester 删除指定学期的所有归档数据 func (r *SemesterRepo) DeleteArchivesBySemester(semesterID int) error { return r.db.Where("semester_id = ?", semesterID).Delete(&model.SemesterArchive{}).Error } // GetArchivesBySemester 获取学期的归档数据 func (r *SemesterRepo) GetArchivesBySemester(semesterID int, classID int, page, pageSize int) ([]model.SemesterArchive, int64, error) { var archives []model.SemesterArchive var total int64 query := r.db.Model(&model.SemesterArchive{}).Where("semester_id = ?", semesterID) if classID > 0 { query = query.Where("class_id = ?", classID) } if err := query.Count(&total).Error; err != nil { return nil, 0, err } offset := (page - 1) * pageSize if err := query.Order("rank_position ASC"). Limit(pageSize). Offset(offset). Find(&archives).Error; err != nil { return nil, 0, err } return archives, total, nil } // GetArchivesByStudent 获取学生在所有已归档学期的数据 func (r *SemesterRepo) GetArchivesByStudent(studentID int) ([]model.SemesterArchive, error) { var archives []model.SemesterArchive if err := r.db.Table("semester_archives sa"). Select("sa.archive_id, sa.semester_id, sa.student_id, sa.student_no, "+ "sa.student_name, sa.final_points, sa.rank_position, "+ "sa.total_students, sa.attendance_present, sa.attendance_absent, "+ "sa.attendance_late, sa.attendance_leave, "+ "sa.homework_submitted, sa.homework_not_submitted, sa.homework_late, "+ "sa.archived_at, s.semester_name, s.start_date, s.end_date"). Joins("JOIN semesters s ON sa.semester_id = s.semester_id"). Where("sa.student_id = ?", studentID). Order("sa.archived_at DESC"). Find(&archives).Error; err != nil { return nil, err } return archives, nil } // ========== 周期归档操作 ========== // GetPeriodArchives 获取周期归档列表 func (r *SemesterRepo) GetPeriodArchives(classID int, periodType string, page, pageSize int) ([]model.PeriodArchive, int64, error) { var archives []model.PeriodArchive var total int64 query := r.db.Model(&model.PeriodArchive{}). Where("class_id = ? AND period_type = ?", classID, periodType) if err := query.Count(&total).Error; err != nil { return nil, 0, err } offset := (page - 1) * pageSize if err := query.Order("archived_at DESC, period_label DESC, rank_position ASC"). Limit(pageSize). Offset(offset). Find(&archives).Error; err != nil { return nil, 0, err } return archives, total, nil } // GetPeriodArchiveLabels 获取班级的所有周期归档标签(按时间倒序去重) func (r *SemesterRepo) GetPeriodArchiveLabels(classID int, periodType string) ([]string, error) { var labels []string if err := r.db.Model(&model.PeriodArchive{}). Where("class_id = ? AND period_type = ?", classID, periodType). Distinct("period_label"). Order("period_label DESC"). Pluck("period_label", &labels).Error; err != nil { return nil, err } return labels, nil } // GetLatestPeriodArchiveLabel 获取指定班级最近一次周期归档的标签 func (r *SemesterRepo) GetLatestPeriodArchiveLabel(classID int, periodType string) (string, error) { var archive model.PeriodArchive if err := r.db.Where("class_id = ? AND period_type = ?", classID, periodType). Order("archived_at DESC"). Limit(1). First(&archive).Error; err != nil { return "", err } return archive.PeriodLabel, nil }