修复复盘保存失败:原地更新成绩并新增 review 专用接口。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,7 +7,15 @@ from sqlalchemy.orm import Session, joinedload
|
||||
from app.core.database import get_db
|
||||
from app.core.deps import get_current_user
|
||||
from app.models.user import ExamRecord, SubjectScore, User
|
||||
from app.schemas import ExamCreate, ExamOut, ExamUpdate, ReviewStatusEnum, ScoreOut, TrendResponse
|
||||
from app.schemas import (
|
||||
ExamCreate,
|
||||
ExamOut,
|
||||
ExamReviewUpdate,
|
||||
ExamUpdate,
|
||||
ReviewStatusEnum,
|
||||
ScoreOut,
|
||||
TrendResponse,
|
||||
)
|
||||
from app.services.score_trend import build_trend
|
||||
from app.services.student_access import get_student_for_user
|
||||
|
||||
@@ -35,7 +43,13 @@ def _parse_review_statuses(raw: str | None) -> list[ReviewStatusEnum]:
|
||||
def _serialize_review_statuses(statuses: list[ReviewStatusEnum] | None) -> str | None:
|
||||
if not statuses:
|
||||
return None
|
||||
return json.dumps([s.value for s in statuses], ensure_ascii=False)
|
||||
values: list[str] = []
|
||||
for item in statuses:
|
||||
if isinstance(item, ReviewStatusEnum):
|
||||
values.append(item.value)
|
||||
else:
|
||||
values.append(str(item))
|
||||
return json.dumps(values, ensure_ascii=False)
|
||||
|
||||
|
||||
def _score_to_out(score: SubjectScore) -> ScoreOut:
|
||||
@@ -62,18 +76,34 @@ def _exam_to_out(exam: ExamRecord) -> ExamOut:
|
||||
|
||||
|
||||
def _apply_scores(db: Session, exam: ExamRecord, scores_data):
|
||||
exam.scores.clear()
|
||||
existing_by_subject = {s.subject_id: s for s in list(exam.scores)}
|
||||
keep_subject_ids: set[int] = set()
|
||||
|
||||
for item in scores_data:
|
||||
keep_subject_ids.add(item.subject_id)
|
||||
ratio = round(item.obtained_score / item.total_score, 4)
|
||||
exam.scores.append(
|
||||
SubjectScore(
|
||||
subject_id=item.subject_id,
|
||||
total_score=item.total_score,
|
||||
obtained_score=item.obtained_score,
|
||||
ratio=ratio,
|
||||
review_statuses_json=_serialize_review_statuses(item.review_statuses),
|
||||
review_json = _serialize_review_statuses(item.review_statuses)
|
||||
existing = existing_by_subject.get(item.subject_id)
|
||||
if existing is not None:
|
||||
existing.total_score = item.total_score
|
||||
existing.obtained_score = item.obtained_score
|
||||
existing.ratio = ratio
|
||||
existing.review_statuses_json = review_json
|
||||
else:
|
||||
exam.scores.append(
|
||||
SubjectScore(
|
||||
subject_id=item.subject_id,
|
||||
total_score=item.total_score,
|
||||
obtained_score=item.obtained_score,
|
||||
ratio=ratio,
|
||||
review_statuses_json=review_json,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
for subject_id, score in existing_by_subject.items():
|
||||
if subject_id not in keep_subject_ids:
|
||||
exam.scores.remove(score)
|
||||
db.delete(score)
|
||||
|
||||
|
||||
@router.get("/students/{student_id}/exams", response_model=list[ExamOut])
|
||||
@@ -177,6 +207,44 @@ def update_exam(
|
||||
return _exam_to_out(exam)
|
||||
|
||||
|
||||
@router.patch("/exams/{exam_id}/review", response_model=ExamOut)
|
||||
def update_exam_review(
|
||||
exam_id: uuid.UUID,
|
||||
data: ExamReviewUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
exam = (
|
||||
db.query(ExamRecord)
|
||||
.options(joinedload(ExamRecord.scores).joinedload(SubjectScore.subject))
|
||||
.join(ExamRecord.student)
|
||||
.filter(ExamRecord.id == exam_id)
|
||||
.first()
|
||||
)
|
||||
if exam is None or exam.student.user_id != current_user.id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="考试记录不存在")
|
||||
|
||||
by_subject = {s.subject_id: s for s in exam.scores}
|
||||
for item in data.reviews:
|
||||
score = by_subject.get(item.subject_id)
|
||||
if score is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"科目 {item.subject_id} 不在该次考试中",
|
||||
)
|
||||
score.review_statuses_json = _serialize_review_statuses(item.review_statuses)
|
||||
|
||||
db.commit()
|
||||
db.refresh(exam)
|
||||
exam = (
|
||||
db.query(ExamRecord)
|
||||
.options(joinedload(ExamRecord.scores).joinedload(SubjectScore.subject))
|
||||
.filter(ExamRecord.id == exam.id)
|
||||
.first()
|
||||
)
|
||||
return _exam_to_out(exam)
|
||||
|
||||
|
||||
@router.delete("/exams/{exam_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_exam(
|
||||
exam_id: uuid.UUID,
|
||||
|
||||
@@ -219,6 +219,15 @@ class ExamUpdate(BaseModel):
|
||||
scores: list[ScoreInput] | None = None
|
||||
|
||||
|
||||
class ReviewScoreInput(BaseModel):
|
||||
subject_id: int
|
||||
review_statuses: list[ReviewStatusEnum] = []
|
||||
|
||||
|
||||
class ExamReviewUpdate(BaseModel):
|
||||
reviews: list[ReviewScoreInput] = []
|
||||
|
||||
|
||||
class ExamOut(BaseModel):
|
||||
id: UUID
|
||||
exam_type: ExamTypeEnum
|
||||
|
||||
Reference in New Issue
Block a user