260320:1131 Refactor Overrall #01
Build and Deploy / deploy (push) Has been cancelled

This commit is contained in:
admin
2026-03-20 11:31:27 +07:00
parent f1b81a7d0d
commit 1d3479770b
147 changed files with 1745 additions and 1567 deletions
@@ -13,7 +13,6 @@ import { AsBuiltDrawingRevision } from './entities/asbuilt-drawing-revision.enti
import { ShopDrawingRevision } from './entities/shop-drawing-revision.entity';
import { Attachment } from '../../common/file-storage/entities/attachment.entity';
import { User } from '../user/entities/user.entity';
import { Project } from '../project/entities/project.entity';
// DTOs
import { CreateAsBuiltDrawingDto } from './dto/create-asbuilt-drawing.dto';
@@ -22,6 +21,7 @@ import { SearchAsBuiltDrawingDto } from './dto/search-asbuilt-drawing.dto';
// Services
import { FileStorageService } from '../../common/file-storage/file-storage.service';
import { UuidResolverService } from '../../common/services/uuid-resolver.service';
@Injectable()
export class AsBuiltDrawingService {
@@ -37,25 +37,10 @@ export class AsBuiltDrawingService {
@InjectRepository(Attachment)
private attachmentRepo: Repository<Attachment>,
private fileStorageService: FileStorageService,
private dataSource: DataSource
private dataSource: DataSource,
private uuidResolver: UuidResolverService
) {}
/**
* ADR-019: Resolve projectId (INT or UUID string) to internal INT ID
*/
private async resolveProjectId(projectId: number | string): Promise<number> {
if (typeof projectId === 'number') return projectId;
const num = Number(projectId);
if (!isNaN(num)) return num;
const project = await this.dataSource.manager.findOne(Project, {
where: { uuid: projectId },
select: ['id'],
});
if (!project)
throw new NotFoundException(`Project with UUID ${projectId} not found`);
return project.id;
}
/**
* สร้าง AS Built Drawing ใหม่ พร้อม Revision แรก (Rev 0)
*/
@@ -91,7 +76,7 @@ export class AsBuiltDrawingService {
}
// ADR-019: Resolve UUID→INT
const internalProjectId = await this.resolveProjectId(
const internalProjectId = await this.uuidResolver.resolveProjectId(
createDto.projectId
);
@@ -12,7 +12,6 @@ import { ContractDrawing } from './entities/contract-drawing.entity';
import { Attachment } from '../../common/file-storage/entities/attachment.entity';
import { User } from '../user/entities/user.entity';
import { Contract } from '../contract/entities/contract.entity';
import { Project } from '../project/entities/project.entity';
// DTOs
import { CreateContractDrawingDto } from './dto/create-contract-drawing.dto';
@@ -21,6 +20,7 @@ import { UpdateContractDrawingDto } from './dto/update-contract-drawing.dto';
// Services
import { FileStorageService } from '../../common/file-storage/file-storage.service';
import { UuidResolverService } from '../../common/services/uuid-resolver.service';
@Injectable()
export class ContractDrawingService {
@@ -34,25 +34,10 @@ export class ContractDrawingService {
@InjectRepository(Contract)
private contractRepo: Repository<Contract>,
private fileStorageService: FileStorageService,
private dataSource: DataSource
private dataSource: DataSource,
private uuidResolver: UuidResolverService
) {}
/**
* ADR-019: Resolve projectId (INT or UUID string) to internal INT ID
*/
private async resolveProjectId(projectId: number | string): Promise<number> {
if (typeof projectId === 'number') return projectId;
const num = Number(projectId);
if (!isNaN(num)) return num;
const project = await this.dataSource.manager.findOne(Project, {
where: { uuid: projectId },
select: ['id'],
});
if (!project)
throw new NotFoundException(`Project with UUID ${projectId} not found`);
return project.id;
}
/**
* Resolve issueDate from contract.startDate for file storage path
* Fallback: contract.startDate → current date
@@ -72,7 +57,9 @@ export class ContractDrawingService {
*/
async create(createDto: CreateContractDrawingDto, user: User) {
// ADR-019: Resolve UUID→INT for projectId
const internalProjectId = await this.resolveProjectId(createDto.projectId);
const internalProjectId = await this.uuidResolver.resolveProjectId(
createDto.projectId
);
// 1. ตรวจสอบเลขที่แบบซ้ำ (Unique per Project)
const exists = await this.drawingRepo.findOne({
@@ -19,6 +19,12 @@ import {
} from '@nestjs/swagger';
import { DrawingMasterDataService } from './drawing-master-data.service';
import { ContractDrawingVolume } from './entities/contract-drawing-volume.entity';
import { ContractDrawingCategory } from './entities/contract-drawing-category.entity';
import { ContractDrawingSubCategory } from './entities/contract-drawing-sub-category.entity';
import { ContractDrawingSubcatCatMap } from './entities/contract-drawing-subcat-cat-map.entity';
import { ShopDrawingMainCategory } from './entities/shop-drawing-main-category.entity';
import { ShopDrawingSubCategory } from './entities/shop-drawing-sub-category.entity';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { RbacGuard } from '../../common/guards/rbac.guard';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
@@ -47,7 +53,10 @@ export class DrawingMasterDataController {
@Post('contract/volumes')
@ApiOperation({ summary: 'Create Volume' })
@RequirePermission('master_data.drawing_category.manage')
createVolume(@Body() body: any) {
createVolume(
@Body()
body: Partial<ContractDrawingVolume> & { projectId: number | string }
) {
return this.masterDataService.createVolume(body);
}
@@ -56,7 +65,7 @@ export class DrawingMasterDataController {
@RequirePermission('master_data.drawing_category.manage')
updateVolume(
@Param('id', ParseIntPipe) id: number,
@Body() body: any
@Body() body: Partial<ContractDrawingVolume>
) {
return this.masterDataService.updateVolume(id, body);
}
@@ -83,7 +92,10 @@ export class DrawingMasterDataController {
@Post('contract/categories')
@ApiOperation({ summary: 'Create Category' })
@RequirePermission('master_data.drawing_category.manage')
createCategory(@Body() body: any) {
createCategory(
@Body()
body: Partial<ContractDrawingCategory> & { projectId: number | string }
) {
return this.masterDataService.createCategory(body);
}
@@ -92,7 +104,7 @@ export class DrawingMasterDataController {
@RequirePermission('master_data.drawing_category.manage')
updateCategory(
@Param('id', ParseIntPipe) id: number,
@Body() body: any
@Body() body: Partial<ContractDrawingCategory>
) {
return this.masterDataService.updateCategory(id, body);
}
@@ -119,7 +131,10 @@ export class DrawingMasterDataController {
@Post('contract/sub-categories')
@ApiOperation({ summary: 'Create Contract Sub-Category' })
@RequirePermission('master_data.drawing_category.manage')
createContractSubCat(@Body() body: any) {
createContractSubCat(
@Body()
body: Partial<ContractDrawingSubCategory> & { projectId: number | string }
) {
return this.masterDataService.createContractSubCat(body);
}
@@ -128,7 +143,7 @@ export class DrawingMasterDataController {
@RequirePermission('master_data.drawing_category.manage')
updateContractSubCat(
@Param('id', ParseIntPipe) id: number,
@Body() body: any
@Body() body: Partial<ContractDrawingSubCategory>
) {
return this.masterDataService.updateContractSubCat(id, body);
}
@@ -162,7 +177,10 @@ export class DrawingMasterDataController {
@Post('contract/mappings')
@ApiOperation({ summary: 'Create Contract Drawing Mapping' })
@RequirePermission('master_data.drawing_category.manage')
createContractMapping(@Body() body: any) {
createContractMapping(
@Body()
body: Partial<ContractDrawingSubcatCatMap> & { projectId: number | string }
) {
return this.masterDataService.createContractMapping(body);
}
@@ -188,7 +206,10 @@ export class DrawingMasterDataController {
@Post('shop/main-categories')
@ApiOperation({ summary: 'Create Shop Main Category' })
@RequirePermission('master_data.drawing_category.manage')
createShopMainCat(@Body() body: any) {
createShopMainCat(
@Body()
body: Partial<ShopDrawingMainCategory> & { projectId: number | string }
) {
return this.masterDataService.createShopMainCat(body);
}
@@ -197,7 +218,7 @@ export class DrawingMasterDataController {
@RequirePermission('master_data.drawing_category.manage')
updateShopMainCat(
@Param('id', ParseIntPipe) id: number,
@Body() body: any
@Body() body: Partial<ShopDrawingMainCategory>
) {
return this.masterDataService.updateShopMainCat(id, body);
}
@@ -231,7 +252,10 @@ export class DrawingMasterDataController {
@Post('shop/sub-categories')
@ApiOperation({ summary: 'Create Shop Sub-Category' })
@RequirePermission('master_data.drawing_category.manage')
createShopSubCat(@Body() body: any) {
createShopSubCat(
@Body()
body: Partial<ShopDrawingSubCategory> & { projectId: number | string }
) {
return this.masterDataService.createShopSubCat(body);
}
@@ -240,7 +264,7 @@ export class DrawingMasterDataController {
@RequirePermission('master_data.drawing_category.manage')
updateShopSubCat(
@Param('id', ParseIntPipe) id: number,
@Body() body: any
@Body() body: Partial<ShopDrawingSubCategory>
) {
return this.masterDataService.updateShopSubCat(id, body);
}
@@ -1,6 +1,6 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository, InjectEntityManager } from '@nestjs/typeorm';
import { Repository, FindOptionsWhere, EntityManager } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, FindOptionsWhere } from 'typeorm';
// Entities
import { ContractDrawingVolume } from './entities/contract-drawing-volume.entity';
@@ -9,7 +9,7 @@ import { ContractDrawingSubCategory } from './entities/contract-drawing-sub-cate
import { ShopDrawingMainCategory } from './entities/shop-drawing-main-category.entity';
import { ShopDrawingSubCategory } from './entities/shop-drawing-sub-category.entity';
import { ContractDrawingSubcatCatMap } from './entities/contract-drawing-subcat-cat-map.entity';
import { Project } from '../project/entities/project.entity';
import { UuidResolverService } from '../../common/services/uuid-resolver.service';
@Injectable()
export class DrawingMasterDataService {
@@ -26,45 +26,25 @@ export class DrawingMasterDataService {
private sdSubCatRepo: Repository<ShopDrawingSubCategory>,
@InjectRepository(ContractDrawingSubcatCatMap)
private cdMapRepo: Repository<ContractDrawingSubcatCatMap>,
@InjectEntityManager()
private entityManager: EntityManager
private uuidResolver: UuidResolverService
) {}
/**
* Helper to resolve projectId (ID or UUID) to internal INT ID
*/
async resolveProjectId(projectId: number | string): Promise<number> {
if (typeof projectId === 'number') return projectId;
const num = Number(projectId);
if (!isNaN(num)) return num;
// If it's a string and not a number, it's a UUID (ADR-019)
const project = await this.entityManager.findOne(Project, {
where: { uuid: projectId as string },
select: ['id'],
});
if (!project) {
throw new NotFoundException(`Project with UUID ${projectId} not found`);
}
return project.id;
}
// =====================================================
// Contract Drawing Volumes
// =====================================================
async findAllVolumes(projectId: number | string) {
const internalId = await this.resolveProjectId(projectId);
const internalId = await this.uuidResolver.resolveProjectId(projectId);
return this.cdVolumeRepo.find({
where: { projectId: internalId },
order: { sortOrder: 'ASC' },
});
}
async createVolume(data: any) {
const internalId = await this.resolveProjectId(data.projectId);
async createVolume(
data: Partial<ContractDrawingVolume> & { projectId: number | string }
) {
const internalId = await this.uuidResolver.resolveProjectId(data.projectId);
const volume = this.cdVolumeRepo.create({ ...data, projectId: internalId });
return this.cdVolumeRepo.save(volume);
}
@@ -88,15 +68,17 @@ export class DrawingMasterDataService {
// =====================================================
async findAllCategories(projectId: number | string) {
const internalId = await this.resolveProjectId(projectId);
const internalId = await this.uuidResolver.resolveProjectId(projectId);
return this.cdCatRepo.find({
where: { projectId: internalId },
order: { sortOrder: 'ASC' },
});
}
async createCategory(data: any) {
const internalId = await this.resolveProjectId(data.projectId);
async createCategory(
data: Partial<ContractDrawingCategory> & { projectId: number | string }
) {
const internalId = await this.uuidResolver.resolveProjectId(data.projectId);
const cat = this.cdCatRepo.create({ ...data, projectId: internalId });
return this.cdCatRepo.save(cat);
}
@@ -120,15 +102,17 @@ export class DrawingMasterDataService {
// =====================================================
async findAllContractSubCats(projectId: number | string) {
const internalId = await this.resolveProjectId(projectId);
const internalId = await this.uuidResolver.resolveProjectId(projectId);
return this.cdSubCatRepo.find({
where: { projectId: internalId },
order: { sortOrder: 'ASC' },
});
}
async createContractSubCat(data: any) {
const internalId = await this.resolveProjectId(data.projectId);
async createContractSubCat(
data: Partial<ContractDrawingSubCategory> & { projectId: number | string }
) {
const internalId = await this.uuidResolver.resolveProjectId(data.projectId);
const subCat = this.cdSubCatRepo.create({ ...data, projectId: internalId });
return this.cdSubCatRepo.save(subCat);
}
@@ -155,8 +139,10 @@ export class DrawingMasterDataService {
// =====================================================
async findContractMappings(projectId: number | string, categoryId?: number) {
const internalId = await this.resolveProjectId(projectId);
const where: FindOptionsWhere<ContractDrawingSubcatCatMap> = { projectId: internalId };
const internalId = await this.uuidResolver.resolveProjectId(projectId);
const where: FindOptionsWhere<ContractDrawingSubcatCatMap> = {
projectId: internalId,
};
if (categoryId) {
where.categoryId = categoryId;
}
@@ -167,8 +153,10 @@ export class DrawingMasterDataService {
});
}
async createContractMapping(data: any) {
const internalId = await this.resolveProjectId(data.projectId);
async createContractMapping(
data: Partial<ContractDrawingSubcatCatMap> & { projectId: number | string }
) {
const internalId = await this.uuidResolver.resolveProjectId(data.projectId);
// Check if mapping already exists to prevent duplicates (though DB has UNIQUE constraint)
const existing = await this.cdMapRepo.findOne({
where: {
@@ -196,15 +184,17 @@ export class DrawingMasterDataService {
// =====================================================
async findAllShopMainCats(projectId: number | string) {
const internalId = await this.resolveProjectId(projectId);
const internalId = await this.uuidResolver.resolveProjectId(projectId);
return this.sdMainCatRepo.find({
where: { projectId: internalId },
order: { sortOrder: 'ASC' },
});
}
async createShopMainCat(data: any) {
const internalId = await this.resolveProjectId(data.projectId);
async createShopMainCat(
data: Partial<ShopDrawingMainCategory> & { projectId: number | string }
) {
const internalId = await this.uuidResolver.resolveProjectId(data.projectId);
const cat = this.sdMainCatRepo.create({ ...data, projectId: internalId });
return this.sdMainCatRepo.save(cat);
}
@@ -227,8 +217,11 @@ export class DrawingMasterDataService {
// Shop Drawing Sub-Categories
// =====================================================
async findAllShopSubCats(projectId: number | string, mainCategoryId?: number) {
const internalId = await this.resolveProjectId(projectId);
async findAllShopSubCats(
projectId: number | string,
mainCategoryId?: number
) {
const internalId = await this.uuidResolver.resolveProjectId(projectId);
const where: FindOptionsWhere<ShopDrawingSubCategory> = {
projectId: internalId,
...(mainCategoryId ? { mainCategoryId } : {}),
@@ -240,8 +233,10 @@ export class DrawingMasterDataService {
});
}
async createShopSubCat(data: any) {
const internalId = await this.resolveProjectId(data.projectId);
async createShopSubCat(
data: Partial<ShopDrawingSubCategory> & { projectId: number | string }
) {
const internalId = await this.uuidResolver.resolveProjectId(data.projectId);
const subCat = this.sdSubCatRepo.create({ ...data, projectId: internalId });
return this.sdSubCatRepo.save(subCat);
}
@@ -13,7 +13,6 @@ import { ShopDrawingRevision } from './entities/shop-drawing-revision.entity';
import { ContractDrawing } from './entities/contract-drawing.entity';
import { Attachment } from '../../common/file-storage/entities/attachment.entity';
import { User } from '../user/entities/user.entity';
import { Project } from '../project/entities/project.entity';
// DTOs
import { CreateShopDrawingDto } from './dto/create-shop-drawing.dto';
@@ -22,6 +21,7 @@ import { SearchShopDrawingDto } from './dto/search-shop-drawing.dto';
// Services
import { FileStorageService } from '../../common/file-storage/file-storage.service';
import { UuidResolverService } from '../../common/services/uuid-resolver.service';
@Injectable()
export class ShopDrawingService {
@@ -37,25 +37,10 @@ export class ShopDrawingService {
@InjectRepository(Attachment)
private attachmentRepo: Repository<Attachment>,
private fileStorageService: FileStorageService,
private dataSource: DataSource
private dataSource: DataSource,
private uuidResolver: UuidResolverService
) {}
/**
* ADR-019: Resolve projectId (INT or UUID string) to internal INT ID
*/
private async resolveProjectId(projectId: number | string): Promise<number> {
if (typeof projectId === 'number') return projectId;
const num = Number(projectId);
if (!isNaN(num)) return num;
const project = await this.dataSource.manager.findOne(Project, {
where: { uuid: projectId },
select: ['id'],
});
if (!project)
throw new NotFoundException(`Project with UUID ${projectId} not found`);
return project.id;
}
/**
* สร้าง Shop Drawing ใหม่ พร้อม Revision แรก (Rev 0)
*/
@@ -91,7 +76,7 @@ export class ShopDrawingService {
}
// ADR-019: Resolve UUID→INT
const internalProjectId = await this.resolveProjectId(
const internalProjectId = await this.uuidResolver.resolveProjectId(
createDto.projectId
);