283 lines
11 KiB
Python
283 lines
11 KiB
Python
# ===========================================
|
||
# 班级操行分管理系统 - 后端服务
|
||
#
|
||
# 开发者: Canglan
|
||
# 联系方式: admin@sea-studio.top
|
||
# 版权归属: Sea Network Technology Studio
|
||
# 许可证: MIT License
|
||
#
|
||
# 版权所有 © Sea Network Technology Studio
|
||
# ===========================================
|
||
|
||
from typing import Dict, Any, List, Optional
|
||
from datetime import datetime
|
||
|
||
from models.student import StudentModel
|
||
from models.conduct import ConductModel
|
||
from models.user import UserModel
|
||
from middleware.permission import PermissionChecker
|
||
from config import settings
|
||
from utils.logger import get_logger
|
||
|
||
logger = get_logger(__name__)
|
||
|
||
|
||
class ConductService:
|
||
"""操行分服务"""
|
||
|
||
@staticmethod
|
||
async def add_points(
|
||
student_ids: List[int],
|
||
points_change: int,
|
||
reason: str,
|
||
recorder_id: int,
|
||
recorder_name: str
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
批量加减分
|
||
"""
|
||
# 验证分值
|
||
if points_change == 0:
|
||
return {"success": False, "message": "分值不能为0"}
|
||
|
||
# 获取操作人角色
|
||
role = await PermissionChecker.get_user_role(recorder_id)
|
||
|
||
# 权限验证
|
||
if role == "班主任":
|
||
# 班主任无限制
|
||
pass
|
||
elif role == "班长":
|
||
# 班长限制 ±5分
|
||
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 == "劳动委员":
|
||
# 劳动委员可加减分,±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": "志愿委员只能加分"}
|
||
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": "考勤委员只能进行扣分操作"}
|
||
if abs(points_change) > settings.ATTENDANCE_REP_MAX_POINTS:
|
||
return {"success": False, "message": f"考勤委员单次最多扣{settings.ATTENDANCE_REP_MAX_POINTS}分"}
|
||
else:
|
||
return {"success": False, "message": "无权进行此操作"}
|
||
|
||
# 批量处理
|
||
success_count = 0
|
||
fail_count = 0
|
||
details = []
|
||
|
||
for student_id in student_ids:
|
||
try:
|
||
# 检查学生是否存在
|
||
student = await StudentModel.get_by_id(student_id)
|
||
if not student:
|
||
details.append({"student_id": student_id, "error": "学生不存在"})
|
||
fail_count += 1
|
||
continue
|
||
|
||
# 创建记录
|
||
record_id = await ConductModel.create_record(
|
||
student_id=student_id,
|
||
points_change=points_change,
|
||
reason=reason,
|
||
recorder_id=recorder_id,
|
||
recorder_name=recorder_name
|
||
)
|
||
|
||
# 更新学生总分
|
||
await StudentModel.update_total_points(student_id, points_change)
|
||
|
||
details.append({"student_id": student_id, "success": True, "record_id": record_id})
|
||
success_count += 1
|
||
|
||
logger.info(f"用户[{recorder_id}] 对学生[{student_id}] 进行 {points_change} 分操作")
|
||
|
||
except Exception as e:
|
||
details.append({"student_id": student_id, "error": str(e)})
|
||
fail_count += 1
|
||
|
||
return {
|
||
"success": fail_count == 0,
|
||
"success_count": success_count,
|
||
"fail_count": fail_count,
|
||
"details": details
|
||
}
|
||
|
||
@staticmethod
|
||
async def revoke_record(record_id: int, revoker_id: int) -> Dict[str, Any]:
|
||
"""撤销扣分记录"""
|
||
# 检查权限
|
||
can_revoke = await PermissionChecker.check_can_revoke(revoker_id, record_id)
|
||
if not can_revoke:
|
||
return {"success": False, "message": "无权撤销此记录"}
|
||
|
||
# 先获取原记录信息(用于恢复分数)
|
||
record = await ConductModel.get_record_by_id(record_id)
|
||
if not record:
|
||
return {"success": False, "message": "记录不存在"}
|
||
|
||
# 归档后班主任仍可撤销/修改记录(任务需求#8)
|
||
# 归档操作本身不可逆,但归档数据可由班主任修改
|
||
|
||
# 撤销记录
|
||
result = await ConductModel.revoke_record(record_id, revoker_id)
|
||
|
||
if result:
|
||
# 反向恢复学生总分
|
||
await StudentModel.update_total_points(record["student_id"], -record["points_change"])
|
||
logger.info(f"用户[{revoker_id}] 撤销了记录[{record_id}]")
|
||
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": "撤销失败"}
|
||
|
||
@staticmethod
|
||
async def restore_record(record_id: int, restorer_id: int) -> Dict[str, Any]:
|
||
"""反撤销(恢复)已撤销的记录"""
|
||
# 检查权限:只有班主任可以反撤销
|
||
role = await PermissionChecker.get_user_role(restorer_id)
|
||
if role != "班主任":
|
||
return {"success": False, "message": "仅班主任可反撤销记录"}
|
||
|
||
# 获取原记录信息
|
||
record = await ConductModel.get_record_by_id(record_id)
|
||
if not record:
|
||
return {"success": False, "message": "记录不存在"}
|
||
|
||
if not record.get("is_revoked"):
|
||
return {"success": False, "message": "该记录未被撤销,无需恢复"}
|
||
|
||
# 恢复记录
|
||
result = await ConductModel.restore_record(record_id, restorer_id)
|
||
|
||
if result:
|
||
# 恢复学生总分(重新加上原来的分数变动)
|
||
await StudentModel.update_total_points(record["student_id"], record["points_change"])
|
||
logger.info(f"用户[{restorer_id}] 反撤销了记录[{record_id}]")
|
||
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": "反撤销失败"}
|
||
|
||
@staticmethod
|
||
async def get_history(
|
||
user_id: int,
|
||
student_id: Optional[int] = None,
|
||
page: int = 1,
|
||
page_size: int = 20,
|
||
start_date: Optional[str] = None,
|
||
end_date: Optional[str] = None,
|
||
grouped: bool = False,
|
||
related_type: Optional[str] = None
|
||
) -> Dict[str, Any]:
|
||
"""获取历史记录"""
|
||
# 空字符串转为None
|
||
if start_date == "":
|
||
start_date = None
|
||
if end_date == "":
|
||
end_date = None
|
||
if related_type == "":
|
||
related_type = None
|
||
|
||
role = await PermissionChecker.get_user_role(user_id)
|
||
offset = (page - 1) * page_size
|
||
|
||
# 班主任/班长/志愿委员可查看全班
|
||
if role in ["班主任", "班长", "志愿委员"]:
|
||
if grouped:
|
||
return await ConductModel.get_grouped_records(
|
||
student_id=student_id,
|
||
start_date=start_date,
|
||
end_date=end_date,
|
||
page=page,
|
||
page_size=page_size
|
||
)
|
||
|
||
records = await ConductModel.get_all_records(
|
||
limit=page_size,
|
||
offset=offset,
|
||
start_date=start_date,
|
||
end_date=end_date,
|
||
student_id=student_id,
|
||
related_type=related_type
|
||
)
|
||
|
||
# 获取总数
|
||
from utils.database import execute_one
|
||
count_conditions = ["1=1"]
|
||
count_params = []
|
||
if student_id:
|
||
count_conditions.append("cr.student_id = %s")
|
||
count_params.append(student_id)
|
||
if start_date:
|
||
count_conditions.append("DATE(cr.created_at) >= %s")
|
||
count_params.append(start_date)
|
||
if end_date:
|
||
count_conditions.append("DATE(cr.created_at) <= %s")
|
||
count_params.append(end_date)
|
||
if related_type:
|
||
count_conditions.append("cr.related_type = %s")
|
||
count_params.append(related_type)
|
||
count_where = " AND ".join(count_conditions)
|
||
count_sql = f"""
|
||
SELECT COUNT(*) as total FROM conduct_records cr
|
||
JOIN students s ON cr.student_id = s.student_id
|
||
WHERE {count_where}
|
||
"""
|
||
total_result = await execute_one(count_sql, tuple(count_params))
|
||
total = total_result["total"] if total_result else 0
|
||
|
||
elif student_id:
|
||
# 管理员可查看指定学生
|
||
|
||
records = await ConductModel.get_student_records(
|
||
student_id=student_id,
|
||
limit=page_size,
|
||
offset=offset
|
||
)
|
||
total = len(await ConductModel.get_student_records(student_id, limit=10000))
|
||
else:
|
||
# 查看自己提交的记录
|
||
records = await ConductModel.get_records_by_recorder(
|
||
recorder_id=user_id,
|
||
limit=page_size,
|
||
offset=offset
|
||
)
|
||
total = len(await ConductModel.get_records_by_recorder(user_id, limit=10000))
|
||
|
||
return {
|
||
"records": records,
|
||
"page": page,
|
||
"page_size": page_size,
|
||
"total": total,
|
||
"total_pages": (total + page_size - 1) // page_size
|
||
} |