core v1
This commit is contained in:
257
server/internal/qldt/attendance.go
Normal file
257
server/internal/qldt/attendance.go
Normal file
@@ -0,0 +1,257 @@
|
||||
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")
|
||||
}
|
||||
@@ -48,6 +48,7 @@ type StudentDTO struct {
|
||||
Gender *int `json:"gender"`
|
||||
Status *string `json:"status"`
|
||||
Location *string `json:"location"`
|
||||
Avatar *string `json:"avatar"`
|
||||
System *struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
||||
Reference in New Issue
Block a user