优化考勤记录
This commit is contained in:
@@ -20,7 +20,7 @@ class AttendanceModel:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_student_records(student_id: int, month: str = None) -> List[Dict[str, Any]]:
|
async def get_student_records(student_id: int, month: str = None) -> List[Dict[str, Any]]:
|
||||||
sql = """
|
sql = """
|
||||||
SELECT attendance_id, date, status, reason, deduction_applied, created_at
|
SELECT attendance_id, date, slot, status, reason, deduction_applied, created_at
|
||||||
FROM attendance_records
|
FROM attendance_records
|
||||||
WHERE student_id = %s
|
WHERE student_id = %s
|
||||||
"""
|
"""
|
||||||
@@ -37,7 +37,8 @@ class AttendanceModel:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_class_records(
|
async def get_class_records(
|
||||||
date: str = None,
|
date: str = None,
|
||||||
student_id: int = None
|
student_id: int = None,
|
||||||
|
slot: str = None
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
sql = """
|
sql = """
|
||||||
SELECT ar.*, s.name as student_name, s.student_no
|
SELECT ar.*, s.name as student_name, s.student_no
|
||||||
@@ -55,6 +56,10 @@ class AttendanceModel:
|
|||||||
sql += " AND ar.student_id = %s"
|
sql += " AND ar.student_id = %s"
|
||||||
params.append(student_id)
|
params.append(student_id)
|
||||||
|
|
||||||
|
if slot:
|
||||||
|
sql += " AND ar.slot = %s"
|
||||||
|
params.append(slot)
|
||||||
|
|
||||||
sql += " ORDER BY ar.date DESC, s.student_no"
|
sql += " ORDER BY ar.date DESC, s.student_no"
|
||||||
|
|
||||||
return await execute_query(sql, tuple(params))
|
return await execute_query(sql, tuple(params))
|
||||||
@@ -65,30 +70,31 @@ class AttendanceModel:
|
|||||||
date: str,
|
date: str,
|
||||||
status: str,
|
status: str,
|
||||||
reason: str = None,
|
reason: str = None,
|
||||||
recorder_id: int = None
|
recorder_id: int = None,
|
||||||
|
slot: str = 'morning'
|
||||||
) -> int:
|
) -> int:
|
||||||
# 检查是否已存在当天记录
|
# 检查是否已存在当天同时段记录
|
||||||
existing = await execute_one(
|
existing = await execute_one(
|
||||||
"SELECT attendance_id FROM attendance_records WHERE student_id = %s AND date = %s",
|
"SELECT attendance_id FROM attendance_records WHERE student_id = %s AND date = %s AND slot = %s",
|
||||||
(student_id, date)
|
(student_id, date, slot)
|
||||||
)
|
)
|
||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
# 更新已有记录
|
# 更新已有记录
|
||||||
sql = """
|
sql = """
|
||||||
UPDATE attendance_records
|
UPDATE attendance_records
|
||||||
SET status = %s, reason = %s, recorder_id = %s
|
SET status = %s, reason = %s, recorder_id = %s
|
||||||
WHERE student_id = %s AND date = %s
|
WHERE student_id = %s AND date = %s AND slot = %s
|
||||||
"""
|
"""
|
||||||
await execute_update(sql, (status, reason, recorder_id, student_id, date))
|
await execute_update(sql, (status, reason, recorder_id, student_id, date, slot))
|
||||||
return existing["attendance_id"]
|
return existing["attendance_id"]
|
||||||
else:
|
else:
|
||||||
# 插入新记录
|
# 插入新记录
|
||||||
sql = """
|
sql = """
|
||||||
INSERT INTO attendance_records (student_id, date, status, reason, recorder_id)
|
INSERT INTO attendance_records (student_id, date, slot, status, reason, recorder_id)
|
||||||
VALUES (%s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s)
|
||||||
"""
|
"""
|
||||||
return await execute_insert(sql, (student_id, date, status, reason, recorder_id))
|
return await execute_insert(sql, (student_id, date, slot, status, reason, recorder_id))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def mark_deduction_applied(attendance_id: int) -> bool:
|
async def mark_deduction_applied(attendance_id: int) -> bool:
|
||||||
|
|||||||
@@ -391,7 +391,8 @@ async def add_attendance(request: Request, req: AddAttendanceRequest):
|
|||||||
reason=req.reason,
|
reason=req.reason,
|
||||||
apply_deduction=req.apply_deduction,
|
apply_deduction=req.apply_deduction,
|
||||||
recorder_id=user["user_id"],
|
recorder_id=user["user_id"],
|
||||||
custom_deduction=req.custom_deduction
|
custom_deduction=req.custom_deduction,
|
||||||
|
slot=req.slot
|
||||||
)
|
)
|
||||||
if result["success"]:
|
if result["success"]:
|
||||||
await LogService.write_operation_log(
|
await LogService.write_operation_log(
|
||||||
@@ -410,14 +411,16 @@ async def add_attendance(request: Request, req: AddAttendanceRequest):
|
|||||||
async def get_attendance_records(
|
async def get_attendance_records(
|
||||||
request: Request,
|
request: Request,
|
||||||
date: Optional[str] = None,
|
date: Optional[str] = None,
|
||||||
student_id: Optional[int] = None
|
student_id: Optional[int] = None,
|
||||||
|
slot: Optional[str] = None
|
||||||
):
|
):
|
||||||
"""获取考勤记录"""
|
"""获取考勤记录"""
|
||||||
user = await get_current_user(request)
|
user = await get_current_user(request)
|
||||||
result = await AttendanceService.get_records(
|
result = await AttendanceService.get_records(
|
||||||
user_id=user["user_id"],
|
user_id=user["user_id"],
|
||||||
date=date,
|
date=date,
|
||||||
student_id=student_id
|
student_id=student_id,
|
||||||
|
slot=slot
|
||||||
)
|
)
|
||||||
return success_response(data=result)
|
return success_response(data=result)
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ from datetime import date, datetime
|
|||||||
|
|
||||||
class AddPointsRequest(BaseModel):
|
class AddPointsRequest(BaseModel):
|
||||||
"""加减分请求"""
|
"""加减分请求"""
|
||||||
student_ids: List[int] = Field(..., description="学生ID列表")
|
student_ids: List[int] = Field(..., min_length=1, max_length=200, description="学生ID列表")
|
||||||
points_change: int = Field(..., description="分数变动")
|
points_change: int = Field(..., gt=-100, lt=100, description="分数变动")
|
||||||
reason: str = Field(..., min_length=1, max_length=255, description="原因")
|
reason: str = Field(..., min_length=1, max_length=255, description="原因")
|
||||||
|
|
||||||
|
|
||||||
@@ -48,11 +48,11 @@ class ImportResult(BaseModel):
|
|||||||
|
|
||||||
class AddAdminRequest(BaseModel):
|
class AddAdminRequest(BaseModel):
|
||||||
"""添加管理员请求"""
|
"""添加管理员请求"""
|
||||||
username: str = Field(..., min_length=1, max_length=50, description="登录账号")
|
username: str = Field(..., min_length=2, max_length=50, pattern=r'^[a-zA-Z0-9_\u4e00-\u9fa]+$', description="登录账号")
|
||||||
real_name: str = Field(..., min_length=1, max_length=50, description="真实姓名")
|
real_name: str = Field(..., min_length=1, max_length=50, description="真实姓名")
|
||||||
password: Optional[str] = Field(None, description="密码(不填则自动生成)")
|
password: Optional[str] = Field(None, min_length=6, max_length=50, description="密码(不填则自动生成)")
|
||||||
role_type: str = Field(..., description="角色类型")
|
role_type: str = Field(..., pattern=r'^(班长|学习委员|考勤委员|劳动委员|志愿委员)$', description="角色类型")
|
||||||
subject_id: Optional[int] = Field(None, description="科目ID(科代表需要)")
|
subject_id: Optional[int] = Field(None, gt=0, description="科目ID(科代表需要)")
|
||||||
|
|
||||||
|
|
||||||
class AddAdminResponse(BaseModel):
|
class AddAdminResponse(BaseModel):
|
||||||
@@ -65,34 +65,35 @@ class AddAdminResponse(BaseModel):
|
|||||||
|
|
||||||
class UpdateHomeworkStatusRequest(BaseModel):
|
class UpdateHomeworkStatusRequest(BaseModel):
|
||||||
"""更新作业状态请求"""
|
"""更新作业状态请求"""
|
||||||
submission_id: int
|
submission_id: int = Field(..., gt=0, description="提交记录ID")
|
||||||
status: str
|
status: str = Field(..., pattern=r'^(submitted|not_submitted|late|excused)$', description="状态")
|
||||||
comments: Optional[str] = None
|
comments: Optional[str] = Field(None, max_length=500, description="评语")
|
||||||
apply_deduction: bool = False
|
apply_deduction: bool = False
|
||||||
|
|
||||||
|
|
||||||
class AddStudentRequest(BaseModel):
|
class AddStudentRequest(BaseModel):
|
||||||
"""新增学生请求"""
|
"""新增学生请求"""
|
||||||
student_no: str = Field(..., min_length=1, max_length=20, description="学号")
|
student_no: str = Field(..., min_length=1, max_length=20, pattern=r'^[a-zA-Z0-9]+$', description="学号")
|
||||||
name: str = Field(..., min_length=1, max_length=50, description="姓名")
|
name: str = Field(..., min_length=1, max_length=50, description="姓名")
|
||||||
parent_phone: Optional[str] = Field(None, max_length=11, description="家长手机号")
|
parent_phone: Optional[str] = Field(None, max_length=11, pattern=r'^\d{0,11}$', description="家长手机号")
|
||||||
|
|
||||||
|
|
||||||
class AddAttendanceRequest(BaseModel):
|
class AddAttendanceRequest(BaseModel):
|
||||||
"""添加考勤请求"""
|
"""添加考勤请求"""
|
||||||
student_id: int
|
student_id: int = Field(..., gt=0, description="学生ID")
|
||||||
date: date
|
date: date
|
||||||
status: str
|
slot: str = Field(default="morning", pattern=r'^(morning|afternoon|evening)$', description="时段")
|
||||||
reason: Optional[str] = None
|
status: str = Field(..., pattern=r'^(present|absent|late|leave)$', description="考勤状态")
|
||||||
|
reason: Optional[str] = Field(None, max_length=255, description="原因")
|
||||||
apply_deduction: bool = True
|
apply_deduction: bool = True
|
||||||
custom_deduction: Optional[int] = Field(default=None, gt=0, description="自定义扣分值")
|
custom_deduction: Optional[int] = Field(default=None, gt=0, le=20, description="自定义扣分值")
|
||||||
|
|
||||||
|
|
||||||
class UpdateAdminRequest(BaseModel):
|
class UpdateAdminRequest(BaseModel):
|
||||||
"""更新管理员请求"""
|
"""更新管理员请求"""
|
||||||
user_id: int = Field(..., description="用户ID")
|
user_id: int = Field(..., gt=0, description="用户ID")
|
||||||
real_name: str = Field(..., min_length=1, max_length=50, description="真实姓名")
|
real_name: str = Field(..., min_length=1, max_length=50, description="真实姓名")
|
||||||
role_type: str = Field(..., description="角色类型")
|
role_type: str = Field(..., pattern=r'^(班长|学习委员|考勤委员|劳动委员|志愿委员)$', description="角色类型")
|
||||||
|
|
||||||
|
|
||||||
class DeleteAdminRequest(BaseModel):
|
class DeleteAdminRequest(BaseModel):
|
||||||
@@ -108,4 +109,4 @@ class ResetPasswordRequest(BaseModel):
|
|||||||
class UpdateStudentRequest(BaseModel):
|
class UpdateStudentRequest(BaseModel):
|
||||||
"""更新学生请求"""
|
"""更新学生请求"""
|
||||||
name: Optional[str] = Field(None, min_length=1, max_length=50, description="姓名")
|
name: Optional[str] = Field(None, min_length=1, max_length=50, description="姓名")
|
||||||
parent_phone: Optional[str] = Field(None, max_length=20, description="家长手机号")
|
parent_phone: Optional[str] = Field(None, max_length=11, pattern=r'^\d{0,11}$', description="家长手机号")
|
||||||
@@ -41,16 +41,29 @@ class AttendanceService:
|
|||||||
reason: Optional[str],
|
reason: Optional[str],
|
||||||
apply_deduction: bool,
|
apply_deduction: bool,
|
||||||
recorder_id: int,
|
recorder_id: int,
|
||||||
custom_deduction: Optional[int] = None
|
custom_deduction: Optional[int] = None,
|
||||||
|
slot: str = 'morning'
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""添加考勤记录"""
|
"""添加考勤记录"""
|
||||||
|
# 校验时段
|
||||||
|
if slot not in ('morning', 'afternoon', 'evening'):
|
||||||
|
return {"success": False, "message": "无效的考勤时段"}
|
||||||
|
# 校验状态
|
||||||
|
if status not in ('present', 'absent', 'late', 'leave'):
|
||||||
|
return {"success": False, "message": "无效的考勤状态"}
|
||||||
|
# 校验自定义扣分范围
|
||||||
|
if custom_deduction is not None and (custom_deduction < 1 or custom_deduction > 20):
|
||||||
|
return {"success": False, "message": "自定义扣分必须在1-20之间"}
|
||||||
|
|
||||||
# 检查权限
|
# 检查权限
|
||||||
role = await PermissionChecker.get_user_role(recorder_id)
|
role = await PermissionChecker.get_user_role(recorder_id)
|
||||||
if role not in ["班主任", "考勤委员"]:
|
if role not in ["班主任", "考勤委员"]:
|
||||||
return {"success": False, "message": "无权进行此操作"}
|
return {"success": False, "message": "无权进行此操作"}
|
||||||
|
|
||||||
# 检查是否同班级
|
# 考勤委员扣分上限
|
||||||
# 单班级系统,管理员均可操作
|
if role == "考勤委员" and apply_deduction and status in ["absent", "late"]:
|
||||||
|
if custom_deduction is not None and custom_deduction > settings.ATTENDANCE_REP_MAX_POINTS:
|
||||||
|
return {"success": False, "message": f"考勤委员单次扣分上限为{settings.ATTENDANCE_REP_MAX_POINTS}分"}
|
||||||
|
|
||||||
# 添加考勤记录
|
# 添加考勤记录
|
||||||
attendance_id = await AttendanceModel.create_record(
|
attendance_id = await AttendanceModel.create_record(
|
||||||
@@ -58,7 +71,8 @@ class AttendanceService:
|
|||||||
date=date,
|
date=date,
|
||||||
status=status,
|
status=status,
|
||||||
reason=reason,
|
reason=reason,
|
||||||
recorder_id=recorder_id
|
recorder_id=recorder_id,
|
||||||
|
slot=slot
|
||||||
)
|
)
|
||||||
|
|
||||||
if not attendance_id:
|
if not attendance_id:
|
||||||
@@ -107,7 +121,8 @@ class AttendanceService:
|
|||||||
async def get_records(
|
async def get_records(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
date: Optional[str] = None,
|
date: Optional[str] = None,
|
||||||
student_id: Optional[int] = None
|
student_id: Optional[int] = None,
|
||||||
|
slot: Optional[str] = None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""获取考勤记录"""
|
"""获取考勤记录"""
|
||||||
role = await PermissionChecker.get_user_role(user_id)
|
role = await PermissionChecker.get_user_role(user_id)
|
||||||
@@ -115,7 +130,8 @@ class AttendanceService:
|
|||||||
if role in ["班主任", "考勤委员"]:
|
if role in ["班主任", "考勤委员"]:
|
||||||
records = await AttendanceModel.get_class_records(
|
records = await AttendanceModel.get_class_records(
|
||||||
date=date,
|
date=date,
|
||||||
student_id=student_id
|
student_id=student_id,
|
||||||
|
slot=slot
|
||||||
)
|
)
|
||||||
elif student_id:
|
elif student_id:
|
||||||
# 管理员可查看指定学生
|
# 管理员可查看指定学生
|
||||||
|
|||||||
@@ -33,12 +33,18 @@ class ConductService:
|
|||||||
recorder_id: int,
|
recorder_id: int,
|
||||||
recorder_name: str
|
recorder_name: str
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""批量加减分"""
|
||||||
批量加减分
|
# 输入校验
|
||||||
"""
|
if not student_ids or len(student_ids) > 200:
|
||||||
|
return {"success": False, "message": "学生数量需在1-200之间"}
|
||||||
|
if not reason or not reason.strip() or len(reason) > 255:
|
||||||
|
return {"success": False, "message": "原因不能为空且不超过255字符"}
|
||||||
|
|
||||||
# 验证分值
|
# 验证分值
|
||||||
if points_change == 0:
|
if points_change == 0:
|
||||||
return {"success": False, "message": "分值不能为0"}
|
return {"success": False, "message": "分值不能为0"}
|
||||||
|
if abs(points_change) > 100:
|
||||||
|
return {"success": False, "message": "单次加减分不能超过100分"}
|
||||||
|
|
||||||
# 获取操作人角色
|
# 获取操作人角色
|
||||||
role = await PermissionChecker.get_user_role(recorder_id)
|
role = await PermissionChecker.get_user_role(recorder_id)
|
||||||
@@ -119,6 +125,9 @@ class ConductService:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def revoke_record(record_id: int, revoker_id: int) -> Dict[str, Any]:
|
async def revoke_record(record_id: int, revoker_id: int) -> Dict[str, Any]:
|
||||||
"""撤销扣分记录"""
|
"""撤销扣分记录"""
|
||||||
|
if not record_id or record_id <= 0:
|
||||||
|
return {"success": False, "message": "无效的记录ID"}
|
||||||
|
|
||||||
# 检查权限
|
# 检查权限
|
||||||
can_revoke = await PermissionChecker.check_can_revoke(revoker_id, record_id)
|
can_revoke = await PermissionChecker.check_can_revoke(revoker_id, record_id)
|
||||||
if not can_revoke:
|
if not can_revoke:
|
||||||
@@ -155,6 +164,9 @@ class ConductService:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def restore_record(record_id: int, restorer_id: int) -> Dict[str, Any]:
|
async def restore_record(record_id: int, restorer_id: int) -> Dict[str, Any]:
|
||||||
"""反撤销(恢复)已撤销的记录"""
|
"""反撤销(恢复)已撤销的记录"""
|
||||||
|
if not record_id or record_id <= 0:
|
||||||
|
return {"success": False, "message": "无效的记录ID"}
|
||||||
|
|
||||||
# 检查权限:只有班主任可以反撤销
|
# 检查权限:只有班主任可以反撤销
|
||||||
role = await PermissionChecker.get_user_role(restorer_id)
|
role = await PermissionChecker.get_user_role(restorer_id)
|
||||||
if role != "班主任":
|
if role != "班主任":
|
||||||
@@ -207,6 +219,8 @@ class ConductService:
|
|||||||
end_date = None
|
end_date = None
|
||||||
if related_type == "":
|
if related_type == "":
|
||||||
related_type = None
|
related_type = None
|
||||||
|
if related_type and related_type not in ('manual', 'homework', 'attendance'):
|
||||||
|
return {"records": [], "page": page, "page_size": page_size, "total": 0, "total_pages": 0}
|
||||||
|
|
||||||
role = await PermissionChecker.get_user_role(user_id)
|
role = await PermissionChecker.get_user_role(user_id)
|
||||||
offset = (page - 1) * page_size
|
offset = (page - 1) * page_size
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ include __DIR__ . '/../includes/header.php';
|
|||||||
<div class="form-group" style="margin:0">
|
<div class="form-group" style="margin:0">
|
||||||
<label>时段</label>
|
<label>时段</label>
|
||||||
<select id="attendanceSlot">
|
<select id="attendanceSlot">
|
||||||
<option value="morning">早 8:15</option>
|
<option value="morning">早上 7:15</option>
|
||||||
<option value="afternoon">下午 14:00</option>
|
<option value="afternoon">中午 14:00</option>
|
||||||
<option value="evening">晚 19:30</option>
|
<option value="evening">晚修 19:30</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-group">
|
<div class="status-group">
|
||||||
@@ -124,7 +124,7 @@ function renderStudentGrid() {
|
|||||||
const currentSlot = document.getElementById('attendanceSlot').value;
|
const currentSlot = document.getElementById('attendanceSlot').value;
|
||||||
let html = '';
|
let html = '';
|
||||||
studentsData.forEach(student => {
|
studentsData.forEach(student => {
|
||||||
const hasRecord = existingRecords.some(r => r.student_id === student.student_id && (!r.slot || r.slot === currentSlot));
|
const hasRecord = existingRecords.some(r => r.student_id === student.student_id && r.slot === currentSlot);
|
||||||
html += `<div class="student-cell${hasRecord ? ' has-record' : ''}"
|
html += `<div class="student-cell${hasRecord ? ' has-record' : ''}"
|
||||||
data-id="${student.student_id}"
|
data-id="${student.student_id}"
|
||||||
data-name="${escapeHtml(student.name)}"
|
data-name="${escapeHtml(student.name)}"
|
||||||
@@ -178,17 +178,19 @@ async function submitAttendance() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const date = document.getElementById('attendanceDate').value;
|
const date = document.getElementById('attendanceDate').value;
|
||||||
|
const slot = document.getElementById('attendanceSlot').value;
|
||||||
const reason = document.getElementById('attendanceReason').value;
|
const reason = document.getElementById('attendanceReason').value;
|
||||||
const customDeduction = document.getElementById('customDeduction').value;
|
const customDeduction = document.getElementById('customDeduction').value;
|
||||||
const customDeductionValue = customDeduction ? parseInt(customDeduction) : null;
|
const customDeductionValue = customDeduction ? parseInt(customDeduction) : null;
|
||||||
|
|
||||||
// 批量提交(不再检查已有记录,允许同一学生同一天多次考勤)
|
// 批量提交
|
||||||
const promises = [];
|
const promises = [];
|
||||||
selectedCells.forEach(cell => {
|
selectedCells.forEach(cell => {
|
||||||
const studentId = parseInt(cell.dataset.id);
|
const studentId = parseInt(cell.dataset.id);
|
||||||
const payload = {
|
const payload = {
|
||||||
student_id: studentId,
|
student_id: studentId,
|
||||||
date: date,
|
date: date,
|
||||||
|
slot: slot,
|
||||||
status: currentStatus,
|
status: currentStatus,
|
||||||
reason: reason,
|
reason: reason,
|
||||||
apply_deduction: true
|
apply_deduction: true
|
||||||
|
|||||||
30
sql/init.sql
30
sql/init.sql
@@ -267,6 +267,36 @@ PREPARE stmt FROM @sql;
|
|||||||
EXECUTE stmt;
|
EXECUTE stmt;
|
||||||
DEALLOCATE PREPARE stmt;
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- attendance_records 表:添加 slot 字段(如不存在)
|
||||||
|
SET @column_exists = (
|
||||||
|
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = 'classmanagerdb'
|
||||||
|
AND TABLE_NAME = 'attendance_records'
|
||||||
|
AND COLUMN_NAME = 'slot'
|
||||||
|
);
|
||||||
|
SET @sql = IF(@column_exists = 0,
|
||||||
|
'ALTER TABLE `attendance_records` ADD COLUMN `slot` VARCHAR(20) DEFAULT ''morning'' COMMENT ''时段: morning/afternoon/evening'' AFTER `date`',
|
||||||
|
'SELECT ''attendance_records.slot already exists'' AS message'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- 删除旧唯一键并添加新唯一键(含slot)
|
||||||
|
SET @uk_exists = (
|
||||||
|
SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
|
||||||
|
WHERE TABLE_SCHEMA = 'classmanagerdb'
|
||||||
|
AND TABLE_NAME = 'attendance_records'
|
||||||
|
AND CONSTRAINT_NAME = 'uk_student_date'
|
||||||
|
);
|
||||||
|
SET @sql = IF(@uk_exists > 0,
|
||||||
|
'ALTER TABLE `attendance_records` DROP INDEX `uk_student_date`, ADD UNIQUE KEY `uk_student_date_slot` (`student_id`, `date`, `slot`)',
|
||||||
|
'SELECT ''uk_student_date does not exist, skipping'' AS message'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
-- 迁移:semester_archives 表新增 attendance_present 字段
|
-- 迁移:semester_archives 表新增 attendance_present 字段
|
||||||
SET @column_exists = (
|
SET @column_exists = (
|
||||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
|||||||
Reference in New Issue
Block a user