Files
rikkei_simple_care/server/internal/qldt/attendance.go
2026-06-30 09:31:33 +07:00

258 lines
6.2 KiB
Go

package qldt
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
type CourseItem struct {
ID int64 `json:"id"`
Name string `json:"name"`
CourseCode string `json:"courseCode"`
}
type CoursesResponse struct {
Data []CourseItem `json:"data"`
Message string `json:"message"`
StatusCode int `json:"statusCode"`
}
type AttendanceStudent struct {
StudentID int64 `json:"student_id"`
Status int `json:"status"`
}
type AttendancePayload struct {
UsersID int `json:"users_id"`
CoursesID int `json:"courses_id"`
Period int `json:"period"`
Students []AttendanceStudent `json:"students"`
ClassID int64 `json:"class_id"`
Date string `json:"date"`
Type string `json:"type"`
}
func ParseUserIDFromToken(token string) (int, error) {
parts := strings.Split(token, ".")
if len(parts) < 2 {
return 0, fmt.Errorf("invalid jwt")
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return 0, err
}
var claims struct {
ID int `json:"id"`
}
if err := json.Unmarshal(payload, &claims); err != nil {
return 0, err
}
if claims.ID == 0 {
return 0, fmt.Errorf("token missing user id")
}
return claims.ID, nil
}
func (c *Client) GetClassCourses(ctx context.Context, token string, classID int64) (*CoursesResponse, error) {
u := fmt.Sprintf("%s/courses/class/%d", c.baseURL, classID)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return nil, err
}
c.setAuthHeaders(req, token)
resp, err := c.http.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("qldt courses http %d: %s", resp.StatusCode, string(body))
}
var out CoursesResponse
if err := json.Unmarshal(body, &out); err != nil {
return nil, err
}
if out.StatusCode == 401 || strings.Contains(strings.ToLower(out.Message), "expired") {
return nil, fmt.Errorf("qldt token invalid: %s", out.Message)
}
return &out, nil
}
func (c *Client) SaveAttendance(ctx context.Context, token string, payload *AttendancePayload) ([]byte, int, error) {
jsonData, err := json.Marshal(payload)
if err != nil {
return nil, 0, err
}
u := c.baseURL + "/attendance"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewBuffer(jsonData))
if err != nil {
return nil, 0, err
}
req.Header.Set("Content-Type", "application/json")
c.setAuthHeaders(req, token)
resp, err := c.http.Do(req)
if err != nil {
return nil, 0, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return body, resp.StatusCode, fmt.Errorf("qldt attendance post %d: %s", resp.StatusCode, string(body))
}
return body, resp.StatusCode, nil
}
func (c *Client) UpdateAttendanceDetailStatus(ctx context.Context, token string, detailID int64, status string) error {
payload := map[string]string{"status": status}
jsonData, _ := json.Marshal(payload)
u := fmt.Sprintf("%s/attendance-detail/status/%d", c.baseURL, detailID)
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, u, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
c.setAuthHeaders(req, token)
resp, err := c.http.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("qldt patch detail %d: %s", resp.StatusCode, string(body))
}
return nil
}
type PortalAttendanceDetail struct {
ID int64 `json:"id"`
Status string `json:"status"`
Student struct {
ID int64 `json:"id"`
} `json:"student"`
}
type PortalAttendanceSession struct {
ID int64 `json:"id"`
Date string `json:"date"`
Period int `json:"period"`
AttendanceDetail []PortalAttendanceDetail `json:"attendanceDetail"`
}
type PortalAttendanceResponse struct {
Data map[string][]PortalAttendanceSession `json:"data"`
Message string `json:"message"`
}
func (c *Client) GetAttendance(ctx context.Context, token string, classID int64, courseID int64) (*PortalAttendanceResponse, error) {
u, _ := url.Parse(c.baseURL + "/attendance")
q := u.Query()
q.Set("class_id", fmt.Sprintf("%d", classID))
q.Set("course_id", fmt.Sprintf("%d", courseID))
u.RawQuery = q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
c.setAuthHeaders(req, token)
resp, err := c.http.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("qldt get attendance %d: %s", resp.StatusCode, string(body))
}
var out PortalAttendanceResponse
if err := json.Unmarshal(body, &out); err != nil {
return nil, err
}
return &out, nil
}
func (c *Client) setAuthHeaders(req *http.Request, token string) {
req.Header.Set("Authorization", "Bearer "+token)
if c.origin != "" {
req.Header.Set("Origin", c.origin)
req.Header.Set("Referer", c.origin+"/")
}
req.Header.Set("Accept", "application/json")
}
func AttendanceStatusLabel(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"
}
}
func ShiftDurationMinutes(start, end string) int {
return parseHM(end) - parseHM(start)
}
func parseHM(t string) int {
parts := strings.Split(t, ":")
if len(parts) < 2 {
return 0
}
h, m := 0, 0
fmt.Sscanf(parts[0], "%d", &h)
fmt.Sscanf(parts[1], "%d", &m)
return h*60 + m
}
func InferStatusFromOnlineMinutes(onlineMins, shiftMins int) int {
if shiftMins <= 0 {
if onlineMins > 0 {
return 4
}
return 0
}
rate := float64(onlineMins) / float64(shiftMins)
switch {
case rate >= 0.85:
return 4
case rate >= 0.5:
return 2
case onlineMins > 0:
return 3
default:
return 0
}
}
func NowDateVN() string {
return time.Now().Format("2006-01-02")
}