251126:1700 1.4.4
This commit is contained in:
17
.vscode/nap-dms.lcbp3.code-workspace
vendored
17
.vscode/nap-dms.lcbp3.code-workspace
vendored
@@ -21,10 +21,21 @@
|
||||
"username": "root"
|
||||
}
|
||||
],
|
||||
"editor.fontSize": 15,
|
||||
"editor.fontSize": 16,
|
||||
"editor.codeActionsOnSave": {
|
||||
|
||||
"terminal": true
|
||||
},
|
||||
"editor.codeActions.triggerOnFocusChange": true
|
||||
"editor.codeActions.triggerOnFocusChange": true,
|
||||
"editor.tabSize": 2,
|
||||
"editor.minimap.sectionHeaderFontSize": 12,
|
||||
"terminal.integrated.fontSize": 15,
|
||||
"workbench.colorTheme": "Default Dark Modern",
|
||||
"workbench.colorCustomizations": {
|
||||
"terminal.background": "#07003c",
|
||||
"terminal.foreground": "#ffffff",
|
||||
"terminalCursor.background": "#ffffff",
|
||||
"terminalCursor.foreground": "#eeff00"
|
||||
},
|
||||
"geminicodeassist.agentYoloMode": false,
|
||||
}
|
||||
}
|
||||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.fixAll.sqltools": "explicit"
|
||||
},
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
@@ -25,5 +26,9 @@
|
||||
"username": "root"
|
||||
}
|
||||
],
|
||||
"editor.fontSize": 16
|
||||
"editor.fontSize": 16,
|
||||
"editor.fontLigatures": false,
|
||||
"editor.tabSize": 2,
|
||||
"editor.minimap.sectionHeaderFontSize": 12,
|
||||
"markdown.extension.print.theme": "dark"
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// File: src/app.module.ts
|
||||
// บันทึกการแก้ไข: เพิ่ม CacheModule (Redis), Config สำหรับ Idempotency และ Maintenance Mode (T1.1)
|
||||
// บันทึกการแก้ไข: เพิ่ม MonitoringModule และ WinstonModule (T6.3)
|
||||
// เพิ่ม MasterModule
|
||||
|
||||
import { Module } from '@nestjs/common';
|
||||
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
|
||||
@@ -9,26 +10,28 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import { WinstonModule } from 'nest-winston'; // ✅ Import WinstonModule
|
||||
import { WinstonModule } from 'nest-winston';
|
||||
import { redisStore } from 'cache-manager-redis-yet';
|
||||
import { RedisModule } from '@nestjs-modules/ioredis';
|
||||
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { envValidationSchema } from './common/config/env.validation.js';
|
||||
import redisConfig from './common/config/redis.config';
|
||||
import { winstonConfig } from './modules/monitoring/logger/winston.config'; // ✅ Import Config
|
||||
import { winstonConfig } from './modules/monitoring/logger/winston.config';
|
||||
|
||||
// Entities & Interceptors
|
||||
import { AuditLog } from './common/entities/audit-log.entity';
|
||||
import { AuditLogInterceptor } from './common/interceptors/audit-log.interceptor';
|
||||
import { MaintenanceModeGuard } from './common/guards/maintenance-mode.guard';
|
||||
// import { IdempotencyInterceptor } from './common/interceptors/idempotency.interceptor';
|
||||
|
||||
// Modules
|
||||
import { AuthModule } from './common/auth/auth.module.js';
|
||||
import { UserModule } from './modules/user/user.module';
|
||||
import { ProjectModule } from './modules/project/project.module';
|
||||
import { MasterModule } from './modules/master/master.module'; // [NEW] ✅ เพิ่ม MasterModule
|
||||
import { FileStorageModule } from './common/file-storage/file-storage.module.js';
|
||||
import { DocumentNumberingModule } from './modules/document-numbering/document-numbering.module';
|
||||
import { AuthModule } from './common/auth/auth.module.js';
|
||||
import { JsonSchemaModule } from './modules/json-schema/json-schema.module.js';
|
||||
import { WorkflowEngineModule } from './modules/workflow-engine/workflow-engine.module';
|
||||
import { CorrespondenceModule } from './modules/correspondence/correspondence.module';
|
||||
@@ -37,12 +40,10 @@ import { DrawingModule } from './modules/drawing/drawing.module';
|
||||
import { TransmittalModule } from './modules/transmittal/transmittal.module';
|
||||
import { CirculationModule } from './modules/circulation/circulation.module';
|
||||
import { NotificationModule } from './modules/notification/notification.module';
|
||||
// ✅ Import Monitoring Module
|
||||
import { MonitoringModule } from './modules/monitoring/monitoring.module';
|
||||
import { ResilienceModule } from './common/resilience/resilience.module'; // ✅ Import
|
||||
// ... imports
|
||||
import { SearchModule } from './modules/search/search.module'; // ✅ Import
|
||||
import { RedisModule } from '@nestjs-modules/ioredis'; // [NEW]
|
||||
import { ResilienceModule } from './common/resilience/resilience.module';
|
||||
import { SearchModule } from './modules/search/search.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
// 1. Setup Config Module พร้อม Validation
|
||||
@@ -80,7 +81,7 @@ import { RedisModule } from '@nestjs-modules/ioredis'; // [NEW]
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
|
||||
// 📝 Setup Winston Logger (Structured Logging) [Req 6.10]
|
||||
// 📝 Setup Winston Logger
|
||||
WinstonModule.forRoot(winstonConfig),
|
||||
|
||||
// 2. Setup TypeORM (MariaDB)
|
||||
@@ -114,7 +115,8 @@ import { RedisModule } from '@nestjs-modules/ioredis'; // [NEW]
|
||||
},
|
||||
}),
|
||||
}),
|
||||
// [NEW] Setup Redis Module (สำหรับ InjectRedis)
|
||||
|
||||
// Setup Redis Module (for InjectRedis)
|
||||
RedisModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
@@ -126,25 +128,27 @@ import { RedisModule } from '@nestjs-modules/ioredis'; // [NEW]
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
// 📊 Register Monitoring Module (Health & Metrics) [Req 6.10]
|
||||
MonitoringModule,
|
||||
|
||||
// Feature Modules
|
||||
// 📊 Monitoring & Resilience
|
||||
MonitoringModule,
|
||||
ResilienceModule,
|
||||
|
||||
// 📦 Feature Modules
|
||||
AuthModule,
|
||||
UserModule,
|
||||
ProjectModule,
|
||||
MasterModule, // ✅ [NEW] Register MasterModule here
|
||||
FileStorageModule,
|
||||
DocumentNumberingModule,
|
||||
JsonSchemaModule,
|
||||
WorkflowEngineModule,
|
||||
CorrespondenceModule,
|
||||
RfaModule, // 👈 ต้องมี
|
||||
DrawingModule, // 👈 ต้องมี
|
||||
TransmittalModule, // 👈 ต้องมี
|
||||
CirculationModule, // 👈 ต้องมี
|
||||
SearchModule, // ✅ Register Module
|
||||
NotificationModule, // 👈 ต้องมี
|
||||
ResilienceModule, // ✅ Register Module
|
||||
RfaModule,
|
||||
DrawingModule,
|
||||
TransmittalModule,
|
||||
CirculationModule,
|
||||
SearchModule,
|
||||
NotificationModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
@@ -164,17 +168,6 @@ import { RedisModule } from '@nestjs-modules/ioredis'; // [NEW]
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: AuditLogInterceptor,
|
||||
},
|
||||
// 🔄 4. Register Idempotency (ถ้าต้องการ Global)
|
||||
// {
|
||||
// provide: APP_INTERCEPTOR,
|
||||
// useClass: IdempotencyInterceptor,
|
||||
// },
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
/*วิธีใช้งาน
|
||||
เมื่อต้องการเปิด Maintenance Mode ให้ Admin (หรือคุณ) ยิงคำสั่งเข้า Redis หรือสร้าง API เพื่อ Set ค่า: SET system:maintenance_mode true (หรือ "true")
|
||||
|
||||
ระบบจะตอบกลับด้วย 503 Service Unavailable ทันที ยกเว้น Controller ที่คุณใส่ @BypassMaintenance() ไว้ครับ
|
||||
*/
|
||||
|
||||
29
backend/src/modules/master/dto/create-discipline.dto.ts
Normal file
29
backend/src/modules/master/dto/create-discipline.dto.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
IsInt,
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsBoolean,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateDisciplineDto {
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
contractId!: number;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
disciplineCode!: string; // เช่น 'STR', 'ARC'
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
codeNameTh?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
codeNameEn?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isActive?: boolean;
|
||||
}
|
||||
23
backend/src/modules/master/dto/create-sub-type.dto.ts
Normal file
23
backend/src/modules/master/dto/create-sub-type.dto.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { IsInt, IsString, IsNotEmpty, IsOptional } from 'class-validator';
|
||||
|
||||
export class CreateSubTypeDto {
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
contractId!: number;
|
||||
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
correspondenceTypeId!: number;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
subTypeCode!: string; // เช่น 'MAT'
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
subTypeName?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
subTypeNumber?: string; // เช่น '11'
|
||||
}
|
||||
15
backend/src/modules/master/dto/save-number-format.dto.ts
Normal file
15
backend/src/modules/master/dto/save-number-format.dto.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { IsInt, IsString, IsNotEmpty, IsOptional } from 'class-validator';
|
||||
|
||||
export class SaveNumberFormatDto {
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
projectId!: number;
|
||||
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
correspondenceTypeId!: number;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
formatTemplate!: string; // เช่น '{ORG}-{TYPE}-{DISCIPLINE}-{SEQ:4}'
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
@@ -12,22 +13,28 @@ import {
|
||||
UseGuards,
|
||||
ParseIntPipe,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
|
||||
import { MasterService } from './master.service';
|
||||
|
||||
// DTOs (สมมติว่ามีการสร้างไฟล์เหล่านี้แล้วตามแผนงาน)
|
||||
import { CreateTagDto } from './dto/create-tag.dto';
|
||||
import { UpdateTagDto } from './dto/update-tag.dto';
|
||||
import { SearchTagDto } from './dto/search-tag.dto';
|
||||
import { CreateDisciplineDto } from './dto/create-discipline.dto'; // [New]
|
||||
import { CreateSubTypeDto } from './dto/create-sub-type.dto'; // [New]
|
||||
import { SaveNumberFormatDto } from './dto/save-number-format.dto'; // [New]
|
||||
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
||||
|
||||
@ApiTags('Master Data')
|
||||
@Controller('master')
|
||||
@UseGuards(JwtAuthGuard) // บังคับ Login ทุก Endpoint
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class MasterController {
|
||||
constructor(private readonly masterService: MasterService) {}
|
||||
|
||||
// =================================================================
|
||||
// 📦 Dropdowns Endpoints (Read-Only for Frontend)
|
||||
// 📦 Common Dropdowns (Read-Only)
|
||||
// =================================================================
|
||||
|
||||
@Get('correspondence-types')
|
||||
@@ -67,7 +74,82 @@ export class MasterController {
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 🏷️ Tag Management Endpoints
|
||||
// 🏗️ Disciplines Management (Req 6B)
|
||||
// =================================================================
|
||||
|
||||
@Get('disciplines')
|
||||
@ApiOperation({ summary: 'Get disciplines (filter by contract optional)' })
|
||||
@ApiQuery({ name: 'contractId', required: false, type: Number })
|
||||
getDisciplines(@Query('contractId') contractId?: number) {
|
||||
return this.masterService.findAllDisciplines(contractId);
|
||||
}
|
||||
|
||||
@Post('disciplines')
|
||||
@RequirePermission('master_data.manage') // สิทธิ์ Admin
|
||||
@ApiOperation({ summary: 'Create a new discipline' })
|
||||
createDiscipline(@Body() dto: CreateDisciplineDto) {
|
||||
return this.masterService.createDiscipline(dto);
|
||||
}
|
||||
|
||||
@Delete('disciplines/:id')
|
||||
@RequirePermission('master_data.manage')
|
||||
@ApiOperation({ summary: 'Delete a discipline' })
|
||||
deleteDiscipline(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.masterService.deleteDiscipline(id);
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 📑 Correspondence Sub-Types (Req 6B)
|
||||
// =================================================================
|
||||
|
||||
@Get('sub-types')
|
||||
@ApiOperation({ summary: 'Get sub-types (filter by contract/type optional)' })
|
||||
@ApiQuery({ name: 'contractId', required: false, type: Number })
|
||||
@ApiQuery({ name: 'typeId', required: false, type: Number })
|
||||
getSubTypes(
|
||||
@Query('contractId') contractId?: number,
|
||||
@Query('typeId') typeId?: number,
|
||||
) {
|
||||
return this.masterService.findAllSubTypes(contractId, typeId);
|
||||
}
|
||||
|
||||
@Post('sub-types')
|
||||
@RequirePermission('master_data.manage')
|
||||
@ApiOperation({ summary: 'Create/Map a new sub-type' })
|
||||
createSubType(@Body() dto: CreateSubTypeDto) {
|
||||
return this.masterService.createSubType(dto);
|
||||
}
|
||||
|
||||
@Delete('sub-types/:id')
|
||||
@RequirePermission('master_data.manage')
|
||||
@ApiOperation({ summary: 'Delete a sub-type' })
|
||||
deleteSubType(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.masterService.deleteSubType(id);
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 🔢 Numbering Formats (Admin Config)
|
||||
// =================================================================
|
||||
|
||||
@Get('numbering-formats')
|
||||
@RequirePermission('master_data.manage') // ข้อมูล config ควรสงวนสิทธิ์
|
||||
@ApiOperation({ summary: 'Get numbering format for specific project/type' })
|
||||
getNumberFormat(
|
||||
@Query('projectId', ParseIntPipe) projectId: number,
|
||||
@Query('typeId', ParseIntPipe) typeId: number,
|
||||
) {
|
||||
return this.masterService.findNumberFormat(projectId, typeId);
|
||||
}
|
||||
|
||||
@Post('numbering-formats')
|
||||
@RequirePermission('system.manage_all') // เฉพาะ Superadmin/System Admin
|
||||
@ApiOperation({ summary: 'Save or Update numbering format template' })
|
||||
saveNumberFormat(@Body() dto: SaveNumberFormatDto) {
|
||||
return this.masterService.saveNumberFormat(dto);
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 🏷️ Tag Management
|
||||
// =================================================================
|
||||
|
||||
@Get('tags')
|
||||
@@ -83,21 +165,21 @@ export class MasterController {
|
||||
}
|
||||
|
||||
@Post('tags')
|
||||
@RequirePermission('master_data.tag.manage') // ต้องมีสิทธิ์ (Admin/Doc Control)
|
||||
@RequirePermission('master_data.tag.manage')
|
||||
@ApiOperation({ summary: 'Create a new tag' })
|
||||
createTag(@Body() dto: CreateTagDto) {
|
||||
return this.masterService.createTag(dto);
|
||||
}
|
||||
|
||||
@Patch('tags/:id')
|
||||
@RequirePermission('master_data.tag.manage') // ต้องมีสิทธิ์
|
||||
@RequirePermission('master_data.tag.manage')
|
||||
@ApiOperation({ summary: 'Update a tag' })
|
||||
updateTag(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateTagDto) {
|
||||
return this.masterService.updateTag(id, dto);
|
||||
}
|
||||
|
||||
@Delete('tags/:id')
|
||||
@RequirePermission('master_data.tag.manage') // ต้องมีสิทธิ์
|
||||
@RequirePermission('master_data.tag.manage')
|
||||
@ApiOperation({ summary: 'Delete a tag' })
|
||||
deleteTag(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.masterService.deleteTag(id);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { MasterService } from './master.service';
|
||||
import { MasterController } from './master.controller';
|
||||
|
||||
// Import Entities
|
||||
// Import Entities เดิม
|
||||
import { Tag } from './entities/tag.entity';
|
||||
import { CorrespondenceType } from '../correspondence/entities/correspondence-type.entity';
|
||||
import { CorrespondenceStatus } from '../correspondence/entities/correspondence-status.entity';
|
||||
@@ -14,6 +14,12 @@ import { RfaStatusCode } from '../rfa/entities/rfa-status-code.entity';
|
||||
import { RfaApproveCode } from '../rfa/entities/rfa-approve-code.entity';
|
||||
import { CirculationStatusCode } from '../circulation/entities/circulation-status-code.entity';
|
||||
|
||||
// [New v1.4.4] Import Entities ใหม่ตาม Req 6B และ T2.6
|
||||
import { Discipline } from './entities/discipline.entity';
|
||||
import { CorrespondenceSubType } from '../correspondence/entities/correspondence-sub-type.entity';
|
||||
// Entity นี้อาจจะอยู่ใน module document-numbering แต่นำมาใช้ที่นี่เพื่อการจัดการ Master Data
|
||||
import { DocumentNumberFormat } from '../document-numbering/entities/document-number-format.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
@@ -24,10 +30,14 @@ import { CirculationStatusCode } from '../circulation/entities/circulation-statu
|
||||
RfaStatusCode,
|
||||
RfaApproveCode,
|
||||
CirculationStatusCode,
|
||||
// [New] Register Repositories
|
||||
Discipline,
|
||||
CorrespondenceSubType,
|
||||
DocumentNumberFormat,
|
||||
]),
|
||||
],
|
||||
controllers: [MasterController],
|
||||
providers: [MasterService],
|
||||
exports: [MasterService], // Export เผื่อ Module อื่นต้องใช้
|
||||
exports: [MasterService],
|
||||
})
|
||||
export class MasterModule {}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// File: src/modules/master/master.service.ts
|
||||
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
ConflictException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@@ -13,132 +17,220 @@ import { RfaApproveCode } from '../rfa/entities/rfa-approve-code.entity';
|
||||
import { CirculationStatusCode } from '../circulation/entities/circulation-status-code.entity';
|
||||
import { Tag } from './entities/tag.entity';
|
||||
|
||||
// [New] Entities
|
||||
import { Discipline } from './entities/discipline.entity';
|
||||
import { CorrespondenceSubType } from '../correspondence/entities/correspondence-sub-type.entity';
|
||||
import { DocumentNumberFormat } from '../document-numbering/entities/document-number-format.entity';
|
||||
|
||||
// Import DTOs
|
||||
import { CreateTagDto } from './dto/create-tag.dto';
|
||||
import { UpdateTagDto } from './dto/update-tag.dto';
|
||||
import { SearchTagDto } from './dto/search-tag.dto';
|
||||
import { CreateDisciplineDto } from './dto/create-discipline.dto';
|
||||
import { CreateSubTypeDto } from './dto/create-sub-type.dto';
|
||||
import { SaveNumberFormatDto } from './dto/save-number-format.dto';
|
||||
|
||||
@Injectable()
|
||||
export class MasterService {
|
||||
constructor(
|
||||
@InjectRepository(CorrespondenceType)
|
||||
private readonly corrTypeRepo: Repository<CorrespondenceType>,
|
||||
|
||||
@InjectRepository(CorrespondenceStatus)
|
||||
private readonly corrStatusRepo: Repository<CorrespondenceStatus>,
|
||||
|
||||
@InjectRepository(RfaType)
|
||||
private readonly rfaTypeRepo: Repository<RfaType>,
|
||||
|
||||
@InjectRepository(RfaStatusCode)
|
||||
private readonly rfaStatusRepo: Repository<RfaStatusCode>,
|
||||
|
||||
@InjectRepository(RfaApproveCode)
|
||||
private readonly rfaApproveRepo: Repository<RfaApproveCode>,
|
||||
|
||||
@InjectRepository(CirculationStatusCode)
|
||||
private readonly circulationStatusRepo: Repository<CirculationStatusCode>,
|
||||
|
||||
@InjectRepository(Tag)
|
||||
private readonly tagRepo: Repository<Tag>,
|
||||
|
||||
// [New] Repositories
|
||||
@InjectRepository(Discipline)
|
||||
private readonly disciplineRepo: Repository<Discipline>,
|
||||
@InjectRepository(CorrespondenceSubType)
|
||||
private readonly subTypeRepo: Repository<CorrespondenceSubType>,
|
||||
@InjectRepository(DocumentNumberFormat)
|
||||
private readonly formatRepo: Repository<DocumentNumberFormat>,
|
||||
) {}
|
||||
|
||||
// =================================================================
|
||||
// ✉️ Correspondence Master Data
|
||||
// =================================================================
|
||||
// ... (Method เดิม: findAllCorrespondenceTypes, findAllCorrespondenceStatuses, ฯลฯ เก็บไว้เหมือนเดิม) ...
|
||||
// หมายเหตุ: ตรวจสอบว่า Entity ใช้ชื่อ property ว่า isActive หรือ is_active (ใน SQL เป็น is_active แต่ใน Entity มักเป็น isActive)
|
||||
// โค้ดเดิมใช้ `where: { isActive: true }` ซึ่งถูกต้องถ้า Entity map column name แล้ว
|
||||
|
||||
async findAllCorrespondenceTypes() {
|
||||
return this.corrTypeRepo.find({
|
||||
where: { isActive: true }, // ✅ แก้เป็น camelCase
|
||||
order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
|
||||
where: { isActive: true },
|
||||
order: { sortOrder: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findAllCorrespondenceStatuses() {
|
||||
return this.corrStatusRepo.find({
|
||||
where: { isActive: true }, // ✅ แก้เป็น camelCase
|
||||
order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
|
||||
where: { isActive: true },
|
||||
order: { sortOrder: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 📐 RFA Master Data
|
||||
// =================================================================
|
||||
|
||||
async findAllRfaTypes() {
|
||||
return this.rfaTypeRepo.find({
|
||||
where: { isActive: true }, // ✅ แก้เป็น camelCase
|
||||
order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
|
||||
where: { isActive: true },
|
||||
order: { sortOrder: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findAllRfaStatuses() {
|
||||
return this.rfaStatusRepo.find({
|
||||
where: { isActive: true }, // ✅ แก้เป็น camelCase
|
||||
order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
|
||||
where: { isActive: true },
|
||||
order: { sortOrder: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findAllRfaApproveCodes() {
|
||||
return this.rfaApproveRepo.find({
|
||||
where: { isActive: true }, // ✅ แก้เป็น camelCase
|
||||
order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
|
||||
where: { isActive: true },
|
||||
order: { sortOrder: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 🔄 Circulation Master Data
|
||||
// =================================================================
|
||||
|
||||
async findAllCirculationStatuses() {
|
||||
return this.circulationStatusRepo.find({
|
||||
where: { isActive: true }, // ✅ แก้เป็น camelCase
|
||||
order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
|
||||
where: { isActive: true },
|
||||
order: { sortOrder: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 🏷️ Tag Management (CRUD)
|
||||
// 🏗️ Disciplines Logic
|
||||
// =================================================================
|
||||
|
||||
async findAllDisciplines(contractId?: number) {
|
||||
const query = this.disciplineRepo
|
||||
.createQueryBuilder('d')
|
||||
.leftJoinAndSelect('d.contract', 'c')
|
||||
.orderBy('d.disciplineCode', 'ASC');
|
||||
|
||||
if (contractId) {
|
||||
query.where('d.contractId = :contractId', { contractId });
|
||||
}
|
||||
// เพิ่มเงื่อนไข Active หากต้องการ
|
||||
query.andWhere('d.isActive = :isActive', { isActive: true });
|
||||
|
||||
return query.getMany();
|
||||
}
|
||||
|
||||
async createDiscipline(dto: CreateDisciplineDto) {
|
||||
const exists = await this.disciplineRepo.findOne({
|
||||
where: { contractId: dto.contractId, disciplineCode: dto.disciplineCode },
|
||||
});
|
||||
if (exists)
|
||||
throw new ConflictException(
|
||||
'Discipline code already exists in this contract',
|
||||
);
|
||||
|
||||
const discipline = this.disciplineRepo.create(dto);
|
||||
return this.disciplineRepo.save(discipline);
|
||||
}
|
||||
|
||||
async deleteDiscipline(id: number) {
|
||||
const result = await this.disciplineRepo.delete(id);
|
||||
if (result.affected === 0)
|
||||
throw new NotFoundException(`Discipline ID ${id} not found`);
|
||||
return { deleted: true };
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 📑 Sub-Types Logic
|
||||
// =================================================================
|
||||
|
||||
async findAllSubTypes(contractId?: number, typeId?: number) {
|
||||
const query = this.subTypeRepo
|
||||
.createQueryBuilder('st')
|
||||
.leftJoinAndSelect('st.contract', 'c')
|
||||
.leftJoinAndSelect('st.correspondenceType', 'ct')
|
||||
.orderBy('st.subTypeCode', 'ASC');
|
||||
|
||||
if (contractId)
|
||||
query.andWhere('st.contractId = :contractId', { contractId });
|
||||
if (typeId) query.andWhere('st.correspondenceTypeId = :typeId', { typeId });
|
||||
|
||||
return query.getMany();
|
||||
}
|
||||
|
||||
async createSubType(dto: CreateSubTypeDto) {
|
||||
// อาจจะเช็ค Duplicate code ด้วย logic คล้าย discipline
|
||||
const subType = this.subTypeRepo.create(dto);
|
||||
return this.subTypeRepo.save(subType);
|
||||
}
|
||||
|
||||
async deleteSubType(id: number) {
|
||||
const result = await this.subTypeRepo.delete(id);
|
||||
if (result.affected === 0)
|
||||
throw new NotFoundException(`Sub-type ID ${id} not found`);
|
||||
return { deleted: true };
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 🔢 Numbering Formats Logic
|
||||
// =================================================================
|
||||
|
||||
async findNumberFormat(projectId: number, typeId: number) {
|
||||
const format = await this.formatRepo.findOne({
|
||||
where: { projectId, correspondenceTypeId: typeId },
|
||||
});
|
||||
if (!format) {
|
||||
// Optional: Return default format structure or null
|
||||
return null;
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
async saveNumberFormat(dto: SaveNumberFormatDto) {
|
||||
// Check if exists (Upsert)
|
||||
let format = await this.formatRepo.findOne({
|
||||
where: {
|
||||
projectId: dto.projectId,
|
||||
correspondenceTypeId: dto.correspondenceTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
if (format) {
|
||||
format.formatTemplate = dto.formatTemplate;
|
||||
// format.updatedBy = ... (ถ้ามี)
|
||||
} else {
|
||||
format = this.formatRepo.create({
|
||||
projectId: dto.projectId,
|
||||
correspondenceTypeId: dto.correspondenceTypeId,
|
||||
formatTemplate: dto.formatTemplate,
|
||||
});
|
||||
}
|
||||
|
||||
return this.formatRepo.save(format);
|
||||
}
|
||||
|
||||
// ... (Tag Logic เดิม คงไว้ตามปกติ) ...
|
||||
async findAllTags(query?: SearchTagDto) {
|
||||
const qb = this.tagRepo.createQueryBuilder('tag');
|
||||
|
||||
if (query?.search) {
|
||||
qb.where('tag.tag_name LIKE :search OR tag.description LIKE :search', {
|
||||
search: `%${query.search}%`,
|
||||
});
|
||||
}
|
||||
|
||||
qb.orderBy('tag.tag_name', 'ASC');
|
||||
|
||||
if (query?.page && query?.limit) {
|
||||
const page = query.page;
|
||||
const limit = query.limit;
|
||||
qb.skip((page - 1) * limit).take(limit);
|
||||
}
|
||||
|
||||
if (query?.page && query?.limit) {
|
||||
const [items, total] = await qb.getManyAndCount();
|
||||
return {
|
||||
data: items,
|
||||
meta: {
|
||||
total,
|
||||
page: query.page,
|
||||
limit: query.limit,
|
||||
totalPages: Math.ceil(total / query.limit),
|
||||
},
|
||||
meta: { total, page, limit, totalPages: Math.ceil(total / limit) },
|
||||
};
|
||||
}
|
||||
|
||||
return qb.getMany();
|
||||
}
|
||||
|
||||
async findOneTag(id: number) {
|
||||
const tag = await this.tagRepo.findOne({ where: { id } });
|
||||
if (!tag) {
|
||||
throw new NotFoundException(`Tag with ID "${id}" not found`);
|
||||
}
|
||||
if (!tag) throw new NotFoundException(`Tag with ID "${id}" not found`);
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user