251209:0000 Backend Test stagenot finish & Frontend add Task 013-015
Spec Validation / validate-markdown (push) Has been cancelled
Spec Validation / validate-diagrams (push) Has been cancelled
Spec Validation / check-todos (push) Has been cancelled

This commit is contained in:
2025-12-09 00:00:28 +07:00
parent 863a727756
commit 8aceced902
23 changed files with 3571 additions and 118 deletions
@@ -1,5 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CorrespondenceController } from './correspondence.controller';
import { CorrespondenceService } from './correspondence.service';
describe('CorrespondenceController', () => {
let controller: CorrespondenceController;
@@ -7,6 +8,20 @@ describe('CorrespondenceController', () => {
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CorrespondenceController],
providers: [
{
provide: CorrespondenceService,
useValue: {
create: jest.fn(),
findAll: jest.fn(),
submit: jest.fn(),
processAction: jest.fn(),
getReferences: jest.fn(),
addReference: jest.fn(),
removeReference: jest.fn(),
},
},
],
}).compile();
controller = module.get<CorrespondenceController>(CorrespondenceController);
@@ -5,91 +5,118 @@ import {
Body,
UseGuards,
Request,
Param, // <--- ✅ 1. เพิ่ม Param
ParseIntPipe, // <--- ✅ 2. เพิ่ม ParseIntPipe
Param,
ParseIntPipe,
Query,
Delete,
} from '@nestjs/common';
import { CorrespondenceService } from './correspondence.service.js';
import { CreateCorrespondenceDto } from './dto/create-correspondence.dto.js';
import { SubmitCorrespondenceDto } from './dto/submit-correspondence.dto.js'; // <--- ✅ 3. เพิ่ม Import DTO นี้
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBearerAuth,
} from '@nestjs/swagger';
import { CorrespondenceService } from './correspondence.service';
import { CreateCorrespondenceDto } from './dto/create-correspondence.dto';
import { SubmitCorrespondenceDto } from './dto/submit-correspondence.dto';
import { WorkflowActionDto } from './dto/workflow-action.dto';
import { AddReferenceDto } from './dto/add-reference.dto';
import { SearchCorrespondenceDto } from './dto/search-correspondence.dto';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard.js';
import { RbacGuard } from '../../common/guards/rbac.guard.js';
import { RequirePermission } from '../../common/decorators/require-permission.decorator.js';
import { WorkflowActionDto } from './dto/workflow-action.dto.js';
// ... imports ...
import { AddReferenceDto } from './dto/add-reference.dto.js';
import { SearchCorrespondenceDto } from './dto/search-correspondence.dto.js';
import { Query, Delete } from '@nestjs/common'; // เพิ่ม Query, Delete
import { Audit } from '../../common/decorators/audit.decorator'; // Import
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { RbacGuard } from '../../common/guards/rbac.guard';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
import { Audit } from '../../common/decorators/audit.decorator';
@ApiTags('Correspondences')
@Controller('correspondences')
@UseGuards(JwtAuthGuard, RbacGuard)
@ApiBearerAuth()
export class CorrespondenceController {
constructor(private readonly correspondenceService: CorrespondenceService) {}
@Post(':id/workflow/action')
@RequirePermission('workflow.action_review') // สิทธิ์ในการกดอนุมัติ/ตรวจสอบ
@ApiOperation({ summary: 'Process workflow action (Approve/Reject/Review)' })
@ApiResponse({ status: 201, description: 'Action processed successfully.' })
@RequirePermission('workflow.action_review')
processAction(
@Param('id', ParseIntPipe) id: number,
@Body() actionDto: WorkflowActionDto,
@Request() req: any,
@Request() req: any
) {
return this.correspondenceService.processAction(id, actionDto, req.user);
}
@Post()
@RequirePermission('correspondence.create') // 🔒 ต้องมีสิทธิ์สร้าง
@Audit('correspondence.create', 'correspondence') // ✅ แปะตรงนี้
@ApiOperation({ summary: 'Create new correspondence' })
@ApiResponse({
status: 201,
description: 'Correspondence created successfully.',
type: CreateCorrespondenceDto,
})
@RequirePermission('correspondence.create')
@Audit('correspondence.create', 'correspondence')
create(@Body() createDto: CreateCorrespondenceDto, @Request() req: any) {
return this.correspondenceService.create(createDto, req.user);
}
// ✅ ปรับปรุง findAll ให้รับ Query Params
@Get()
@ApiOperation({ summary: 'Search correspondences' })
@ApiResponse({ status: 200, description: 'Return list of correspondences.' })
@RequirePermission('document.view')
findAll(@Query() searchDto: SearchCorrespondenceDto) {
return this.correspondenceService.findAll(searchDto);
}
// ✅ เพิ่ม Endpoint นี้ครับ
@Post(':id/submit')
@RequirePermission('correspondence.create') // หรือจะสร้าง Permission ใหม่ 'workflow.submit' ก็ได้
@Audit('correspondence.create', 'correspondence') // ✅ แปะตรงนี้
@ApiOperation({ summary: 'Submit correspondence to workflow' })
@ApiResponse({
status: 201,
description: 'Correspondence submitted successfully.',
})
@RequirePermission('correspondence.create')
@Audit('correspondence.create', 'correspondence')
submit(
@Param('id', ParseIntPipe) id: number,
@Body() submitDto: SubmitCorrespondenceDto,
@Request() req: any,
@Request() req: any
) {
return this.correspondenceService.submit(
id,
submitDto.templateId,
req.user,
req.user
);
}
// --- REFERENCES ---
@Get(':id/references')
@ApiOperation({ summary: 'Get referenced documents' })
@ApiResponse({
status: 200,
description: 'Return list of referenced documents.',
})
@RequirePermission('document.view')
getReferences(@Param('id', ParseIntPipe) id: number) {
return this.correspondenceService.getReferences(id);
}
@Post(':id/references')
@RequirePermission('document.edit') // ต้องมีสิทธิ์แก้ไขถึงจะเพิ่ม Ref ได้
@ApiOperation({ summary: 'Add reference to another document' })
@ApiResponse({ status: 201, description: 'Reference added successfully.' })
@RequirePermission('document.edit')
addReference(
@Param('id', ParseIntPipe) id: number,
@Body() dto: AddReferenceDto,
@Body() dto: AddReferenceDto
) {
return this.correspondenceService.addReference(id, dto);
}
@Delete(':id/references/:targetId')
@ApiOperation({ summary: 'Remove reference' })
@ApiResponse({ status: 200, description: 'Reference removed successfully.' })
@RequirePermission('document.edit')
removeReference(
@Param('id', ParseIntPipe) id: number,
@Param('targetId', ParseIntPipe) targetId: number,
@Param('targetId', ParseIntPipe) targetId: number
) {
return this.correspondenceService.removeReference(id, targetId);
}
@@ -1,6 +1,11 @@
import { IsInt, IsNotEmpty } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class AddReferenceDto {
@ApiProperty({
description: 'Target Correspondence ID to reference',
example: 20,
})
@IsInt()
@IsNotEmpty()
targetId!: number;
@@ -1,33 +1,43 @@
import { IsOptional, IsString, IsInt } from 'class-validator';
import { Type } from 'class-transformer'; // <--- ✅ Import จาก class-transformer
import { Type } from 'class-transformer';
import { ApiPropertyOptional } from '@nestjs/swagger';
export class SearchCorrespondenceDto {
@ApiPropertyOptional({
description: 'Search term (Title or Document Number)',
})
@IsOptional()
@IsString()
search?: string; // ค้นหาจาก Title หรือ Number
search?: string;
@ApiPropertyOptional({ description: 'Filter by Document Type ID' })
@IsOptional()
@Type(() => Number)
@IsInt()
typeId?: number;
@ApiPropertyOptional({ description: 'Filter by Project ID' })
@IsOptional()
@Type(() => Number)
@IsInt()
projectId?: number;
// status อาจจะซับซ้อนหน่อยเพราะอยู่ที่ Revision แต่ใส่ไว้ก่อน
@ApiPropertyOptional({ description: 'Filter by Status ID' })
@IsOptional()
@Type(() => Number)
@IsInt()
statusId?: number;
// Pagination
@ApiPropertyOptional({ description: 'Page number (default 1)', default: 1 })
@IsOptional()
@Type(() => Number)
@IsInt()
page?: number;
@ApiPropertyOptional({
description: 'Items per page (default 10)',
default: 10,
})
@IsOptional()
@Type(() => Number)
@IsInt()
@@ -1,6 +1,11 @@
import { IsInt, IsNotEmpty } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class SubmitCorrespondenceDto {
@ApiProperty({
description: 'ID of the Workflow Template to start',
example: 1,
})
@IsInt()
@IsNotEmpty()
templateId!: number;
@@ -1,14 +1,27 @@
import { IsEnum, IsString, IsOptional, IsInt } from 'class-validator';
import { WorkflowAction } from '../../workflow-engine/interfaces/workflow.interface.js';
import { WorkflowAction } from '../../workflow-engine/interfaces/workflow.interface';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class WorkflowActionDto {
@ApiProperty({
description: 'Workflow Action',
enum: ['APPROVE', 'REJECT', 'RETURN', 'CANCEL', 'ACKNOWLEDGE'],
})
@IsEnum(WorkflowAction)
action!: WorkflowAction; // APPROVE, REJECT, RETURN, ACKNOWLEDGE
@ApiPropertyOptional({
description: 'Review comments',
example: 'Approved with note...',
})
@IsString()
@IsOptional()
comments?: string;
@ApiPropertyOptional({
description: 'Sequence to return to (only for RETURN action)',
example: 1,
})
@IsInt()
@IsOptional()
returnToSequence?: number; // ใช้กรณี action = RETURN