Files
ClassManager/backend/models/semester.py
2026-04-22 02:51:58 +08:00

286 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ===========================================
# 班级操行分管理系统 - 学期数据模型
#
# 开发者: Canglan
# 联系方式: admin@sea-studio.top
# 版权归属: Sea Network Technology Studio
# 许可证: MIT License
#
# 版权所有 © Sea Network Technology Studio
# ===========================================
from typing import Optional, List, Dict, Any
from utils.database import execute_one, execute_query, execute_insert, execute_update, execute_many
from utils.logger import get_logger
logger = get_logger(__name__)
class SemesterModel:
"""学期数据模型"""
@staticmethod
async def create(
semester_name: str,
start_date: str = None,
end_date: str = None
) -> int:
"""创建学期"""
sql = """
INSERT INTO semesters (semester_name, start_date, end_date)
VALUES (%s, %s, %s)
"""
return await execute_insert(sql, (semester_name, start_date, end_date))
@staticmethod
async def get_by_id(semester_id: int) -> Optional[Dict[str, Any]]:
"""根据ID获取学期信息"""
sql = "SELECT * FROM semesters WHERE semester_id = %s"
return await execute_one(sql, (semester_id,))
@staticmethod
async def get_all() -> List[Dict[str, Any]]:
"""获取所有学期列表"""
sql = """
SELECT semester_id, semester_name, start_date, end_date,
is_active, is_archived, created_at
FROM semesters
ORDER BY created_at DESC
"""
return await execute_query(sql)
@staticmethod
async def get_active() -> Optional[Dict[str, Any]]:
"""获取当前活跃学期(优先 is_active 标记,降级为日期范围匹配)"""
fields = "semester_id, semester_name, start_date, end_date, is_active, is_archived, created_at"
# 第一优先级is_active 标记
sql = f"""
SELECT {fields}
FROM semesters
WHERE is_active = 1 AND is_archived = 0
LIMIT 1
"""
result = await execute_one(sql)
if result:
return result
# 第二优先级:日期范围匹配
# 注:无日期的学期不会自动匹配为活跃学期(需手动激活)
sql = f"""
SELECT {fields}
FROM semesters
WHERE is_archived = 0 AND start_date <= CURDATE() AND (end_date IS NULL OR end_date >= CURDATE())
LIMIT 1
"""
return await execute_one(sql)
@staticmethod
async def deactivate_all() -> int:
"""将所有学期设为非活跃"""
sql = "UPDATE semesters SET is_active = 0 WHERE is_active = 1"
return await execute_update(sql)
@staticmethod
async def activate(semester_id: int) -> bool:
"""设为当前活跃学期"""
sql = """
UPDATE semesters SET is_active = 1
WHERE semester_id = %s AND is_archived = 0
"""
result = await execute_update(sql, (semester_id,))
return result > 0
@staticmethod
async def archive(semester_id: int) -> bool:
"""归档学期"""
sql = """
UPDATE semesters SET is_archived = 1, is_active = 0
WHERE semester_id = %s AND is_archived = 0
"""
result = await execute_update(sql, (semester_id,))
return result > 0
@staticmethod
async def is_archived(semester_id: int) -> bool:
"""检查学期是否已归档"""
sql = "SELECT is_archived FROM semesters WHERE semester_id = %s"
result = await execute_one(sql, (semester_id,))
if not result:
return False
return bool(result['is_archived'])
@staticmethod
async def get_record_semester_id(record_id: int) -> Optional[int]:
"""获取操行分记录所属的学期ID"""
sql = "SELECT semester_id FROM conduct_records WHERE record_id = %s"
result = await execute_one(sql, (record_id,))
return result['semester_id'] if result else None
@staticmethod
async def get_attendance_stats_by_semester(semester_id: int, start_date: str, end_date: str) -> List[Dict]:
"""批量查询学期内所有学生的考勤统计"""
sql = """
SELECT student_id, status, COUNT(*) as cnt
FROM attendance_records
WHERE (semester_id = %s OR (semester_id IS NULL AND `date` BETWEEN %s AND %s))
GROUP BY student_id, status
"""
return await execute_query(sql, (semester_id, start_date, end_date))
@staticmethod
async def get_homework_stats_by_date_range(start_date: str, end_date: str) -> List[Dict]:
"""通过作业截止日期范围查询所有学生的作业提交统计"""
sql = """
SELECT hs.student_id, hs.status, COUNT(*) as cnt
FROM homework_submissions hs
JOIN assignments a ON hs.assignment_id = a.assignment_id
WHERE a.deadline BETWEEN %s AND %s
GROUP BY hs.student_id, hs.status
"""
return await execute_query(sql, (start_date, end_date))
@staticmethod
async def update(
semester_id: int,
semester_name: str = None,
start_date: str = None,
end_date: str = None
) -> bool:
"""编辑学期信息(仅未归档)"""
sets = []
params = []
if semester_name is not None:
sets.append("semester_name = %s")
params.append(semester_name)
if start_date is not None:
sets.append("start_date = %s")
params.append(start_date)
if end_date is not None:
sets.append("end_date = %s")
params.append(end_date)
if not sets:
return False
params.append(semester_id)
sql = f"UPDATE semesters SET {', '.join(sets)} WHERE semester_id = %s AND is_archived = 0"
result = await execute_update(sql, tuple(params))
return result > 0
@staticmethod
async def delete(semester_id: int) -> bool:
"""删除学期"""
sql = "DELETE FROM semesters WHERE semester_id = %s"
result = await execute_update(sql, (semester_id,))
return result > 0
@staticmethod
async def count_archives(semester_id: int) -> int:
"""统计学期的归档数据数量"""
sql = "SELECT COUNT(*) as cnt FROM semester_archives WHERE semester_id = %s"
result = await execute_one(sql, (semester_id,))
return result['cnt'] if result else 0
@staticmethod
async def associate_records_by_date_range(
semester_id: int,
start_date: str,
end_date: str
) -> Dict[str, int]:
"""按日期范围关联记录到学期"""
# 关联操行分记录created_at 为 TIMESTAMP需包含 end_date 当天)
conduct_sql = """
UPDATE conduct_records
SET semester_id = %s
WHERE semester_id IS NULL
AND created_at BETWEEN %s AND CONCAT(%s, ' 23:59:59')
"""
conduct_count = await execute_update(conduct_sql, (semester_id, start_date, end_date))
# 关联考勤记录
attendance_sql = """
UPDATE attendance_records
SET semester_id = %s
WHERE semester_id IS NULL
AND `date` BETWEEN %s AND %s
"""
attendance_count = await execute_update(attendance_sql, (semester_id, start_date, end_date))
return {"conduct": conduct_count, "attendance": attendance_count}
class SemesterArchiveModel:
"""学期归档快照数据模型"""
@staticmethod
async def batch_create(archives_data: List[Dict]) -> int:
"""批量创建归档快照"""
if not archives_data:
return 0
sql = """
INSERT INTO semester_archives
(semester_id, student_id, student_no, student_name, final_points, rank_position, total_students,
attendance_present, attendance_absent, attendance_late, attendance_leave,
homework_submitted, homework_not_submitted, homework_late)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
params_list = [
(
a['semester_id'], a['student_id'], a['student_no'],
a['student_name'], a['final_points'],
a.get('rank_position', 0), a.get('total_students', 0),
a.get('attendance_present', 0), a.get('attendance_absent', 0),
a.get('attendance_late', 0), a.get('attendance_leave', 0),
a.get('homework_submitted', 0), a.get('homework_not_submitted', 0),
a.get('homework_late', 0)
)
for a in archives_data
]
return await execute_many(sql, params_list)
@staticmethod
async def delete_by_semester(semester_id: int) -> int:
"""删除指定学期的所有归档数据(用于归档操作的幂等性)"""
sql = "DELETE FROM semester_archives WHERE semester_id = %s"
return await execute_update(sql, (semester_id,))
@staticmethod
async def get_by_semester(semester_id: int) -> List[Dict[str, Any]]:
"""获取学期的归档数据"""
sql = """
SELECT archive_id, semester_id, student_id, student_no,
student_name, final_points, rank_position, total_students,
attendance_present, attendance_absent, attendance_late, attendance_leave,
homework_submitted, homework_not_submitted, homework_late, archived_at
FROM semester_archives
WHERE semester_id = %s
ORDER BY rank_position ASC
"""
return await execute_query(sql, (semester_id,))
@staticmethod
async def get_by_semester_and_student(semester_id: int, student_id: int) -> Optional[Dict[str, Any]]:
"""获取指定学期指定学生的归档数据"""
sql = """
SELECT archive_id, semester_id, student_id, student_no,
student_name, final_points, rank_position, total_students, archived_at
FROM semester_archives
WHERE semester_id = %s AND student_id = %s
"""
return await execute_one(sql, (semester_id, student_id))
@staticmethod
async def get_by_student(student_id: int) -> List[Dict[str, Any]]:
"""获取学生在所有已归档学期的数据"""
sql = """
SELECT sa.archive_id, sa.semester_id, sa.student_id, sa.student_no,
sa.student_name, sa.final_points, sa.rank_position,
sa.total_students, sa.attendance_present, sa.attendance_absent,
sa.attendance_late, sa.attendance_leave,
sa.homework_submitted, sa.homework_not_submitted, sa.homework_late,
sa.archived_at,
s.semester_name, s.start_date, s.end_date
FROM semester_archives sa
JOIN semesters s ON sa.semester_id = s.semester_id
WHERE sa.student_id = %s
ORDER BY sa.archived_at DESC
"""
return await execute_query(sql, (student_id,))