All checks were successful
Deploy on Master Change / deploy (push) Successful in 53s
259 lines
7.5 KiB
Go
259 lines
7.5 KiB
Go
package handlers
|
||
|
||
import (
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
internalDb "server/internal/db"
|
||
"server/internal/models"
|
||
|
||
"github.com/gofiber/fiber/v2"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
type studentShiftItem struct {
|
||
Period int `json:"period"`
|
||
CourseName string `json:"courseName"`
|
||
StartTime string `json:"startTime"`
|
||
EndTime string `json:"endTime"`
|
||
IsActiveNow bool `json:"isActiveNow"`
|
||
OnlineSeconds int `json:"onlineSeconds"`
|
||
OfflineSeconds int `json:"offlineSeconds"`
|
||
AttendanceStatus int `json:"attendanceStatus"`
|
||
AttendanceLabel string `json:"attendanceLabel"`
|
||
}
|
||
|
||
// GET /api/student/status — trạng thái giám sát, lớp/ca hiện tại, tích lũy & điểm danh hôm nay
|
||
func GetStudentStatusHandler(db *gorm.DB) fiber.Handler {
|
||
return func(c *fiber.Ctx) error {
|
||
studentID, err := strconv.ParseInt(c.Query("studentRkId", "0"), 10, 64)
|
||
if err != nil || studentID <= 0 {
|
||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "studentRkId is required"})
|
||
}
|
||
|
||
now := time.Now()
|
||
today := now.Format("2006-01-02")
|
||
dayIdx := weekdayIndex(now)
|
||
currMin := now.Hour()*60 + now.Minute()
|
||
|
||
classID, activePeriod := internalDb.FindActiveClassAndPeriodForStudent(db, studentID)
|
||
if classID <= 0 {
|
||
var link models.ClassStudent
|
||
if err := db.Where("student_rk_id = ?", studentID).First(&link).Error; err == nil {
|
||
classID = link.ClassRkID
|
||
}
|
||
}
|
||
|
||
className, classCode := "", ""
|
||
if classID > 0 {
|
||
var cl models.Class
|
||
if err := db.Where("rk_id = ?", classID).First(&cl).Error; err == nil {
|
||
className = cl.Name
|
||
classCode = cl.ClassCode
|
||
}
|
||
}
|
||
|
||
var todaySchedules []models.ClassSchedule
|
||
if classID > 0 {
|
||
_ = db.Where("class_rk_id = ? AND day_of_week = ? AND is_active = ?", classID, dayIdx, true).
|
||
Order("period asc, start_time asc").Find(&todaySchedules).Error
|
||
}
|
||
|
||
inScheduleNow := false
|
||
currentPeriod := 0
|
||
currentCourse, currentStart, currentEnd := "", "", ""
|
||
for _, s := range todaySchedules {
|
||
startMin := parseTimeMinutes(s.StartTime)
|
||
endMin := parseTimeMinutes(s.EndTime)
|
||
if currMin >= startMin && currMin <= endMin {
|
||
inScheduleNow = true
|
||
currentPeriod = s.Period
|
||
if currentPeriod < 1 {
|
||
currentPeriod = 1
|
||
}
|
||
currentCourse = s.CourseName
|
||
currentStart = s.StartTime
|
||
currentEnd = s.EndTime
|
||
break
|
||
}
|
||
}
|
||
if !inScheduleNow && activePeriod > 0 {
|
||
currentPeriod = activePeriod
|
||
}
|
||
|
||
hasApps := false
|
||
keywords := ""
|
||
if classID > 0 {
|
||
var appConfig models.ClassAllowedApp
|
||
if err := db.Where("class_rk_id = ?", classID).First(&appConfig).Error; err == nil {
|
||
keywords = strings.TrimSpace(appConfig.Keywords)
|
||
hasApps = keywords != ""
|
||
if hasApps {
|
||
keywords = keywords + "," + systemAllowedApps
|
||
}
|
||
}
|
||
}
|
||
|
||
monitorMode := "outside_schedule"
|
||
monitorLabel := "Ngoài giờ học"
|
||
if classID <= 0 {
|
||
monitorMode = "not_configured"
|
||
monitorLabel = "Chưa xác định được lớp học"
|
||
} else if !hasApps {
|
||
monitorMode = "not_configured"
|
||
monitorLabel = "Lớp chưa cấu hình ứng dụng được phép"
|
||
} else if inScheduleNow {
|
||
monitorMode = "learning"
|
||
if currentCourse != "" {
|
||
monitorLabel = fmt.Sprintf("Ca %d — %s (%s–%s)", currentPeriod, currentCourse, currentStart, currentEnd)
|
||
} else {
|
||
monitorLabel = fmt.Sprintf("Đang giám sát — Ca %d", currentPeriod)
|
||
}
|
||
}
|
||
// monitorMode = "exam" — xử lý ở nhánh FindActiveExamForStudent phía trên
|
||
|
||
sessionMap := map[int]models.StudentSession{}
|
||
attMap := map[int]models.AttendanceResult{}
|
||
if classID > 0 {
|
||
var sessions []models.StudentSession
|
||
_ = db.Where("student_rk_id = ? AND class_rk_id = ? AND session_date = ?", studentID, classID, today).Find(&sessions).Error
|
||
for _, s := range sessions {
|
||
p := s.Period
|
||
if p < 1 {
|
||
p = 1
|
||
}
|
||
sessionMap[p] = s
|
||
}
|
||
var attendances []models.AttendanceResult
|
||
_ = db.Where("student_rk_id = ? AND class_rk_id = ? AND session_date = ?", studentID, classID, today).Find(&attendances).Error
|
||
for _, a := range attendances {
|
||
p := a.Period
|
||
if p < 1 {
|
||
p = 1
|
||
}
|
||
attMap[p] = a
|
||
}
|
||
}
|
||
|
||
shifts := make([]studentShiftItem, 0, len(todaySchedules))
|
||
for _, sch := range todaySchedules {
|
||
period := sch.Period
|
||
if period < 1 {
|
||
period = 1
|
||
}
|
||
sess := sessionMap[period]
|
||
att := attMap[period]
|
||
startMin := parseTimeMinutes(sch.StartTime)
|
||
endMin := parseTimeMinutes(sch.EndTime)
|
||
isActiveNow := currMin >= startMin && currMin <= endMin
|
||
|
||
attStatus := -1
|
||
attLabel := "Chưa tính"
|
||
if att.ID > 0 {
|
||
attStatus = att.Status
|
||
attLabel = att.StatusLabel
|
||
if attLabel == "" {
|
||
attLabel = attendanceLabelFallback(att.Status)
|
||
}
|
||
}
|
||
|
||
shifts = append(shifts, studentShiftItem{
|
||
Period: period,
|
||
CourseName: sch.CourseName,
|
||
StartTime: sch.StartTime,
|
||
EndTime: sch.EndTime,
|
||
IsActiveNow: isActiveNow,
|
||
OnlineSeconds: sess.OnlineSeconds,
|
||
OfflineSeconds: sess.OfflineSeconds,
|
||
AttendanceStatus: attStatus,
|
||
AttendanceLabel: attLabel,
|
||
})
|
||
}
|
||
|
||
// Ưu tiên phòng thi nếu sinh viên đang trong khung giờ thi
|
||
if examInfo := internalDb.FindActiveExamForStudent(db, studentID); examInfo != nil {
|
||
examKeywords := strings.TrimSpace(examInfo.Room.AllowedApps)
|
||
if examKeywords == "" {
|
||
examKeywords = systemAllowedApps
|
||
} else {
|
||
examKeywords = examKeywords + "," + systemAllowedApps
|
||
}
|
||
paperSent := examInfo.Enrollment.PaperSentAt != nil
|
||
var subCount int64
|
||
_ = db.Model(&models.ExamSubmission{}).Where("exam_room_id = ? AND student_rk_id = ?", examInfo.Room.ID, studentID).Count(&subCount).Error
|
||
examPayload := fiber.Map{
|
||
"examRoomId": examInfo.Room.ID,
|
||
"examName": examInfo.Room.Name,
|
||
"startTime": examInfo.Room.StartTime,
|
||
"endTime": examInfo.Room.EndTime,
|
||
"quizUrl": examInfo.Room.QuizURL,
|
||
"paperSent": paperSent,
|
||
"submitted": subCount > 0,
|
||
}
|
||
if paperSent && examInfo.Paper != nil {
|
||
examPayload["paperId"] = examInfo.Paper.ID
|
||
examPayload["paperTitle"] = examInfo.Paper.Title
|
||
}
|
||
return c.JSON(fiber.Map{
|
||
"sessionDate": today,
|
||
"monitorMode": "exam",
|
||
"monitorLabel": fmt.Sprintf("Phòng thi: %s", examInfo.Room.Name),
|
||
"classRkId": classID,
|
||
"className": className,
|
||
"classCode": classCode,
|
||
"currentPeriod": 0,
|
||
"currentCourseName": "",
|
||
"currentShiftStart": "",
|
||
"currentShiftEnd": "",
|
||
"inScheduleNow": false,
|
||
"blockerActive": examKeywords != "",
|
||
"allowedKeywords": examKeywords,
|
||
"shifts": shifts,
|
||
"exam": examPayload,
|
||
})
|
||
}
|
||
|
||
return c.JSON(fiber.Map{
|
||
"sessionDate": today,
|
||
"monitorMode": monitorMode,
|
||
"monitorLabel": monitorLabel,
|
||
"classRkId": classID,
|
||
"className": className,
|
||
"classCode": classCode,
|
||
"currentPeriod": currentPeriod,
|
||
"currentCourseName": currentCourse,
|
||
"currentShiftStart": currentStart,
|
||
"currentShiftEnd": currentEnd,
|
||
"inScheduleNow": inScheduleNow,
|
||
"blockerActive": hasApps && inScheduleNow,
|
||
"allowedKeywords": keywords,
|
||
"shifts": shifts,
|
||
})
|
||
}
|
||
}
|
||
|
||
func weekdayIndex(t time.Time) int {
|
||
wd := t.Weekday()
|
||
if wd == time.Sunday {
|
||
return 6
|
||
}
|
||
return int(wd) - 1
|
||
}
|
||
|
||
func attendanceLabelFallback(status int) string {
|
||
switch status {
|
||
case 1:
|
||
return "Nghỉ có phép"
|
||
case 2:
|
||
return "Nghỉ nửa buổi"
|
||
case 3:
|
||
return "Đi học muộn"
|
||
case 4:
|
||
return "Đi học đầy đủ"
|
||
default:
|
||
return "Nghỉ không phép"
|
||
}
|
||
}
|