v1.2版本更新发布
This commit is contained in:
@@ -52,17 +52,25 @@ class ConductService:
|
||||
if points_change > settings.MONITOR_MAX_ADD or points_change < settings.MONITOR_MAX_SUBTRACT:
|
||||
return {"success": False, "message": f"班长单次只能加减{settings.MONITOR_MAX_ADD}分以内"}
|
||||
elif role == "劳动委员":
|
||||
# 劳动委员固定 ±1分
|
||||
if points_change not in [settings.LABOR_POINTS_ADD, settings.LABOR_POINTS_SUBTRACT]:
|
||||
return {"success": False, "message": "劳动委员只能进行±1分操作"}
|
||||
# 劳动委员可加减分,±LABOR_REP_MAX_POINTS以内
|
||||
if abs(points_change) > settings.LABOR_REP_MAX_POINTS:
|
||||
return {"success": False, "message": f"劳动委员单次只能加减{settings.LABOR_REP_MAX_POINTS}分以内"}
|
||||
elif role == "志愿委员":
|
||||
# 志愿委员只能加分,不限制正分上限
|
||||
# 志愿委员只能加分,上限VOLUNTEER_REP_MAX_POINTS
|
||||
if points_change < 0:
|
||||
return {"success": False, "message": "志愿委员只能加分"}
|
||||
elif role in ["学习委员", "考勤委员"]:
|
||||
# 学习委员和考勤委员只能扣分
|
||||
if points_change > settings.VOLUNTEER_REP_MAX_POINTS:
|
||||
return {"success": False, "message": f"志愿委员单次最多加{settings.VOLUNTEER_REP_MAX_POINTS}分"}
|
||||
elif role == "学习委员":
|
||||
# 学习委员可加减分,±STUDY_COMMISSIONER_MAX_POINTS以内
|
||||
if abs(points_change) > settings.STUDY_COMMISSIONER_MAX_POINTS:
|
||||
return {"success": False, "message": f"学习委员单次只能加减{settings.STUDY_COMMISSIONER_MAX_POINTS}分以内"}
|
||||
elif role == "考勤委员":
|
||||
# 考勤委员只能扣分,上限ATTENDANCE_REP_MAX_POINTS
|
||||
if points_change > 0:
|
||||
return {"success": False, "message": "该角色只能进行扣分操作"}
|
||||
return {"success": False, "message": "考勤委员只能进行扣分操作"}
|
||||
if abs(points_change) > settings.ATTENDANCE_REP_MAX_POINTS:
|
||||
return {"success": False, "message": f"考勤委员单次最多扣{settings.ATTENDANCE_REP_MAX_POINTS}分"}
|
||||
else:
|
||||
return {"success": False, "message": "无权进行此操作"}
|
||||
|
||||
@@ -121,6 +129,9 @@ class ConductService:
|
||||
if not record:
|
||||
return {"success": False, "message": "记录不存在"}
|
||||
|
||||
# 归档后班主任仍可撤销/修改记录(任务需求#8)
|
||||
# 归档操作本身不可逆,但归档数据可由班主任修改
|
||||
|
||||
# 撤销记录
|
||||
result = await ConductModel.revoke_record(record_id, revoker_id)
|
||||
|
||||
@@ -128,7 +139,16 @@ class ConductService:
|
||||
# 反向恢复学生总分
|
||||
await StudentModel.update_total_points(record["student_id"], -record["points_change"])
|
||||
logger.info(f"用户[{revoker_id}] 撤销了记录[{record_id}]")
|
||||
return {"success": True, "message": "撤销成功"}
|
||||
return {
|
||||
"success": True,
|
||||
"message": "撤销成功",
|
||||
"record": {
|
||||
"student_id": record["student_id"],
|
||||
"recorder_name": record.get("recorder_name", "未知"),
|
||||
"points_change": record["points_change"],
|
||||
"reason": record.get("reason", "")
|
||||
}
|
||||
}
|
||||
else:
|
||||
return {"success": False, "message": "撤销失败"}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
# 版权所有 © Sea Network Technology Studio
|
||||
# ===========================================
|
||||
|
||||
import math
|
||||
from typing import Dict, Any, Optional, List
|
||||
|
||||
from models.user import UserModel
|
||||
@@ -93,22 +94,27 @@ class ParentService:
|
||||
|
||||
# 获取全班排名
|
||||
ranking = await StudentModel.get_ranking(limit=1000)
|
||||
total_students = await StudentModel.get_total_count()
|
||||
|
||||
# 查找当前学生排名
|
||||
student_rank = None
|
||||
total_students = 0
|
||||
for r in ranking:
|
||||
total_students += 1
|
||||
if r["student_id"] == user["student_id"]:
|
||||
student_rank = r["rank"]
|
||||
|
||||
# 计算百分比排名
|
||||
percentile = None
|
||||
if student_rank and total_students and total_students > 0:
|
||||
percentile = math.ceil(student_rank / total_students * 100)
|
||||
|
||||
return {
|
||||
"student_id": student["student_id"],
|
||||
"student_name": student["name"],
|
||||
"student_no": student["student_no"],
|
||||
"total_points": student["total_points"],
|
||||
"rank": student_rank,
|
||||
"total_students": total_students
|
||||
"total_students": total_students,
|
||||
"percentile": percentile
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
||||
244
backend/services/semester_service.py
Normal file
244
backend/services/semester_service.py
Normal file
@@ -0,0 +1,244 @@
|
||||
# ===========================================
|
||||
# 班级操行分管理系统 - 学期服务
|
||||
#
|
||||
# 开发者: Canglan
|
||||
# 联系方式: admin@sea-studio.top
|
||||
# 版权归属: Sea Network Technology Studio
|
||||
# 许可证: MIT License
|
||||
#
|
||||
# 版权所有 © Sea Network Technology Studio
|
||||
# ===========================================
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from models.semester import SemesterModel, SemesterArchiveModel
|
||||
from models.student import StudentModel
|
||||
from middleware.permission import PermissionChecker
|
||||
from config import settings
|
||||
from utils.logger import get_logger
|
||||
from utils.database import execute_query
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class SemesterService:
|
||||
"""学期管理服务"""
|
||||
|
||||
@staticmethod
|
||||
async def list_semesters() -> Dict[str, Any]:
|
||||
"""获取学期列表"""
|
||||
try:
|
||||
semesters = await SemesterModel.get_all()
|
||||
return {
|
||||
"success": True,
|
||||
"semesters": semesters
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"获取学期列表失败: {e}")
|
||||
return {"success": False, "message": f"获取学期列表失败: {str(e)}"}
|
||||
|
||||
@staticmethod
|
||||
async def create_semester(
|
||||
semester_name: str,
|
||||
start_date: str = None,
|
||||
end_date: str = None,
|
||||
operator_id: int = None
|
||||
) -> Dict[str, Any]:
|
||||
"""创建新学期"""
|
||||
if not semester_name or not semester_name.strip():
|
||||
return {"success": False, "message": "学期名称不能为空"}
|
||||
|
||||
try:
|
||||
# 自动将之前的活跃学期设为非活跃
|
||||
await SemesterModel.deactivate_all()
|
||||
|
||||
# 创建新学期并自动设为活跃
|
||||
semester_id = await SemesterModel.create(
|
||||
semester_name=semester_name.strip(),
|
||||
start_date=start_date,
|
||||
end_date=end_date
|
||||
)
|
||||
|
||||
# 设为活跃学期
|
||||
await SemesterModel.activate(semester_id)
|
||||
|
||||
logger.info(f"用户[{operator_id}] 创建了新学期: {semester_name}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "学期创建成功",
|
||||
"semester_id": semester_id
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"创建学期失败: {e}")
|
||||
return {"success": False, "message": f"创建学期失败: {str(e)}"}
|
||||
|
||||
@staticmethod
|
||||
async def activate_semester(
|
||||
semester_id: int,
|
||||
operator_id: int = None
|
||||
) -> Dict[str, Any]:
|
||||
"""设为当前活跃学期"""
|
||||
try:
|
||||
# 检查学期是否存在
|
||||
semester = await SemesterModel.get_by_id(semester_id)
|
||||
if not semester:
|
||||
return {"success": False, "message": "学期不存在"}
|
||||
|
||||
# 已归档的学期不能激活
|
||||
if semester['is_archived']:
|
||||
return {"success": False, "message": "已归档的学期不能设为当前学期"}
|
||||
|
||||
# 自动将之前的活跃学期设为非活跃
|
||||
await SemesterModel.deactivate_all()
|
||||
|
||||
# 设为活跃
|
||||
result = await SemesterModel.activate(semester_id)
|
||||
|
||||
if result:
|
||||
logger.info(f"用户[{operator_id}] 激活了学期: {semester['semester_name']}")
|
||||
return {"success": True, "message": "已设为当前学期"}
|
||||
else:
|
||||
return {"success": False, "message": "激活失败"}
|
||||
except Exception as e:
|
||||
logger.error(f"激活学期失败: {e}")
|
||||
return {"success": False, "message": f"激活学期失败: {str(e)}"}
|
||||
|
||||
@staticmethod
|
||||
async def archive_semester(
|
||||
semester_id: int,
|
||||
operator_id: int = None
|
||||
) -> Dict[str, Any]:
|
||||
"""归档学期"""
|
||||
try:
|
||||
# 检查学期是否存在
|
||||
semester = await SemesterModel.get_by_id(semester_id)
|
||||
if not semester:
|
||||
return {"success": False, "message": "学期不存在"}
|
||||
|
||||
# 已归档的不能重复归档
|
||||
if semester['is_archived']:
|
||||
return {"success": False, "message": "该学期已归档"}
|
||||
|
||||
# 获取所有活跃学生及其当前分数
|
||||
students = await StudentModel.get_all(include_disabled=False)
|
||||
if not students:
|
||||
return {"success": False, "message": "没有可归档的学生数据"}
|
||||
|
||||
total_students = len(students)
|
||||
|
||||
# 按分数降序排列以计算排名
|
||||
sorted_students = sorted(students, key=lambda s: s['total_points'], reverse=True)
|
||||
|
||||
# 构建归档快照数据
|
||||
archives_data = []
|
||||
for rank, student in enumerate(sorted_students, 1):
|
||||
archives_data.append({
|
||||
'semester_id': semester_id,
|
||||
'student_id': student['student_id'],
|
||||
'student_no': student['student_no'],
|
||||
'student_name': student['name'],
|
||||
'final_points': student['total_points'],
|
||||
'rank_position': rank,
|
||||
'total_students': total_students
|
||||
})
|
||||
|
||||
# 保存归档快照
|
||||
await SemesterArchiveModel.batch_create(archives_data)
|
||||
|
||||
# 标记学期为已归档
|
||||
await SemesterModel.archive(semester_id)
|
||||
|
||||
logger.info(
|
||||
f"用户[{operator_id}] 归档了学期: {semester['semester_name']}, "
|
||||
f"共 {total_students} 名学生"
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"学期归档成功,共归档 {total_students} 名学生数据"
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"归档学期失败: {e}")
|
||||
return {"success": False, "message": f"归档学期失败: {str(e)}"}
|
||||
|
||||
@staticmethod
|
||||
async def get_archive_records(
|
||||
semester_id: int,
|
||||
page: int = 1,
|
||||
page_size: int = 50
|
||||
) -> Dict[str, Any]:
|
||||
"""获取归档数据(只读)"""
|
||||
try:
|
||||
# 检查学期是否存在
|
||||
semester = await SemesterModel.get_by_id(semester_id)
|
||||
if not semester:
|
||||
return {"success": False, "message": "学期不存在"}
|
||||
|
||||
archives = await SemesterArchiveModel.get_by_semester(semester_id)
|
||||
|
||||
total = len(archives)
|
||||
offset = (page - 1) * page_size
|
||||
paged_archives = archives[offset:offset + page_size]
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": {
|
||||
"semester": {
|
||||
"semester_id": semester['semester_id'],
|
||||
"semester_name": semester['semester_name'],
|
||||
"start_date": semester.get('start_date'),
|
||||
"end_date": semester.get('end_date'),
|
||||
"is_archived": bool(semester['is_archived'])
|
||||
},
|
||||
"archives": paged_archives,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"total_pages": (total + page_size - 1) // page_size
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"获取归档数据失败: {e}")
|
||||
return {"success": False, "message": f"获取归档数据失败: {str(e)}"}
|
||||
|
||||
@staticmethod
|
||||
async def reset_student_points(initial_points: int = None) -> Dict[str, Any]:
|
||||
"""重置所有学生的操行分为初始分"""
|
||||
if initial_points is None:
|
||||
initial_points = settings.STUDENT_INITIAL_POINTS
|
||||
|
||||
try:
|
||||
from utils.database import execute_update
|
||||
sql = "UPDATE students SET total_points = %s WHERE status = 1"
|
||||
affected = await execute_update(sql, (initial_points,))
|
||||
|
||||
logger.info(f"已重置 {affected} 名学生的操行分为 {initial_points}")
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"已重置 {affected} 名学生的操行分为 {initial_points} 分",
|
||||
"affected": affected
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"重置学生分数失败: {e}")
|
||||
return {"success": False, "message": f"重置学生分数失败: {str(e)}"}
|
||||
|
||||
@staticmethod
|
||||
async def get_active_semester() -> Dict[str, Any]:
|
||||
"""获取当前活跃学期"""
|
||||
try:
|
||||
active = await SemesterModel.get_active()
|
||||
if active:
|
||||
return {
|
||||
"success": True,
|
||||
"semester": active
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": True,
|
||||
"semester": None,
|
||||
"message": "当前没有活跃学期"
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"获取活跃学期失败: {e}")
|
||||
return {"success": False, "message": f"获取活跃学期失败: {str(e)}"}
|
||||
@@ -123,9 +123,11 @@ class StudentService:
|
||||
) -> Dict[str, Any]:
|
||||
"""获取排行榜(单班级系统)"""
|
||||
ranking = await StudentModel.get_ranking(limit=limit)
|
||||
total_students = await StudentModel.get_total_count()
|
||||
|
||||
return {
|
||||
"ranking": ranking
|
||||
"ranking": ranking,
|
||||
"total_students": total_students
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user