feat: 添加复查功能和批量审核操作
- 复查功能: PUT /assessment/:id/review * 支持调整最终总分 * 记录复查历史(reviewHistory) * 保存原始分数(originalScore) * 保留复查人、复查时间、复查意见 - 批量审核: POST /question-banks/:bankId/items/batch-review * 支持批量通过/拒绝题目 * 可添加审核意见 - AssessmentSession实体: 添加复查相关字段
This commit is contained in:
@@ -158,4 +158,21 @@ export class AssessmentController {
|
||||
knowledgeGroupId,
|
||||
);
|
||||
}
|
||||
|
||||
@Put(':id/review')
|
||||
@ApiOperation({ summary: 'Review assessment - adjust final score' })
|
||||
async review(
|
||||
@Param('id') sessionId: string,
|
||||
@Body() body: { newScore: number; comment?: string },
|
||||
@Req() req: any,
|
||||
) {
|
||||
const { id: userId, tenantId } = req.user;
|
||||
return this.assessmentService.reviewAssessment(
|
||||
sessionId,
|
||||
body.newScore,
|
||||
body.comment,
|
||||
userId,
|
||||
tenantId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1438,4 +1438,53 @@ const initialState: Partial<EvaluationState> = {
|
||||
recentRecords,
|
||||
};
|
||||
}
|
||||
|
||||
async reviewAssessment(
|
||||
sessionId: string,
|
||||
newScore: number,
|
||||
comment: string | undefined,
|
||||
reviewerId: string,
|
||||
tenantId: string,
|
||||
): Promise<AssessmentSession> {
|
||||
const session = await this.sessionRepository.findOne({
|
||||
where: { id: sessionId },
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
throw new NotFoundException('Assessment session not found');
|
||||
}
|
||||
|
||||
if (session.status !== AssessmentStatus.COMPLETED) {
|
||||
throw new ForbiddenException('Can only review completed assessments');
|
||||
}
|
||||
|
||||
const reviewRecord = {
|
||||
reviewedBy: reviewerId,
|
||||
reviewedAt: new Date().toISOString(),
|
||||
originalScore: session.finalScore,
|
||||
newScore: newScore,
|
||||
comment: comment || '',
|
||||
};
|
||||
|
||||
const reviewHistory = session.reviewHistory || [];
|
||||
reviewHistory.push(reviewRecord);
|
||||
|
||||
if (!session.originalScore) {
|
||||
session.originalScore = session.finalScore;
|
||||
}
|
||||
|
||||
session.finalScore = newScore;
|
||||
session.reviewedBy = reviewerId;
|
||||
session.reviewedAt = new Date();
|
||||
session.reviewComment = comment || null;
|
||||
session.reviewHistory = reviewHistory;
|
||||
|
||||
await this.sessionRepository.save(session);
|
||||
|
||||
this.logger.log(
|
||||
`[reviewAssessment] Session ${sessionId} reviewed by ${reviewerId}, score changed from ${reviewRecord.originalScore} to ${newScore}`,
|
||||
);
|
||||
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,4 +133,18 @@ export class QuestionBankController {
|
||||
req.user.tenantId,
|
||||
);
|
||||
}
|
||||
|
||||
@Post(':bankId/items/batch-review')
|
||||
async batchReviewItems(
|
||||
@Param('bankId') bankId: string,
|
||||
@Body() body: { itemIds: string[]; approved: boolean; comment?: string },
|
||||
) {
|
||||
this.logger.log(`[batchReview] Reviewing ${body.itemIds.length} items, approved: ${body.approved}`);
|
||||
return this.questionBankService.batchReviewItems(
|
||||
bankId,
|
||||
body.itemIds,
|
||||
body.approved,
|
||||
body.comment,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,9 @@ export class AssessmentSession {
|
||||
@Column({ type: 'float', name: 'final_score', nullable: true })
|
||||
finalScore: number;
|
||||
|
||||
@Column({ type: 'float', name: 'original_score', nullable: true })
|
||||
originalScore: number;
|
||||
|
||||
@Column({ type: 'text', name: 'final_report', nullable: true })
|
||||
finalReport: string;
|
||||
|
||||
@@ -70,6 +73,18 @@ export class AssessmentSession {
|
||||
@Column({ type: 'simple-json', name: 'feedback_history', nullable: true })
|
||||
feedbackHistory: any[];
|
||||
|
||||
@Column({ name: 'reviewed_by', nullable: true, type: 'text' })
|
||||
reviewedBy: string | null;
|
||||
|
||||
@Column({ name: 'reviewed_at', nullable: true, type: 'datetime' })
|
||||
reviewedAt: Date | null;
|
||||
|
||||
@Column({ name: 'review_comment', nullable: true, type: 'text' })
|
||||
reviewComment: string | null;
|
||||
|
||||
@Column({ type: 'simple-json', name: 'review_history', nullable: true })
|
||||
reviewHistory: any[];
|
||||
|
||||
@Column({ type: 'int', name: 'current_question_index', default: 0 })
|
||||
currentQuestionIndex: number;
|
||||
|
||||
|
||||
@@ -406,4 +406,38 @@ export class QuestionBankService {
|
||||
);
|
||||
return selected;
|
||||
}
|
||||
|
||||
async batchReviewItems(
|
||||
bankId: string,
|
||||
itemIds: string[],
|
||||
approved: boolean,
|
||||
comment?: string,
|
||||
): Promise<QuestionBankItem[]> {
|
||||
await this.findOne(bankId);
|
||||
|
||||
const items = await this.itemRepository.find({
|
||||
where: itemIds.map(id => ({ id, bankId })),
|
||||
});
|
||||
|
||||
if (items.length !== itemIds.length) {
|
||||
throw new NotFoundException('Some items not found');
|
||||
}
|
||||
|
||||
const newStatus = approved
|
||||
? QuestionBankItemStatus.PUBLISHED
|
||||
: QuestionBankItemStatus.PENDING_REVIEW;
|
||||
|
||||
for (const item of items) {
|
||||
item.status = newStatus;
|
||||
if (comment) {
|
||||
item.basis = item.basis
|
||||
? `${item.basis}\n[审核意见]: ${comment}`
|
||||
: `[审核意见]: ${comment}`;
|
||||
}
|
||||
}
|
||||
|
||||
await this.itemRepository.save(items);
|
||||
this.logger.log(`[batchReview] ${items.length} items ${approved ? 'approved' : 'rejected'}`);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
@@ -149,4 +149,14 @@ export const questionBankService = {
|
||||
if (!response.ok) throw new Error('Failed to generate questions');
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
async batchReviewItems(bankId: string, itemIds: string[], approved: boolean, comment?: string): Promise<QuestionBankItem[]> {
|
||||
const response = await apiClient.request(`/question-banks/${bankId}/items/batch-review`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ itemIds, approved, comment }),
|
||||
});
|
||||
if (!response.ok) throw new Error('Failed to batch review items');
|
||||
return await response.json();
|
||||
},
|
||||
};
|
||||
@@ -37,6 +37,16 @@ export class AssessmentStatsService {
|
||||
const { data } = await apiClient.get<AssessmentStats>(url);
|
||||
return data;
|
||||
}
|
||||
|
||||
async reviewAssessment(sessionId: string, newScore: number, comment?: string): Promise<any> {
|
||||
const response = await apiClient.request(`/assessment/${sessionId}/review`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ newScore, comment }),
|
||||
});
|
||||
if (!response.ok) throw new Error('Failed to review assessment');
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
export const assessmentStatsService = new AssessmentStatsService();
|
||||
Reference in New Issue
Block a user