// =========================================== // 多班级版班级管理系统 - Go 后端 // // 开发者: Canglan // 联系方式: admin@sea-studio.top // 版权归属: Sea Network Technology Studio // 许可证: Apache License 2.0 // // 版权所有 © Sea Network Technology Studio // =========================================== package repository import ( "fmt" "strings" "time" "gorm.io/gorm" "hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/model" ) // ConductRepo 操行分记录数据访问层 type ConductRepo struct { db *gorm.DB } // NewConductRepo 创建操行分 Repository func NewConductRepo(db *gorm.DB) *ConductRepo { return &ConductRepo{db: db} } // CreateRecord 创建操行分记录 func (r *ConductRepo) CreateRecord(record *model.ConductRecord) (int64, error) { if err := r.db.Create(record).Error; err != nil { return 0, err } return record.RecordID, nil } // GetRecordByID 根据ID获取记录(含学生信息) func (r *ConductRepo) GetRecordByID(recordID int64) (*model.ConductRecord, error) { var record model.ConductRecord if err := r.db.Table("conduct_records cr"). Select("cr.*, s.name as student_name, s.total_points"). Joins("JOIN students s ON cr.student_id = s.student_id"). Where("cr.record_id = ?", recordID). First(&record).Error; err != nil { return nil, err } return &record, nil } // CountStudentRecords 统计学生操行分记录总数 func (r *ConductRepo) CountStudentRecords(studentID int, includeRevoked bool, startDate, endDate string, recorderID int) (int64, error) { var count int64 query := r.db.Model(&model.ConductRecord{}).Where("student_id = ?", studentID) if !includeRevoked { query = query.Where("is_revoked = 0") } if startDate != "" { query = query.Where("DATE(created_at) >= ?", startDate) } if endDate != "" { query = query.Where("DATE(created_at) <= ?", endDate) } if recorderID > 0 { query = query.Where("recorder_id = ?", recorderID) } if err := query.Count(&count).Error; err != nil { return 0, err } return count, nil } // GetStudentRecords 获取学生操行分记录 func (r *ConductRepo) GetStudentRecords(studentID int, limit, offset int, includeRevoked bool, startDate, endDate string, recorderID int) ([]model.ConductRecord, error) { var records []model.ConductRecord query := r.db.Table("conduct_records cr"). Select("cr.*, u.real_name as recorder_real"). Joins("LEFT JOIN users u ON cr.recorder_id = u.user_id"). Where("cr.student_id = ?", studentID) if !includeRevoked { query = query.Where("cr.is_revoked = 0") } if startDate != "" { query = query.Where("DATE(cr.created_at) >= ?", startDate) } if endDate != "" { query = query.Where("DATE(cr.created_at) <= ?", endDate) } if recorderID > 0 { query = query.Where("cr.recorder_id = ?", recorderID) } if err := query.Order("cr.created_at DESC"). Limit(limit). Offset(offset). Find(&records).Error; err != nil { return nil, err } return records, nil } // GetAllRecords 获取所有记录(管理员用,支持多种过滤条件) func (r *ConductRepo) GetAllRecords(classID int, limit, offset int, startDate, endDate string, studentID int, includeRevoked bool, relatedType, reasonPrefix string, isRevoked *int, reasonSearch string) ([]model.ConductRecord, error) { var records []model.ConductRecord query := r.db.Table("conduct_records cr"). Select("cr.*, s.name as student_name, s.student_no, s.class_id, u.real_name as recorder_real, ru.real_name as revoker_name"). Joins("JOIN students s ON cr.student_id = s.student_id"). Joins("JOIN users u ON cr.recorder_id = u.user_id"). Joins("LEFT JOIN users ru ON cr.revoked_by = ru.user_id"). Where("1 = 1") if !includeRevoked { query = query.Where("cr.is_revoked = 0") } if classID > 0 { query = query.Where("s.class_id = ?", classID) } if studentID > 0 { query = query.Where("cr.student_id = ?", studentID) } if startDate != "" { query = query.Where("DATE(cr.created_at) >= ?", startDate) } if endDate != "" { query = query.Where("DATE(cr.created_at) <= ?", endDate) } if relatedType != "" { query = query.Where("cr.related_type = ?", relatedType) } if reasonPrefix != "" { query = query.Where("cr.reason LIKE ?", fmt.Sprintf("%s%%", reasonPrefix)) } if reasonSearch != "" { escaped := strings.NewReplacer("\\", "\\\\", "%", "\\%", "_", "\\_").Replace(reasonSearch) query = query.Where("cr.reason LIKE ?", fmt.Sprintf("%%%s%%", escaped)) } if isRevoked != nil { query = query.Where("cr.is_revoked = ?", *isRevoked) } if err := query.Order("cr.created_at DESC"). Limit(limit). Offset(offset). Find(&records).Error; err != nil { return nil, err } return records, nil } // CountAllRecords 统计记录总数(与 GetAllRecords 使用相同过滤条件) func (r *ConductRepo) CountAllRecords(classID int, startDate, endDate string, studentID int, includeRevoked bool, relatedType, reasonPrefix string, isRevoked *int, reasonSearch string) (int64, error) { var count int64 query := r.db.Table("conduct_records cr"). Joins("JOIN students s ON cr.student_id = s.student_id"). Where("1 = 1") if !includeRevoked { query = query.Where("cr.is_revoked = 0") } if classID > 0 { query = query.Where("s.class_id = ?", classID) } if studentID > 0 { query = query.Where("cr.student_id = ?", studentID) } if startDate != "" { query = query.Where("DATE(cr.created_at) >= ?", startDate) } if endDate != "" { query = query.Where("DATE(cr.created_at) <= ?", endDate) } if relatedType != "" { query = query.Where("cr.related_type = ?", relatedType) } if reasonPrefix != "" { query = query.Where("cr.reason LIKE ?", fmt.Sprintf("%s%%", reasonPrefix)) } if reasonSearch != "" { escaped := strings.NewReplacer("\\", "\\\\", "%", "\\%", "_", "\\_").Replace(reasonSearch) query = query.Where("cr.reason LIKE ?", fmt.Sprintf("%%%s%%", escaped)) } if isRevoked != nil { query = query.Where("cr.is_revoked = ?", *isRevoked) } if err := query.Count(&count).Error; err != nil { return 0, err } return count, nil } // RevokeRecord 撤销单条操行分记录 func (r *ConductRepo) RevokeRecord(recordID int64, revokerID int) error { return r.db.Model(&model.ConductRecord{}). Where("record_id = ? AND is_revoked = 0", recordID). Updates(map[string]interface{}{ "is_revoked": 1, "revoked_by": revokerID, }).Error } // BatchRevokeRecords 批量撤销记录 func (r *ConductRepo) BatchRevokeRecords(recordIDs []int64, revokerID int) (int64, error) { result := r.db.Model(&model.ConductRecord{}). Where("record_id IN ? AND is_revoked = 0", recordIDs). Updates(map[string]interface{}{ "is_revoked": 1, "revoked_by": revokerID, "revoked_at": time.Now(), }) if result.Error != nil { return 0, result.Error } return result.RowsAffected, nil } // BatchRestoreRecords 批量反撤销记录 func (r *ConductRepo) BatchRestoreRecords(recordIDs []int64) (int64, error) { result := r.db.Model(&model.ConductRecord{}). Where("record_id IN ? AND is_revoked = 1", recordIDs). Updates(map[string]interface{}{ "is_revoked": 0, "revoked_by": nil, "revoked_at": nil, }) if result.Error != nil { return 0, result.Error } return result.RowsAffected, nil } // AssociateSemester 将记录关联到学期 func (r *ConductRepo) AssociateSemester(recordID int64, semesterID int) error { return r.db.Model(&model.ConductRecord{}). Where("record_id = ? AND semester_id IS NULL", recordID). Update("semester_id", semesterID).Error } // GetHomeworkRecords 获取学生作业相关的操行分记录 func (r *ConductRepo) GetHomeworkRecords(studentID int) ([]model.ConductRecord, error) { var records []model.ConductRecord if err := r.db.Where("student_id = ? AND related_type = 'homework' AND is_revoked = 0", studentID). Order("created_at DESC"). Find(&records).Error; err != nil { return nil, err } return records, nil } // GetStudentPointsByType 按 related_type 在 SQL 层聚合学生分数(避免全量加载),支持 limit 限制返回数量 func (r *ConductRepo) GetStudentPointsByType(classID int, relatedType string, limit int) ([]struct { StudentID int StudentNo string Name string TotalPoints int }, error) { var results []struct { StudentID int StudentNo string Name string TotalPoints int } err := r.db.Table("conduct_records cr"). Select("cr.student_id, s.student_no, s.name, SUM(cr.points_change) as total_points"). Joins("JOIN students s ON cr.student_id = s.student_id"). Where("s.class_id = ? AND s.status = 1 AND cr.related_type = ? AND cr.is_revoked = 0", classID, relatedType). Group("cr.student_id, s.student_no, s.name"). Order("total_points DESC"). Limit(limit). Find(&results).Error return results, err } // GetStudentTotalPoints 获取学生当前总分 func (r *ConductRepo) GetStudentTotalPoints(studentID int) (int, error) { var student model.Student if err := r.db.Where("student_id = ?", studentID).First(&student).Error; err != nil { return 0, err } return student.TotalPoints, nil }