This commit is contained in:
2026-06-29 23:45:08 +07:00
parent 5290eeeb91
commit c736326162
15 changed files with 3067 additions and 388 deletions

View File

@@ -0,0 +1,298 @@
package handlers
import (
"strconv"
"strings"
"server/internal/models"
"server/internal/qldt"
"server/internal/syncjobs"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)
// Helper convert string to int
func atoiDefault(s string, def int) int {
if s == "" {
return def
}
v, err := strconv.Atoi(s)
if err != nil {
return def
}
return v
}
// Handler bắt đầu đồng bộ lớp học
func StartClassesSyncHandler(db *gorm.DB, client *qldt.Client, job *syncjobs.ClassesSyncJob, token string) fiber.Handler {
return func(c *fiber.Ctx) error {
if token == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Missing token QLDT_TOKEN"})
}
started := job.Start(db, client, token)
if !started {
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "Sync classes is already running"})
}
return c.JSON(fiber.Map{"ok": true, "message": "Classes sync job started"})
}
}
// Handler lấy status đồng bộ lớp học
func GetClassesSyncStatusHandler(job *syncjobs.ClassesSyncJob) fiber.Handler {
return func(c *fiber.Ctx) error {
return c.JSON(job.Status())
}
}
// Handler bắt đầu đồng bộ toàn bộ sinh viên
func StartStudentsSyncHandler(db *gorm.DB, client *qldt.Client, job *syncjobs.StudentsSyncJob, token string) fiber.Handler {
return func(c *fiber.Ctx) error {
if token == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Missing token QLDT_TOKEN"})
}
started := job.Start(db, client, token)
if !started {
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "Sync students is already running"})
}
return c.JSON(fiber.Map{"ok": true, "message": "Students sync job started"})
}
}
// Handler lấy status đồng bộ sinh viên
func GetStudentsSyncStatusHandler(job *syncjobs.StudentsSyncJob) fiber.Handler {
return func(c *fiber.Ctx) error {
return c.JSON(job.Status())
}
}
type courseRefItem struct {
CourseRkID int64 `json:"courseRkId"`
CourseName string `json:"courseName"`
}
type classItem struct {
RkID int64 `json:"rkId"`
Name string `json:"name"`
ClassCode string `json:"classCode"`
Type string `json:"type"`
StudentCount int `json:"studentCount"`
SpecializeRkID *int64 `json:"specializeRkId"`
SpecializeName *string `json:"specializeName"`
SystemRkID *int64 `json:"systemRkId"`
SystemCode *string `json:"systemCode"`
SystemName *string `json:"systemName"`
IsStudying bool `json:"isStudying"`
Courses []courseRefItem `json:"courses"`
}
// Handler danh sách lớp học
func ListClassesHandler(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
page := atoiDefault(c.Query("page"), 1)
pageSize := atoiDefault(c.Query("pageSize"), 20)
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
queryDB := db.Model(&models.Class{})
if sys := c.Query("systemRkId"); sys != "" {
if id, err := strconv.ParseInt(sys, 10, 64); err == nil {
queryDB = queryDB.Where("system_rk_id = ?", id)
}
}
if s := strings.TrimSpace(c.Query("q")); s != "" {
pat := "%" + s + "%"
queryDB = queryDB.Where("(name LIKE ? OR class_code LIKE ? OR type LIKE ? OR specialize_name LIKE ? OR system_name LIKE ?)", pat, pat, pat, pat, pat)
}
studyingOnly := c.Query("studyingOnly")
if studyingOnly == "1" || studyingOnly == "true" {
queryDB = queryDB.Where("is_studying = ?", true)
}
var total int64
if err := queryDB.Count(&total).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to count classes: " + err.Error()})
}
var rows []models.Class
if err := queryDB.Order("rk_id desc").Offset((page - 1) * pageSize).Limit(pageSize).Find(&rows).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to query classes: " + err.Error()})
}
// Kéo danh sách Courses cho các lớp
var classIDs []int64
for _, r := range rows {
classIDs = append(classIDs, r.RkID)
}
coursesByClass := map[int64][]courseRefItem{}
if len(classIDs) > 0 {
var links []models.ClassCourseLink
if err := db.Where("class_rk_id IN ?", classIDs).Order("course_order asc").Find(&links).Error; err == nil {
for _, link := range links {
coursesByClass[link.ClassRkID] = append(coursesByClass[link.ClassRkID], courseRefItem{
CourseRkID: link.CourseRkID,
CourseName: link.CourseName,
})
}
}
}
out := make([]classItem, 0, len(rows))
for _, r := range rows {
courses := coursesByClass[r.RkID]
if courses == nil {
courses = []courseRefItem{}
}
out = append(out, classItem{
RkID: r.RkID,
Name: r.Name,
ClassCode: r.ClassCode,
Type: r.Type,
StudentCount: r.StudentCount,
SpecializeRkID: r.SpecializeRkID,
SpecializeName: r.SpecializeName,
SystemRkID: r.SystemRkID,
SystemCode: r.SystemCode,
SystemName: r.SystemName,
IsStudying: r.IsStudying,
Courses: courses,
})
}
return c.JSON(fiber.Map{
"data": out,
"total": total,
"page": page,
"pageSize": pageSize,
})
}
}
// Handler cập nhật trạng thái đang học (isStudying) của lớp học
func PatchClassStudyingHandler(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
rkIDStr := c.Params("rkId")
rkID, err := strconv.ParseInt(rkIDStr, 10, 64)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
}
var body struct {
IsStudying bool `json:"isStudying"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid body payload"})
}
var cl models.Class
if err := db.Where("rk_id = ?", rkID).First(&cl).Error; err != nil {
if gorm.ErrRecordNotFound == err {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Class not found"})
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
cl.IsStudying = body.IsStudying
if err := db.Save(&cl).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to update class: " + err.Error()})
}
return c.JSON(fiber.Map{"ok": true, "data": cl})
}
}
// Handler lấy danh sách sinh viên của lớp học cụ thể
func ListClassStudentsHandler(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
rkIDStr := c.Params("rkId")
classRkID, err := strconv.ParseInt(rkIDStr, 10, 64)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
}
// Tìm danh sách student_rk_id thuộc lớp
var links []models.ClassStudent
if err := db.Where("class_rk_id = ?", classRkID).Find(&links).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to query class students: " + err.Error()})
}
var studentRkIDs []int64
for _, link := range links {
studentRkIDs = append(studentRkIDs, link.StudentRkID)
}
students := []models.Student{}
if len(studentRkIDs) > 0 {
if err := db.Where("rk_id IN ?", studentRkIDs).Order("full_name asc").Find(&students).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to load students info: " + err.Error()})
}
}
return c.JSON(fiber.Map{"data": students, "total": len(students)})
}
}
// Handler danh sách toàn bộ sinh viên (đã sync)
func ListAllStudentsHandler(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
page := atoiDefault(c.Query("page"), 1)
pageSize := atoiDefault(c.Query("pageSize"), 20)
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
queryDB := db.Model(&models.Student{})
if s := strings.TrimSpace(c.Query("q")); s != "" {
pat := "%" + s + "%"
queryDB = queryDB.Where("(full_name LIKE ? OR student_code LIKE ? OR email LIKE ? OR phone LIKE ?)", pat, pat, pat, pat)
}
var total int64
if err := queryDB.Count(&total).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to count students: " + err.Error()})
}
var students []models.Student
if err := queryDB.Order("id desc").Offset((page - 1) * pageSize).Limit(pageSize).Find(&students).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to load students: " + err.Error()})
}
return c.JSON(fiber.Map{
"data": students,
"total": total,
"page": page,
"pageSize": pageSize,
})
}
}
// Handler lấy stats nhanh hiển thị ở Dashboard
func GetStatsHandler(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
var totalClasses int64
var activeClasses int64
var totalStudents int64
db.Model(&models.Class{}).Count(&totalClasses)
db.Model(&models.Class{}).Where("is_studying = ?", true).Count(&activeClasses)
db.Model(&models.Student{}).Count(&totalStudents)
return c.JSON(fiber.Map{
"totalClasses": totalClasses,
"activeClasses": activeClasses,
"totalStudents": totalStudents,
})
}
}