251123:2300 Update T1

This commit is contained in:
2025-11-24 08:15:15 +07:00
parent 23006898d9
commit 9360d78ea6
81 changed files with 4232 additions and 347 deletions
@@ -0,0 +1,15 @@
import { IsString, IsNotEmpty, IsOptional, IsBoolean } from 'class-validator';
export class CreateProjectDto {
@IsString()
@IsNotEmpty()
projectCode!: string; // รหัสโครงการ (เช่น LCBP3)
@IsString()
@IsNotEmpty()
projectName!: string; // ชื่อโครงการ
@IsBoolean()
@IsOptional()
isActive?: boolean; // สถานะการใช้งาน (Default: true)
}
@@ -0,0 +1,27 @@
import { IsString, IsOptional, IsInt, IsBoolean } from 'class-validator';
import { Type, Transform } from 'class-transformer';
export class SearchProjectDto {
@IsString()
@IsOptional()
search?: string; // ค้นหาจาก Project Code หรือ Name
@IsOptional()
@IsBoolean()
@Transform(({ value }) => {
if (value === 'true') return true;
if (value === 'false') return false;
return value;
})
isActive?: boolean; // กรองตามสถานะ Active
@IsOptional()
@IsInt()
@Type(() => Number)
page: number = 1;
@IsOptional()
@IsInt()
@Type(() => Number)
limit: number = 20;
}
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateProjectDto } from './create-project.dto';
export class UpdateProjectDto extends PartialType(CreateProjectDto) {}
@@ -1,4 +1,75 @@
import { Controller } from '@nestjs/common';
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
Query,
UseGuards,
ParseIntPipe,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
@Controller('project')
export class ProjectController {}
import { ProjectService } from './project.service.js';
import { CreateProjectDto } from './dto/create-project.dto.js';
import { UpdateProjectDto } from './dto/update-project.dto.js';
import { SearchProjectDto } from './dto/search-project.dto.js';
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';
@ApiTags('Projects')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, RbacGuard)
@Controller('projects') // แนะนำให้ใช้ plural noun (projects)
export class ProjectController {
constructor(private readonly projectService: ProjectService) {}
@Post()
@ApiOperation({ summary: 'Create new Project' })
@RequirePermission('project.create')
create(@Body() createDto: CreateProjectDto) {
return this.projectService.create(createDto);
}
@Get()
@ApiOperation({ summary: 'Search Projects' })
@RequirePermission('project.view')
findAll(@Query() searchDto: SearchProjectDto) {
return this.projectService.findAll(searchDto);
}
@Get('organizations')
@ApiOperation({ summary: 'List All Organizations (Master Data)' })
// @RequirePermission('organization.view') // หรือเปิดให้ดูได้ทั่วไปถ้าจำเป็น
findAllOrgs() {
return this.projectService.findAllOrganizations();
}
@Get(':id')
@ApiOperation({ summary: 'Get Project Details' })
@RequirePermission('project.view')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.projectService.findOne(id);
}
@Patch(':id')
@ApiOperation({ summary: 'Update Project' })
@RequirePermission('project.edit')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateDto: UpdateProjectDto,
) {
return this.projectService.update(id, updateDto);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete Project (Soft Delete)' })
@RequirePermission('project.delete')
remove(@Param('id', ParseIntPipe) id: number) {
return this.projectService.remove(id);
}
}
@@ -7,7 +7,8 @@ import { Organization } from './entities/organization.entity.js';
import { Contract } from './entities/contract.entity.js';
import { ProjectOrganization } from './entities/project-organization.entity.js'; // เพิ่ม
import { ContractOrganization } from './entities/contract-organization.entity.js'; // เพิ่ม
// Modules
import { UserModule } from '../user/user.module'; // ✅ 1. Import UserModule
@Module({
imports: [
TypeOrmModule.forFeature([
@@ -17,6 +18,7 @@ import { ContractOrganization } from './entities/contract-organization.entity.js
ProjectOrganization, // ลงทะเบียน
ContractOrganization, // ลงทะเบียน
]),
UserModule, // ✅ 2. เพิ่ม UserModule เข้าไปใน imports
],
controllers: [ProjectController],
providers: [ProjectService],
+100 -7
View File
@@ -1,11 +1,25 @@
import { Injectable } from '@nestjs/common';
import {
Injectable,
NotFoundException,
ConflictException,
Logger,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Repository, Like } from 'typeorm';
// Entities
import { Project } from './entities/project.entity.js';
import { Organization } from './entities/organization.entity.js';
// DTOs
import { CreateProjectDto } from './dto/create-project.dto.js';
import { UpdateProjectDto } from './dto/update-project.dto.js';
import { SearchProjectDto } from './dto/search-project.dto.js';
@Injectable()
export class ProjectService {
private readonly logger = new Logger(ProjectService.name);
constructor(
@InjectRepository(Project)
private projectRepository: Repository<Project>,
@@ -13,13 +27,92 @@ export class ProjectService {
private organizationRepository: Repository<Organization>,
) {}
// ดึงรายการ Project ทั้งหมด
async findAllProjects() {
return this.projectRepository.find();
// --- CRUD Operations ---
async create(createDto: CreateProjectDto) {
// 1. เช็คชื่อ/รหัสซ้ำ (ถ้าจำเป็น)
const existing = await this.projectRepository.findOne({
where: { projectCode: createDto.projectCode },
});
if (existing) {
throw new ConflictException(
`Project Code "${createDto.projectCode}" already exists`,
);
}
// 2. สร้าง Project
const project = this.projectRepository.create(createDto);
return this.projectRepository.save(project);
}
// ดึงรายการ Organization ทั้งหมด
async findAll(searchDto: SearchProjectDto) {
const { search, isActive, page = 1, limit = 20 } = searchDto;
const skip = (page - 1) * limit;
// สร้าง Query Builder
const query = this.projectRepository.createQueryBuilder('project');
if (isActive !== undefined) {
query.andWhere('project.isActive = :isActive', { isActive });
}
if (search) {
query.andWhere(
'(project.projectCode LIKE :search OR project.projectName LIKE :search)',
{ search: `%${search}%` },
);
}
query.orderBy('project.created_at', 'DESC');
query.skip(skip).take(limit);
const [items, total] = await query.getManyAndCount();
return {
data: items,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
}
async findOne(id: number) {
const project = await this.projectRepository.findOne({
where: { id },
relations: ['contracts'], // ดึงสัญญาที่เกี่ยวข้องมาด้วย
});
if (!project) {
throw new NotFoundException(`Project ID ${id} not found`);
}
return project;
}
async update(id: number, updateDto: UpdateProjectDto) {
const project = await this.findOne(id);
// Merge ข้อมูลใหม่ใส่ข้อมูลเดิม
this.projectRepository.merge(project, updateDto);
return this.projectRepository.save(project);
}
async remove(id: number) {
const project = await this.findOne(id);
// ใช้ Soft Delete
return this.projectRepository.softRemove(project);
}
// --- Organization Helper ---
async findAllOrganizations() {
return this.organizationRepository.find();
return this.organizationRepository.find({
where: { isActive: true },
order: { organizationCode: 'ASC' },
});
}
}