forked from hangshuo652/aurak
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,
|
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,
|
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,
|
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 })
|
@Column({ type: 'float', name: 'final_score', nullable: true })
|
||||||
finalScore: number;
|
finalScore: number;
|
||||||
|
|
||||||
|
@Column({ type: 'float', name: 'original_score', nullable: true })
|
||||||
|
originalScore: number;
|
||||||
|
|
||||||
@Column({ type: 'text', name: 'final_report', nullable: true })
|
@Column({ type: 'text', name: 'final_report', nullable: true })
|
||||||
finalReport: string;
|
finalReport: string;
|
||||||
|
|
||||||
@@ -70,6 +73,18 @@ export class AssessmentSession {
|
|||||||
@Column({ type: 'simple-json', name: 'feedback_history', nullable: true })
|
@Column({ type: 'simple-json', name: 'feedback_history', nullable: true })
|
||||||
feedbackHistory: any[];
|
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 })
|
@Column({ type: 'int', name: 'current_question_index', default: 0 })
|
||||||
currentQuestionIndex: number;
|
currentQuestionIndex: number;
|
||||||
|
|
||||||
|
|||||||
@@ -406,4 +406,38 @@ export class QuestionBankService {
|
|||||||
);
|
);
|
||||||
return selected;
|
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');
|
if (!response.ok) throw new Error('Failed to generate questions');
|
||||||
return await response.json();
|
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);
|
const { data } = await apiClient.get<AssessmentStats>(url);
|
||||||
return data;
|
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();
|
export const assessmentStatsService = new AssessmentStatsService();
|
||||||
Reference in New Issue
Block a user