# =========================================== # 班级操行分管理系统 - 学期数据模型 # # 开发者: 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,))