feat(rfa-ai): Complete RFA Approval Refactor and AI Model Revision
This commit is contained in:
@@ -95,4 +95,8 @@ export class ReviewTask extends UuidBaseEntity {
|
||||
@ManyToOne(() => User)
|
||||
@JoinColumn({ name: 'delegated_from_user_id' })
|
||||
delegatedFromUser?: User;
|
||||
|
||||
@ManyToOne('RfaRevision')
|
||||
@JoinColumn({ name: 'rfa_revision_id' })
|
||||
rfaRevision?: unknown; // Use unknown to avoid circular dependency and satisfy linter
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@ import {
|
||||
ParseUUIDPipe,
|
||||
} from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { PermissionsGuard } from '../../common/auth/guards/permissions.guard';
|
||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
||||
import { Audit } from '../../common/decorators/audit.decorator';
|
||||
import { ReviewTaskService } from './review-task.service';
|
||||
|
||||
import { ConsensusService } from './services/consensus.service';
|
||||
import { VetoOverrideService } from './services/veto-override.service';
|
||||
import type { VetoOverrideDto } from './services/veto-override.service';
|
||||
@@ -23,7 +27,7 @@ import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
||||
import { User } from '../user/entities/user.entity';
|
||||
|
||||
@Controller('review-tasks')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(JwtAuthGuard, PermissionsGuard)
|
||||
export class ReviewTaskController {
|
||||
constructor(
|
||||
private readonly reviewTaskService: ReviewTaskService,
|
||||
@@ -32,21 +36,27 @@ export class ReviewTaskController {
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@RequirePermission('document.view')
|
||||
findAll(@Query() dto: SearchReviewTaskDto) {
|
||||
return this.reviewTaskService.findAll(dto);
|
||||
}
|
||||
|
||||
@Get(':publicId')
|
||||
@RequirePermission('document.view')
|
||||
findOne(@Param('publicId', ParseUUIDPipe) publicId: string) {
|
||||
return this.reviewTaskService.findByPublicId(publicId);
|
||||
}
|
||||
|
||||
@Patch(':publicId/start')
|
||||
@RequirePermission('workflow.action_review')
|
||||
@Audit('review_task.start', 'review_task')
|
||||
startReview(@Param('publicId', ParseUUIDPipe) publicId: string) {
|
||||
return this.reviewTaskService.startReview(publicId);
|
||||
}
|
||||
|
||||
@Patch(':publicId/complete')
|
||||
@RequirePermission('workflow.action_review')
|
||||
@Audit('review_task.complete', 'review_task')
|
||||
async completeReview(
|
||||
@Param('publicId', ParseUUIDPipe) publicId: string,
|
||||
@Body() dto: CompleteReviewTaskDto,
|
||||
@@ -102,6 +112,8 @@ export class ReviewTaskController {
|
||||
}
|
||||
|
||||
@Post('veto-override')
|
||||
@RequirePermission('document.admin_edit')
|
||||
@Audit('review_task.veto_override', 'review_task')
|
||||
async overrideVeto(@Body() dto: VetoOverrideDto, @CurrentUser() user: User) {
|
||||
return this.vetoOverrideService.executeOverride({
|
||||
...dto,
|
||||
|
||||
@@ -18,9 +18,12 @@ import {
|
||||
AddTeamMemberDto,
|
||||
SearchReviewTeamDto,
|
||||
} from './dto/shared/review-team.dto';
|
||||
import { PermissionsGuard } from '../../common/auth/guards/permissions.guard';
|
||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
||||
import { Audit } from '../../common/decorators/audit.decorator';
|
||||
|
||||
@Controller('review-teams')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(JwtAuthGuard, PermissionsGuard)
|
||||
export class ReviewTeamController {
|
||||
constructor(private readonly reviewTeamService: ReviewTeamService) {}
|
||||
|
||||
@@ -29,6 +32,7 @@ export class ReviewTeamController {
|
||||
* ดึงรายการ Review Teams ตาม project
|
||||
*/
|
||||
@Get()
|
||||
@RequirePermission('master_data.view')
|
||||
findAll(@Query() dto: SearchReviewTeamDto) {
|
||||
return this.reviewTeamService.findAll(dto);
|
||||
}
|
||||
@@ -38,6 +42,7 @@ export class ReviewTeamController {
|
||||
* ดึง Review Team เดียว (ADR-019)
|
||||
*/
|
||||
@Get(':publicId')
|
||||
@RequirePermission('master_data.view')
|
||||
findOne(@Param('publicId') publicId: string) {
|
||||
return this.reviewTeamService.findByPublicId(publicId);
|
||||
}
|
||||
@@ -47,6 +52,8 @@ export class ReviewTeamController {
|
||||
* สร้าง Review Team ใหม่
|
||||
*/
|
||||
@Post()
|
||||
@RequirePermission('master_data.manage')
|
||||
@Audit('review_team.create', 'review_team')
|
||||
create(@Body() dto: CreateReviewTeamDto) {
|
||||
return this.reviewTeamService.create(dto);
|
||||
}
|
||||
@@ -56,6 +63,8 @@ export class ReviewTeamController {
|
||||
* อัปเดต Review Team
|
||||
*/
|
||||
@Patch(':publicId')
|
||||
@RequirePermission('master_data.manage')
|
||||
@Audit('review_team.update', 'review_team')
|
||||
update(
|
||||
@Param('publicId') publicId: string,
|
||||
@Body() dto: UpdateReviewTeamDto
|
||||
@@ -68,6 +77,8 @@ export class ReviewTeamController {
|
||||
* เพิ่มสมาชิก
|
||||
*/
|
||||
@Post(':publicId/members')
|
||||
@RequirePermission('master_data.manage')
|
||||
@Audit('review_team.add_member', 'review_team')
|
||||
addMember(
|
||||
@Param('publicId') teamPublicId: string,
|
||||
@Body() dto: AddTeamMemberDto
|
||||
@@ -80,6 +91,8 @@ export class ReviewTeamController {
|
||||
* ลบสมาชิก
|
||||
*/
|
||||
@Delete(':publicId/members/:memberPublicId')
|
||||
@RequirePermission('master_data.manage')
|
||||
@Audit('review_team.remove_member', 'review_team')
|
||||
removeMember(
|
||||
@Param('publicId') teamPublicId: string,
|
||||
@Param('memberPublicId') memberPublicId: string
|
||||
@@ -92,6 +105,8 @@ export class ReviewTeamController {
|
||||
* Deactivate Review Team (soft delete)
|
||||
*/
|
||||
@Delete(':publicId')
|
||||
@RequirePermission('master_data.manage')
|
||||
@Audit('review_team.deactivate', 'review_team')
|
||||
deactivate(@Param('publicId') publicId: string) {
|
||||
return this.reviewTeamService.deactivate(publicId);
|
||||
}
|
||||
|
||||
@@ -118,4 +118,23 @@ export class AggregateStatusService {
|
||||
|
||||
return ConsensusDecision.APPROVED_WITH_COMMENTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* คืนค่า Response Code ที่เข้มงวดที่สุดจาก Tasks ที่เสร็จแล้ว (T068 Improvement)
|
||||
* Code Priority: 3 > 2 > 1B > 1A
|
||||
*/
|
||||
async getMostRestrictiveResponseCode(rfaRevisionId: number): Promise<string> {
|
||||
const tasks = await this.taskRepo.find({
|
||||
where: { rfaRevisionId, status: ReviewTaskStatus.COMPLETED },
|
||||
relations: ['responseCode'],
|
||||
});
|
||||
|
||||
if (tasks.length === 0) return '1A';
|
||||
|
||||
const codes = tasks.map((t) => t.responseCode?.code ?? '').filter(Boolean);
|
||||
if (codes.includes('3')) return '3';
|
||||
if (codes.includes('2')) return '2';
|
||||
if (codes.includes('1B')) return '1B';
|
||||
return '1A';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,7 @@ import { Repository } from 'typeorm';
|
||||
import { ReviewTask } from '../entities/review-task.entity';
|
||||
import { AggregateStatusService } from './aggregate-status.service';
|
||||
import { ApprovalListenerService } from '../../distribution/services/approval-listener.service';
|
||||
import {
|
||||
ConsensusDecision,
|
||||
ReviewTaskStatus,
|
||||
} from '../../common/enums/review.enums';
|
||||
import { ConsensusDecision } from '../../common/enums/review.enums';
|
||||
|
||||
export interface ConsensusResult {
|
||||
decision: ConsensusDecision;
|
||||
@@ -72,15 +69,10 @@ export class ConsensusService {
|
||||
decision === ConsensusDecision.APPROVED ||
|
||||
decision === ConsensusDecision.APPROVED_WITH_COMMENTS
|
||||
) {
|
||||
// ดึง response code ที่ predominant
|
||||
const completedTasks = await this.taskRepo.find({
|
||||
where: { rfaRevisionId, status: ReviewTaskStatus.COMPLETED },
|
||||
relations: ['responseCode'],
|
||||
order: { completedAt: 'DESC' },
|
||||
take: 1,
|
||||
});
|
||||
|
||||
const responseCode = completedTasks[0]?.responseCode?.code ?? '1A';
|
||||
const responseCode =
|
||||
await this.aggregateStatusService.getMostRestrictiveResponseCode(
|
||||
rfaRevisionId
|
||||
);
|
||||
|
||||
await this.approvalListenerService.onConsensusReached({
|
||||
...context,
|
||||
|
||||
@@ -45,6 +45,7 @@ export class TaskCreationService {
|
||||
*/
|
||||
async createParallelTasks(
|
||||
rfaRevisionId: number,
|
||||
rfaPublicId: string,
|
||||
reviewTeamPublicId: string,
|
||||
dueDate: Date,
|
||||
manager: EntityManager,
|
||||
@@ -113,7 +114,7 @@ export class TaskCreationService {
|
||||
if (saved.assignedToUserId) {
|
||||
await this.schedulerService.scheduleForTask({
|
||||
taskPublicId: saved.publicId,
|
||||
rfaPublicId: rfaRevisionId.toString(), // ใช้ rfaRevisionId เป็น placeholder
|
||||
rfaPublicId: rfaPublicId, // ADR-019: Use actual UUID
|
||||
assigneeUserId: saved.assignedToUserId,
|
||||
dueDate: saved.dueDate ?? dueDate,
|
||||
reminderType: ReminderType.DUE_SOON, // Start type, scheduler will fetch rules
|
||||
|
||||
Reference in New Issue
Block a user