This commit is contained in:
@@ -3,16 +3,18 @@ import {
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { TransmittalPurpose } from './create-transmittal.dto';
|
||||
|
||||
export class SearchTransmittalDto {
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
@IsNotEmpty()
|
||||
projectId!: number; // บังคับระบุ Project
|
||||
@IsUUID('all')
|
||||
@IsOptional()
|
||||
projectUuid?: string; // ADR-019: Public UUID of the project
|
||||
|
||||
/** @internal Resolved INT ID — set by controller, do NOT expose in API */
|
||||
projectId?: number;
|
||||
|
||||
@IsEnum(TransmittalPurpose)
|
||||
@IsOptional()
|
||||
|
||||
@@ -5,39 +5,59 @@ import {
|
||||
Body,
|
||||
Param,
|
||||
UseGuards,
|
||||
ParseIntPipe,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { TransmittalService } from './transmittal.service';
|
||||
import { CreateTransmittalDto } from './dto/create-transmittal.dto';
|
||||
import { SearchTransmittalDto } from './dto/search-transmittal.dto';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { RbacGuard } from '../../common/guards/rbac.guard';
|
||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
||||
import { User } from '../user/entities/user.entity';
|
||||
import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam } from '@nestjs/swagger';
|
||||
import { ParseUuidPipe } from '../../common/pipes/parse-uuid.pipe';
|
||||
import { ProjectService } from '../project/project.service';
|
||||
|
||||
@ApiTags('Transmittals')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(JwtAuthGuard, RbacGuard)
|
||||
@Controller('transmittals')
|
||||
export class TransmittalController {
|
||||
constructor(private readonly transmittalService: TransmittalService) {}
|
||||
constructor(
|
||||
private readonly transmittalService: TransmittalService,
|
||||
private readonly projectService: ProjectService
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a new Transmittal' })
|
||||
@RequirePermission('document.create')
|
||||
create(@Body() createDto: CreateTransmittalDto, @CurrentUser() user: User) {
|
||||
return this.transmittalService.create(createDto, user);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Search Transmittals' })
|
||||
findAll(@Query() searchDto: any) {
|
||||
// Using any for simplicity as I can't import SearchTransmittalDto easily without checking its export
|
||||
@RequirePermission('document.view')
|
||||
async findAll(
|
||||
@Query() searchDto: SearchTransmittalDto,
|
||||
@CurrentUser() _user: User
|
||||
) {
|
||||
// ADR-019: resolve projectUuid → internal INT projectId if needed
|
||||
if (searchDto.projectUuid) {
|
||||
const project = await this.projectService.findOneByUuid(
|
||||
searchDto.projectUuid
|
||||
);
|
||||
searchDto.projectId = project.id;
|
||||
}
|
||||
return this.transmittalService.findAll(searchDto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Get(':uuid')
|
||||
@ApiOperation({ summary: 'Get Transmittal details' })
|
||||
findOne(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.transmittalService.findOne(id);
|
||||
@ApiParam({ name: 'uuid', description: 'Transmittal UUID (from correspondences.uuid)' })
|
||||
@RequirePermission('document.view')
|
||||
findOne(@Param('uuid', ParseUuidPipe) uuid: string) {
|
||||
return this.transmittalService.findOneByUuid(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CorrespondenceStatus } from '../correspondence/entities/correspondence-
|
||||
import { TransmittalService } from './transmittal.service';
|
||||
import { TransmittalController } from './transmittal.controller';
|
||||
import { DocumentNumberingModule } from '../document-numbering/document-numbering.module';
|
||||
import { ProjectModule } from '../project/project.module';
|
||||
import { UserModule } from '../user/user.module';
|
||||
import { SearchModule } from '../search/search.module';
|
||||
|
||||
@@ -21,6 +22,7 @@ import { SearchModule } from '../search/search.module';
|
||||
CorrespondenceStatus,
|
||||
]),
|
||||
DocumentNumberingModule,
|
||||
ProjectModule,
|
||||
UserModule,
|
||||
SearchModule,
|
||||
],
|
||||
|
||||
@@ -9,7 +9,11 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { Transmittal } from './entities/transmittal.entity';
|
||||
import { TransmittalItem } from './entities/transmittal-item.entity';
|
||||
import { CreateTransmittalDto } from './dto/create-transmittal.dto';
|
||||
import {
|
||||
CreateTransmittalDto,
|
||||
TransmittalItemDto,
|
||||
} from './dto/create-transmittal.dto';
|
||||
import { SearchTransmittalDto } from './dto/search-transmittal.dto';
|
||||
import { User } from '../user/entities/user.entity';
|
||||
import { DocumentNumberingService } from '../document-numbering/services/document-numbering.service';
|
||||
import { Correspondence } from '../correspondence/entities/correspondence.entity';
|
||||
@@ -125,14 +129,7 @@ export class TransmittalService {
|
||||
|
||||
// 6. Create Items
|
||||
if (createDto.items && createDto.items.length > 0) {
|
||||
// Filter only items that are effectively correspondences (or mapped as such)
|
||||
// For now, assuming itemId refers to correspondenceId if itemType is CORRESPONDENCE
|
||||
// If itemType is DRAWING, we skip or throw error (Schema Restriction)
|
||||
const validItems = createDto.items.filter(
|
||||
(i) => i.itemType === 'CORRESPONDENCE' || i.itemType === 'DRAWING' // Temporary allow DRAWING if ID matches Correspondence? Unsafe.
|
||||
);
|
||||
|
||||
const items = createDto.items.map((item) =>
|
||||
const items = createDto.items.map((item: TransmittalItemDto) =>
|
||||
queryRunner.manager.create(TransmittalItem, {
|
||||
transmittalId: savedCorr.id,
|
||||
itemCorrespondenceId: item.itemId, // Direct mapping forced by Schema
|
||||
@@ -160,6 +157,21 @@ export class TransmittalService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ADR-019: Find Transmittal by parent Correspondence UUID (public identifier).
|
||||
* Resolves correspondence.uuid → internal correspondenceId (INT)
|
||||
*/
|
||||
async findOneByUuid(uuid: string) {
|
||||
const correspondence = await this.dataSource.manager.findOne(
|
||||
Correspondence,
|
||||
{ where: { uuid }, select: ['id'] }
|
||||
);
|
||||
if (!correspondence) {
|
||||
throw new NotFoundException(`Transmittal with UUID ${uuid} not found`);
|
||||
}
|
||||
return this.findOne(correspondence.id);
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const transmittal = await this.transmittalRepo.findOne({
|
||||
where: { correspondenceId: id },
|
||||
@@ -170,9 +182,9 @@ export class TransmittalService {
|
||||
return transmittal;
|
||||
}
|
||||
|
||||
async findAll(query: any) {
|
||||
async findAll(query: SearchTransmittalDto) {
|
||||
const { page = 1, limit = 20, projectId, search } = query;
|
||||
const skip = (page - 1) * limit;
|
||||
const skip = ((page ?? 1) - 1) * (limit ?? 20);
|
||||
|
||||
const queryBuilder = this.transmittalRepo
|
||||
.createQueryBuilder('transmittal')
|
||||
@@ -205,8 +217,14 @@ export class TransmittalService {
|
||||
.take(limit)
|
||||
.getManyAndCount();
|
||||
|
||||
// ADR-019: Map correspondence.uuid to top level for frontend convenience
|
||||
const mappedItems = items.map((t) => ({
|
||||
...t,
|
||||
uuid: t.correspondence?.uuid,
|
||||
}));
|
||||
|
||||
return {
|
||||
data: items,
|
||||
data: mappedItems,
|
||||
meta: {
|
||||
total,
|
||||
page,
|
||||
|
||||
Reference in New Issue
Block a user