From 0e5d7e7e9eff6a7409d5aa814b60cdffa2bbe5fa Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 24 Nov 2025 17:03:36 +0700 Subject: [PATCH] 251124:1702 Ready to Phase 7 --- .../src/modules/master/master.controller.ts | 53 ++++++++-- backend/src/modules/master/master.service.ts | 98 ++++++++++++++++--- 2 files changed, 130 insertions(+), 21 deletions(-) diff --git a/backend/src/modules/master/master.controller.ts b/backend/src/modules/master/master.controller.ts index f4da7ad..f52db0f 100644 --- a/backend/src/modules/master/master.controller.ts +++ b/backend/src/modules/master/master.controller.ts @@ -1,9 +1,22 @@ // File: src/modules/master/master.controller.ts -import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + Query, + UseGuards, + ParseIntPipe, +} from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { MasterService } from './master.service'; import { CreateTagDto } from './dto/create-tag.dto'; +import { UpdateTagDto } from './dto/update-tag.dto'; +import { SearchTagDto } from './dto/search-tag.dto'; import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; import { RequirePermission } from '../../common/decorators/require-permission.decorator'; @@ -13,6 +26,10 @@ import { RequirePermission } from '../../common/decorators/require-permission.de export class MasterController { constructor(private readonly masterService: MasterService) {} + // ================================================================= + // 📦 Dropdowns Endpoints (Read-Only for Frontend) + // ================================================================= + @Get('correspondence-types') @ApiOperation({ summary: 'Get all active correspondence types' }) getCorrespondenceTypes() { @@ -49,16 +66,40 @@ export class MasterController { return this.masterService.findAllCirculationStatuses(); } + // ================================================================= + // 🏷️ Tag Management Endpoints + // ================================================================= + @Get('tags') - @ApiOperation({ summary: 'Get all tags' }) - getTags() { - return this.masterService.findAllTags(); + @ApiOperation({ summary: 'Get all tags (supports search & pagination)' }) + getTags(@Query() query: SearchTagDto) { + return this.masterService.findAllTags(query); + } + + @Get('tags/:id') + @ApiOperation({ summary: 'Get a tag by ID' }) + getTagById(@Param('id', ParseIntPipe) id: number) { + return this.masterService.findOneTag(id); } @Post('tags') - @RequirePermission('master_data.tag.manage') // ต้องมีสิทธิ์จัดการ Tag - @ApiOperation({ summary: 'Create a new tag (Admin only)' }) + @RequirePermission('master_data.tag.manage') // ต้องมีสิทธิ์ (Admin/Doc Control) + @ApiOperation({ summary: 'Create a new tag' }) createTag(@Body() dto: CreateTagDto) { return this.masterService.createTag(dto); } + + @Patch('tags/:id') + @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') // ต้องมีสิทธิ์ + @ApiOperation({ summary: 'Delete a tag' }) + deleteTag(@Param('id', ParseIntPipe) id: number) { + return this.masterService.deleteTag(id); + } } diff --git a/backend/src/modules/master/master.service.ts b/backend/src/modules/master/master.service.ts index 6047900..315cb06 100644 --- a/backend/src/modules/master/master.service.ts +++ b/backend/src/modules/master/master.service.ts @@ -1,19 +1,22 @@ // File: src/modules/master/master.service.ts -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -// Import Entities จาก Module อื่นๆ (ตามโครงสร้างที่มีอยู่แล้ว) +// Import Entities import { CorrespondenceType } from '../correspondence/entities/correspondence-type.entity'; import { CorrespondenceStatus } from '../correspondence/entities/correspondence-status.entity'; import { RfaType } from '../rfa/entities/rfa-type.entity'; 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'; -import { Tag } from './entities/tag.entity'; // Entity ของ Module นี้เอง +import { Tag } from './entities/tag.entity'; +// Import DTOs import { CreateTagDto } from './dto/create-tag.dto'; +import { UpdateTagDto } from './dto/update-tag.dto'; +import { SearchTagDto } from './dto/search-tag.dto'; @Injectable() export class MasterService { @@ -40,58 +43,123 @@ export class MasterService { private readonly tagRepo: Repository, ) {} - // --- Correspondence --- - findAllCorrespondenceTypes() { + // ================================================================= + // ✉️ Correspondence Master Data + // ================================================================= + + async findAllCorrespondenceTypes() { return this.corrTypeRepo.find({ where: { is_active: true }, order: { sort_order: 'ASC' }, }); } - findAllCorrespondenceStatuses() { + async findAllCorrespondenceStatuses() { return this.corrStatusRepo.find({ where: { is_active: true }, order: { sort_order: 'ASC' }, }); } - // --- RFA --- - findAllRfaTypes() { + // ================================================================= + // 📐 RFA Master Data + // ================================================================= + + async findAllRfaTypes() { return this.rfaTypeRepo.find({ where: { is_active: true }, order: { sort_order: 'ASC' }, }); } - findAllRfaStatuses() { + async findAllRfaStatuses() { return this.rfaStatusRepo.find({ where: { is_active: true }, order: { sort_order: 'ASC' }, }); } - findAllRfaApproveCodes() { + async findAllRfaApproveCodes() { return this.rfaApproveRepo.find({ where: { is_active: true }, order: { sort_order: 'ASC' }, }); } - // --- Circulation --- - findAllCirculationStatuses() { + // ================================================================= + // 🔄 Circulation Master Data + // ================================================================= + + async findAllCirculationStatuses() { return this.circulationStatusRepo.find({ where: { is_active: true }, order: { sort_order: 'ASC' }, }); } - // --- Tags --- - findAllTags() { - return this.tagRepo.find({ order: { tag_name: 'ASC' } }); + // ================================================================= + // 🏷️ Tag Management (CRUD) + // ================================================================= + + /** + * ค้นหา Tag ทั้งหมด พร้อมรองรับการ Search และ Pagination + */ + 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'); + + // Pagination Logic + if (query?.page && query?.limit) { + const page = query.page; + const limit = query.limit; + qb.skip((page - 1) * limit).take(limit); + } + + // ถ้ามีการแบ่งหน้า ให้ส่งคืนทั้งข้อมูลและจำนวนทั้งหมด (count) + 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), + }, + }; + } + + 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`); + } + return tag; } async createTag(dto: CreateTagDto) { const tag = this.tagRepo.create(dto); return this.tagRepo.save(tag); } + + async updateTag(id: number, dto: UpdateTagDto) { + const tag = await this.findOneTag(id); // Reuse findOne for check + Object.assign(tag, dto); + return this.tagRepo.save(tag); + } + + async deleteTag(id: number) { + const tag = await this.findOneTag(id); + return this.tagRepo.remove(tag); + } }