forked from wangziqi/gongxue-base
100 lines
3.0 KiB
TypeScript
100 lines
3.0 KiB
TypeScript
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { DataSource, Repository } from 'typeorm';
|
|
import { AiReview } from './entities/ai-review.entity';
|
|
import type {
|
|
AiReviewSection,
|
|
AiReviewSectionType,
|
|
} from './entities/ai-review.entity';
|
|
import {
|
|
AiReviewStepSubmitResult,
|
|
AiReviewSubmitResult,
|
|
} from './ai-review.shared';
|
|
import { parseSections } from './ai-review.workbook';
|
|
import {
|
|
submitAll,
|
|
submitGroup,
|
|
submitSection,
|
|
} from './ai-review.submit';
|
|
import type { AiReviewSubmitContext } from './ai-review.submit';
|
|
|
|
/**
|
|
* A2UI batch-import review confirmation lifecycle.
|
|
*
|
|
* Existing previews can be confirmed per section, per group, or all at
|
|
* once; each section is imported in its own transaction in dependency
|
|
* order: students → rooms → transfers → checkins.
|
|
*/
|
|
@Injectable()
|
|
export class AiReviewService {
|
|
constructor(
|
|
@InjectRepository(AiReview)
|
|
private readonly reviews: Repository<AiReview>,
|
|
private readonly dataSource: DataSource,
|
|
) {}
|
|
|
|
private get submitContext(): AiReviewSubmitContext {
|
|
return { reviews: this.reviews, dataSource: this.dataSource };
|
|
}
|
|
|
|
async findOwnedPending(reviewId: string, userId: number): Promise<AiReview> {
|
|
const review = await this.reviews.findOne({
|
|
where: { id: reviewId, userId, status: 'pending' },
|
|
});
|
|
if (!review) throw new NotFoundException('导入预览不存在、已确认或已失效');
|
|
return review;
|
|
}
|
|
|
|
/**
|
|
* Return a review owned by the user regardless of overall status.
|
|
* Used by step confirmation so an already-completed card can respond
|
|
* with a conflict instead of a plain not-found error.
|
|
*/
|
|
async findOwned(reviewId: string, userId: number): Promise<AiReview> {
|
|
const review = await this.reviews.findOne({
|
|
where: { id: reviewId, userId },
|
|
});
|
|
if (!review) throw new NotFoundException('导入预览不存在');
|
|
return review;
|
|
}
|
|
|
|
/** Public shape used by message metadata and the unified `ui.artifact` payload. */
|
|
serialize(review: AiReview): Record<string, unknown> {
|
|
return {
|
|
id: review.id,
|
|
title: review.title,
|
|
summary: review.summary,
|
|
sections: this.parseSections(review.sectionsJson),
|
|
status: review.status,
|
|
resultSummary: review.resultSummary,
|
|
};
|
|
}
|
|
|
|
parseSections(sectionsJson: string): AiReviewSection[] {
|
|
return parseSections(sectionsJson);
|
|
}
|
|
|
|
async submitSection(
|
|
reviewId: string,
|
|
userId: number,
|
|
sectionKey: string,
|
|
): Promise<AiReviewStepSubmitResult> {
|
|
return submitSection(this.submitContext, reviewId, userId, sectionKey);
|
|
}
|
|
|
|
async submitAll(reviewId: string, userId: number): Promise<{
|
|
review: AiReview;
|
|
result: AiReviewSubmitResult;
|
|
}> {
|
|
return submitAll(this.submitContext, reviewId, userId);
|
|
}
|
|
|
|
async submitGroup(
|
|
reviewId: string,
|
|
userId: number,
|
|
type: AiReviewSectionType,
|
|
): Promise<{ review: AiReview }> {
|
|
return submitGroup(this.submitContext, reviewId, userId, type);
|
|
}
|
|
}
|