All checks were successful
Deploy on Master Change / deploy (push) Successful in 1m30s
697 lines
22 KiB
Go
697 lines
22 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
internalDb "server/internal/db"
|
|
"server/internal/models"
|
|
"server/internal/qldt"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
var standardShiftTemplate = []struct {
|
|
Period int
|
|
StartTime string
|
|
EndTime string
|
|
}{
|
|
{1, "07:00", "09:00"},
|
|
{2, "09:10", "11:10"},
|
|
{3, "12:10", "14:10"},
|
|
{4, "14:20", "16:20"},
|
|
}
|
|
|
|
// GET /api/classes/:rkId/courses
|
|
func GetClassCoursesHandler(db *gorm.DB, qldtClient *qldt.Client) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
rkID, err := strconv.ParseInt(c.Params("rkId"), 10, 64)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
|
|
}
|
|
|
|
token := GetQldtToken(db)
|
|
var liveErr error
|
|
if token != "" {
|
|
res, err := qldtClient.GetClassCourses(context.Background(), token, rkID)
|
|
if err == nil && len(res.Data) > 0 {
|
|
dtos := qldtCourseItemsToDTO(res.Data)
|
|
_ = internalDb.UpsertClassCourses(db, rkID, dtos)
|
|
return c.JSON(fiber.Map{"data": dtos, "source": "qldt"})
|
|
}
|
|
liveErr = err
|
|
} else {
|
|
liveErr = fmt.Errorf("QLDT_TOKEN chưa cấu hình")
|
|
}
|
|
|
|
cached, err := internalDb.ListClassCourses(db, rkID)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
if len(cached) > 0 {
|
|
resp := fiber.Map{
|
|
"data": cached,
|
|
"source": "cache",
|
|
"cached": true,
|
|
}
|
|
if liveErr != nil {
|
|
resp["warning"] = liveErr.Error()
|
|
}
|
|
return c.JSON(resp)
|
|
}
|
|
|
|
if liveErr != nil {
|
|
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{
|
|
"error": liveErr.Error(),
|
|
"hint": "Cập nhật QLDT_TOKEN trong server/.env rồi chạy Sync lớp học để tải danh sách môn.",
|
|
})
|
|
}
|
|
return c.JSON(fiber.Map{"data": []internalDb.ClassCourseDTO{}})
|
|
}
|
|
}
|
|
|
|
func qldtCourseItemsToDTO(items []qldt.CourseItem) []internalDb.ClassCourseDTO {
|
|
out := make([]internalDb.ClassCourseDTO, 0, len(items))
|
|
for _, it := range items {
|
|
if it.ID <= 0 {
|
|
continue
|
|
}
|
|
out = append(out, internalDb.ClassCourseDTO{
|
|
ID: it.ID,
|
|
Name: it.Name,
|
|
CourseCode: it.CourseCode,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// POST /api/classes/:rkId/schedule/apply-template
|
|
func ApplyScheduleTemplateHandler(db *gorm.DB) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
rkID, err := strconv.ParseInt(c.Params("rkId"), 10, 64)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
|
|
}
|
|
|
|
created := 0
|
|
skipped := 0
|
|
for day := 0; day <= 4; day++ {
|
|
for _, slot := range standardShiftTemplate {
|
|
var existing int64
|
|
db.Model(&models.ClassSchedule{}).
|
|
Where("class_rk_id = ? AND day_of_week = ? AND period = ?", rkID, day, slot.Period).
|
|
Count(&existing)
|
|
if existing > 0 {
|
|
skipped++
|
|
continue
|
|
}
|
|
row := models.ClassSchedule{
|
|
ClassRkID: rkID,
|
|
DayOfWeek: day,
|
|
Period: slot.Period,
|
|
StartTime: slot.StartTime,
|
|
EndTime: slot.EndTime,
|
|
CourseID: 0,
|
|
CourseName: "",
|
|
IsActive: false,
|
|
}
|
|
if err := db.Create(&row).Error; err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
created++
|
|
}
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true, "created": created, "skipped": skipped})
|
|
}
|
|
}
|
|
|
|
type attendanceRow struct {
|
|
StudentRkID int64 `json:"studentRkId"`
|
|
StudentCode string `json:"studentCode"`
|
|
FullName string `json:"fullName"`
|
|
Email string `json:"email"`
|
|
Status int `json:"status"`
|
|
StatusLabel string `json:"statusLabel"`
|
|
OnlineMinutes int `json:"onlineMinutes"`
|
|
StatusEditedByTeacher bool `json:"statusEditedByTeacher"`
|
|
PushedToQLDTAt *time.Time `json:"pushedToQldtAt,omitempty"`
|
|
}
|
|
|
|
func resolveScheduleForPeriod(db *gorm.DB, classRkID int64, dayOfWeek, period int) (*models.ClassSchedule, error) {
|
|
var row models.ClassSchedule
|
|
err := db.Where("class_rk_id = ? AND day_of_week = ? AND period = ?", classRkID, dayOfWeek, period).First(&row).Error
|
|
if err == nil {
|
|
return &row, nil
|
|
}
|
|
var slots []models.ClassSchedule
|
|
if err := db.Where("class_rk_id = ? AND day_of_week = ?", classRkID, dayOfWeek).
|
|
Order("start_time").Find(&slots).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
if period < 1 || period > len(slots) {
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
return &slots[period-1], nil
|
|
}
|
|
|
|
func recalcAttendanceForPeriod(db *gorm.DB, classRkID int64, date string, period int, dayOfWeek int) error {
|
|
sched, err := resolveScheduleForPeriod(db, classRkID, dayOfWeek, period)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
shiftMins := qldt.ShiftDurationMinutes(sched.StartTime, sched.EndTime)
|
|
|
|
var mappings []models.ClassStudent
|
|
if err := db.Where("class_rk_id = ?", classRkID).Find(&mappings).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, m := range mappings {
|
|
var session models.StudentSession
|
|
onlineMins := 0
|
|
periods := []int{period}
|
|
if period == 1 {
|
|
periods = append(periods, 0)
|
|
}
|
|
err := db.Where("student_rk_id = ? AND class_rk_id = ? AND session_date = ? AND period IN ?",
|
|
m.StudentRkID, classRkID, date, periods).
|
|
Order("period DESC").First(&session).Error
|
|
if err == nil {
|
|
onlineMins = session.OnlineSeconds / 60
|
|
}
|
|
|
|
status := qldt.InferStatusFromOnlineMinutes(onlineMins, shiftMins)
|
|
label := qldt.AttendanceStatusLabel(status)
|
|
|
|
row := models.AttendanceResult{
|
|
ClassRkID: classRkID,
|
|
SessionDate: date,
|
|
Period: period,
|
|
StudentRkID: m.StudentRkID,
|
|
Status: status,
|
|
StatusLabel: label,
|
|
OnlineMinutes: onlineMins,
|
|
}
|
|
|
|
if err := db.Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{
|
|
{Name: "class_rk_id"},
|
|
{Name: "session_date"},
|
|
{Name: "period"},
|
|
{Name: "student_rk_id"},
|
|
},
|
|
DoUpdates: clause.Assignments(map[string]interface{}{
|
|
"online_minutes": gorm.Expr("VALUES(online_minutes)"),
|
|
"status": gorm.Expr("IF(status_edited_by_teacher, status, VALUES(status))"),
|
|
"status_label": gorm.Expr("IF(status_edited_by_teacher, status_label, VALUES(status_label))"),
|
|
"updated_at": time.Now(),
|
|
}),
|
|
}).Create(&row).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func scheduleDayFromDate(date string) (int, error) {
|
|
t, err := time.Parse("2006-01-02", date)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
wd := t.Weekday()
|
|
if wd == time.Sunday {
|
|
return 6, nil
|
|
}
|
|
return int(wd) - 1, nil
|
|
}
|
|
|
|
// GET /api/classes/:rkId/attendance?date=&period=
|
|
func GetClassAttendanceHandler(db *gorm.DB) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
classRkID, _ := strconv.ParseInt(c.Params("rkId"), 10, 64)
|
|
date := c.Query("date", qldt.NowDateVN())
|
|
period, _ := strconv.Atoi(c.Query("period", "1"))
|
|
if period < 1 {
|
|
period = 1
|
|
}
|
|
|
|
dayOfWeek, err := scheduleDayFromDate(date)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid date"})
|
|
}
|
|
|
|
_ = recalcAttendanceForPeriod(db, classRkID, date, period, dayOfWeek)
|
|
|
|
var mappings []models.ClassStudent
|
|
db.Where("class_rk_id = ?", classRkID).Find(&mappings)
|
|
studentIDs := make([]int64, 0, len(mappings))
|
|
for _, m := range mappings {
|
|
studentIDs = append(studentIDs, m.StudentRkID)
|
|
}
|
|
if len(studentIDs) == 0 {
|
|
return c.JSON(fiber.Map{"data": []any{}, "period": period, "date": date})
|
|
}
|
|
|
|
var students []models.Student
|
|
db.Where("rk_id IN ?", studentIDs).Find(&students)
|
|
|
|
var results []models.AttendanceResult
|
|
db.Where("class_rk_id = ? AND session_date = ? AND period = ?", classRkID, date, period).Find(&results)
|
|
resMap := map[int64]models.AttendanceResult{}
|
|
for _, r := range results {
|
|
resMap[r.StudentRkID] = r
|
|
}
|
|
|
|
out := make([]attendanceRow, 0, len(students))
|
|
for _, st := range students {
|
|
r, ok := resMap[st.RkID]
|
|
row := attendanceRow{
|
|
StudentRkID: st.RkID,
|
|
StudentCode: st.StudentCode,
|
|
FullName: st.FullName,
|
|
Email: st.Email,
|
|
}
|
|
if ok {
|
|
row.Status = r.Status
|
|
row.StatusLabel = r.StatusLabel
|
|
row.OnlineMinutes = r.OnlineMinutes
|
|
row.StatusEditedByTeacher = r.StatusEditedByTeacher
|
|
row.PushedToQLDTAt = r.PushedToQLDTAt
|
|
}
|
|
out = append(out, row)
|
|
}
|
|
|
|
var shiftPushedAt *time.Time
|
|
qldtDirty := false
|
|
for _, r := range results {
|
|
if r.PushedToQLDTAt != nil {
|
|
if shiftPushedAt == nil || r.PushedToQLDTAt.After(*shiftPushedAt) {
|
|
t := *r.PushedToQLDTAt
|
|
shiftPushedAt = &t
|
|
}
|
|
if r.UpdatedAt.After(*r.PushedToQLDTAt) {
|
|
qldtDirty = true
|
|
}
|
|
}
|
|
}
|
|
|
|
sched, _ := resolveScheduleForPeriod(db, classRkID, dayOfWeek, period)
|
|
shiftInfo := fiber.Map{
|
|
"pushedToQldtAt": shiftPushedAt,
|
|
"qldtDirty": qldtDirty,
|
|
}
|
|
if sched != nil {
|
|
shiftInfo["startTime"] = sched.StartTime
|
|
shiftInfo["endTime"] = sched.EndTime
|
|
shiftInfo["courseId"] = sched.CourseID
|
|
shiftInfo["courseName"] = sched.CourseName
|
|
shiftInfo["isActive"] = sched.IsActive
|
|
}
|
|
|
|
return c.JSON(fiber.Map{"data": out, "period": period, "date": date, "shift": shiftInfo})
|
|
}
|
|
}
|
|
|
|
// PUT /api/classes/:rkId/attendance/status
|
|
func UpdateAttendanceStatusHandler(db *gorm.DB) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
classRkID, _ := strconv.ParseInt(c.Params("rkId"), 10, 64)
|
|
var req struct {
|
|
Date string `json:"date"`
|
|
Period int `json:"period"`
|
|
StudentRkID int64 `json:"studentRkId"`
|
|
Status int `json:"status"`
|
|
}
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload"})
|
|
}
|
|
if req.Date == "" {
|
|
req.Date = qldt.NowDateVN()
|
|
}
|
|
if req.Period < 1 {
|
|
req.Period = 1
|
|
}
|
|
|
|
label := qldt.AttendanceStatusLabel(req.Status)
|
|
var row models.AttendanceResult
|
|
err := db.Where("class_rk_id = ? AND session_date = ? AND period = ? AND student_rk_id = ?",
|
|
classRkID, req.Date, req.Period, req.StudentRkID).First(&row).Error
|
|
if err == gorm.ErrRecordNotFound {
|
|
row = models.AttendanceResult{
|
|
ClassRkID: classRkID,
|
|
SessionDate: req.Date,
|
|
Period: req.Period,
|
|
StudentRkID: req.StudentRkID,
|
|
Status: req.Status,
|
|
StatusLabel: label,
|
|
StatusEditedByTeacher: true,
|
|
}
|
|
if err := db.Create(&row).Error; err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
} else if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
} else {
|
|
row.Status = req.Status
|
|
row.StatusLabel = label
|
|
row.StatusEditedByTeacher = true
|
|
if err := db.Save(&row).Error; err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true, "data": row})
|
|
}
|
|
}
|
|
|
|
// PUT /api/classes/:rkId/attendance/bulk-status
|
|
func UpdateAttendanceBulkStatusHandler(db *gorm.DB) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
classRkID, _ := strconv.ParseInt(c.Params("rkId"), 10, 64)
|
|
var req struct {
|
|
Date string `json:"date"`
|
|
Period int `json:"period"`
|
|
StudentRkIDs []int64 `json:"studentRkIds"`
|
|
Status int `json:"status"`
|
|
}
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload"})
|
|
}
|
|
if req.Date == "" {
|
|
req.Date = qldt.NowDateVN()
|
|
}
|
|
if req.Period < 1 {
|
|
req.Period = 1
|
|
}
|
|
if len(req.StudentRkIDs) == 0 {
|
|
return c.JSON(fiber.Map{"ok": true, "message": "No students selected"})
|
|
}
|
|
|
|
label := qldt.AttendanceStatusLabel(req.Status)
|
|
tx := db.Begin()
|
|
for _, studentRkID := range req.StudentRkIDs {
|
|
var row models.AttendanceResult
|
|
err := tx.Where("class_rk_id = ? AND session_date = ? AND period = ? AND student_rk_id = ?",
|
|
classRkID, req.Date, req.Period, studentRkID).First(&row).Error
|
|
if err == gorm.ErrRecordNotFound {
|
|
row = models.AttendanceResult{
|
|
ClassRkID: classRkID,
|
|
SessionDate: req.Date,
|
|
Period: req.Period,
|
|
StudentRkID: studentRkID,
|
|
Status: req.Status,
|
|
StatusLabel: label,
|
|
StatusEditedByTeacher: true,
|
|
}
|
|
if err := tx.Create(&row).Error; err != nil {
|
|
tx.Rollback()
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
} else if err != nil {
|
|
tx.Rollback()
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
} else {
|
|
row.Status = req.Status
|
|
row.StatusLabel = label
|
|
row.StatusEditedByTeacher = true
|
|
if err := tx.Save(&row).Error; err != nil {
|
|
tx.Rollback()
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
}
|
|
}
|
|
if err := tx.Commit().Error; err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
}
|
|
|
|
// POST /api/classes/:rkId/attendance/push-qldt
|
|
func PushAttendanceToQLDTHandler(db *gorm.DB, qldtClient *qldt.Client) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
classRkID, _ := strconv.ParseInt(c.Params("rkId"), 10, 64)
|
|
var req struct {
|
|
Date string `json:"date"`
|
|
Period int `json:"period"`
|
|
}
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload"})
|
|
}
|
|
if req.Date == "" {
|
|
req.Date = qldt.NowDateVN()
|
|
}
|
|
if req.Period < 1 {
|
|
req.Period = 1
|
|
}
|
|
token := GetQldtToken(db)
|
|
if token == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "QLDT_TOKEN chưa cấu hình"})
|
|
}
|
|
|
|
dayOfWeek, _ := scheduleDayFromDate(req.Date)
|
|
sched, err := resolveScheduleForPeriod(db, classRkID, dayOfWeek, req.Period)
|
|
if err != nil || sched == nil || sched.CourseID == 0 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Ca học chưa cấu hình môn học (courseId)"})
|
|
}
|
|
|
|
userID, err := qldt.ParseUserIDFromToken(token)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Không đọc được users_id từ QLDT_TOKEN: " + err.Error()})
|
|
}
|
|
|
|
var results []models.AttendanceResult
|
|
db.Where("class_rk_id = ? AND session_date = ? AND period = ?", classRkID, req.Date, req.Period).Find(&results)
|
|
if len(results) == 0 {
|
|
_ = recalcAttendanceForPeriod(db, classRkID, req.Date, req.Period, dayOfWeek)
|
|
db.Where("class_rk_id = ? AND session_date = ? AND period = ?", classRkID, req.Date, req.Period).Find(&results)
|
|
}
|
|
|
|
students := make([]qldt.AttendanceStudent, 0, len(results))
|
|
for _, r := range results {
|
|
students = append(students, qldt.AttendanceStudent{
|
|
StudentID: r.StudentRkID,
|
|
Status: r.Status,
|
|
})
|
|
}
|
|
|
|
payload := &qldt.AttendancePayload{
|
|
UsersID: userID,
|
|
CoursesID: int(sched.CourseID),
|
|
Period: req.Period,
|
|
Students: students,
|
|
ClassID: classRkID,
|
|
Date: req.Date,
|
|
Type: "OFFLINE",
|
|
}
|
|
|
|
body, code, err := qldtClient.SaveAttendance(context.Background(), token, payload)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error(), "statusCode": code, "body": string(body)})
|
|
}
|
|
|
|
now := time.Now()
|
|
db.Model(&models.AttendanceResult{}).
|
|
Where("class_rk_id = ? AND session_date = ? AND period = ?", classRkID, req.Date, req.Period).
|
|
Update("pushed_to_qldt_at", now)
|
|
|
|
return c.JSON(fiber.Map{"ok": true, "message": "Đã đẩy điểm danh lên QLĐT", "studentCount": len(students)})
|
|
}
|
|
}
|
|
|
|
// GET /api/classes/:rkId/attendance/shifts?date=
|
|
func ListAttendanceShiftsHandler(db *gorm.DB) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
classRkID, _ := strconv.ParseInt(c.Params("rkId"), 10, 64)
|
|
date := c.Query("date", qldt.NowDateVN())
|
|
dayOfWeek, err := scheduleDayFromDate(date)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid date"})
|
|
}
|
|
|
|
var slots []models.ClassSchedule
|
|
db.Where("class_rk_id = ? AND day_of_week = ?", classRkID, dayOfWeek).
|
|
Order("period, start_time").Find(&slots)
|
|
|
|
if len(slots) == 0 {
|
|
for p := 1; p <= 4; p++ {
|
|
slots = append(slots, models.ClassSchedule{
|
|
Period: p, DayOfWeek: dayOfWeek, IsActive: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
out := make([]fiber.Map, 0, len(slots))
|
|
for _, s := range slots {
|
|
period := s.Period
|
|
if period == 0 {
|
|
period = 1
|
|
}
|
|
out = append(out, fiber.Map{
|
|
"period": period,
|
|
"startTime": s.StartTime,
|
|
"endTime": s.EndTime,
|
|
"courseId": s.CourseID,
|
|
"courseName": s.CourseName,
|
|
"isActive": s.IsActive,
|
|
})
|
|
}
|
|
return c.JSON(fiber.Map{"data": out, "date": date, "dayOfWeek": dayOfWeek})
|
|
}
|
|
}
|
|
|
|
// GET /api/classes/:rkId/leave-requests
|
|
func GetLeaveRequestsHandler(db *gorm.DB, qldtClient *qldt.Client) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
classRkID, _ := strconv.ParseInt(c.Params("rkId"), 10, 64)
|
|
courseID, _ := strconv.ParseInt(c.Query("courseId"), 10, 64)
|
|
date := c.Query("date", qldt.NowDateVN())
|
|
|
|
if courseID == 0 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Missing courseId"})
|
|
}
|
|
|
|
token := GetQldtToken(db)
|
|
if token == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "QLDT_TOKEN chưa cấu hình"})
|
|
}
|
|
|
|
body, err := qldtClient.GetLeaveRequests(context.Background(), token, classRkID, courseID, date)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
var data []map[string]interface{}
|
|
if err := json.Unmarshal(body, &data); err != nil {
|
|
var fallback any
|
|
if errFallback := json.Unmarshal(body, &fallback); errFallback == nil {
|
|
return c.JSON(fallback)
|
|
}
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to parse QLDT response"})
|
|
}
|
|
|
|
// Ghi đè trạng thái duyệt local nếu có
|
|
var localReqs []models.LocalLeaveRequest
|
|
if err := db.Find(&localReqs).Error; err == nil && len(localReqs) > 0 {
|
|
localMap := make(map[int64]string)
|
|
for _, lr := range localReqs {
|
|
localMap[lr.LeaveID] = lr.Status
|
|
}
|
|
for i, item := range data {
|
|
if idVal, ok := item["id"]; ok {
|
|
var idInt int64
|
|
switch v := idVal.(type) {
|
|
case float64:
|
|
idInt = int64(v)
|
|
case int64:
|
|
idInt = v
|
|
case int:
|
|
idInt = int64(v)
|
|
}
|
|
if localStatus, found := localMap[idInt]; found {
|
|
data[i]["status"] = localStatus
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return c.JSON(data)
|
|
}
|
|
}
|
|
|
|
// POST /api/classes/:rkId/leave-requests/:leaveId/status
|
|
func UpdateLeaveStatusHandler(db *gorm.DB, qldtClient *qldt.Client) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
classRkID, _ := strconv.ParseInt(c.Params("rkId"), 10, 64)
|
|
leaveID, _ := strconv.ParseInt(c.Params("leaveId"), 10, 64)
|
|
|
|
var req struct {
|
|
Status string `json:"status"` // "Phê duyệt" or "Từ chối"
|
|
StudentRkID int64 `json:"studentRkId"`
|
|
Date string `json:"date"`
|
|
Period int `json:"period"`
|
|
}
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid body payload"})
|
|
}
|
|
|
|
if req.Status != "Phê duyệt" && req.Status != "Từ chối" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Trạng thái phê duyệt không hợp lệ"})
|
|
}
|
|
|
|
// 1. Lưu trạng thái đơn phép vào database local thay vì gọi QLDT Portal API
|
|
var localLeave models.LocalLeaveRequest
|
|
errLocal := db.Where("leave_id = ?", leaveID).First(&localLeave).Error
|
|
if errLocal == gorm.ErrRecordNotFound {
|
|
localLeave = models.LocalLeaveRequest{
|
|
LeaveID: leaveID,
|
|
Status: req.Status,
|
|
}
|
|
if err := db.Create(&localLeave).Error; err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Lỗi lưu đơn phép local: " + err.Error()})
|
|
}
|
|
} else if errLocal == nil {
|
|
localLeave.Status = req.Status
|
|
if err := db.Save(&localLeave).Error; err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Lỗi cập nhật đơn phép local: " + err.Error()})
|
|
}
|
|
} else {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": errLocal.Error()})
|
|
}
|
|
|
|
// 2. Nếu status là "Phê duyệt", tự động cập nhật điểm danh trong hệ thống của chúng ta thành "Nghỉ có phép"
|
|
if req.Status == "Phê duyệt" {
|
|
if req.StudentRkID == 0 || req.Date == "" || req.Period == 0 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Thiếu thông tin cập nhật điểm danh (studentRkId, date, period)"})
|
|
}
|
|
|
|
dayOfWeek, err := scheduleDayFromDate(req.Date)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Ngày không hợp lệ"})
|
|
}
|
|
|
|
sched, err := resolveScheduleForPeriod(db, classRkID, dayOfWeek, req.Period)
|
|
if err != nil || sched == nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Không tìm thấy lịch học cho ca này"})
|
|
}
|
|
|
|
// Cập nhật hoặc Tạo mới AttendanceResult
|
|
var row models.AttendanceResult
|
|
errFind := db.Where("class_rk_id = ? AND session_date = ? AND period = ? AND student_rk_id = ?",
|
|
classRkID, req.Date, req.Period, req.StudentRkID).First(&row).Error
|
|
|
|
if errFind == nil {
|
|
// Cập nhật hàng có sẵn
|
|
row.Status = 1 // Nghỉ có phép
|
|
row.StatusLabel = "Nghỉ có phép"
|
|
row.StatusEditedByTeacher = true
|
|
if err := db.Save(&row).Error; err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Lỗi lưu điểm danh: " + err.Error()})
|
|
}
|
|
} else if errFind == gorm.ErrRecordNotFound {
|
|
// Tạo mới hàng
|
|
row = models.AttendanceResult{
|
|
ClassRkID: classRkID,
|
|
SessionDate: req.Date,
|
|
Period: req.Period,
|
|
StudentRkID: req.StudentRkID,
|
|
Status: 1,
|
|
StatusLabel: "Nghỉ có phép",
|
|
StatusEditedByTeacher: true,
|
|
}
|
|
if err := db.Create(&row).Error; err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Lỗi tạo điểm danh: " + err.Error()})
|
|
}
|
|
} else {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": errFind.Error()})
|
|
}
|
|
}
|
|
|
|
return c.JSON(fiber.Map{"ok": true, "message": "Cập nhật đơn xin nghỉ thành công!"})
|
|
}
|
|
}
|