251123:0200 T6.1 to DO
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
IsInt,
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsArray,
|
||||
IsNotEmpty,
|
||||
IsEnum,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
// Enum นี้ควรตรงกับใน Entity หรือสร้างไฟล์ enum แยก (ในที่นี้ใส่ไว้ใน DTO เพื่อความสะดวก)
|
||||
export enum TransmittalPurpose {
|
||||
FOR_APPROVAL = 'FOR_APPROVAL',
|
||||
FOR_INFORMATION = 'FOR_INFORMATION',
|
||||
FOR_REVIEW = 'FOR_REVIEW',
|
||||
OTHER = 'OTHER',
|
||||
}
|
||||
|
||||
export class CreateTransmittalDto {
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
projectId!: number; // จำเป็นสำหรับการออกเลขที่เอกสาร (Running Number)
|
||||
|
||||
@IsEnum(TransmittalPurpose)
|
||||
@IsOptional()
|
||||
purpose?: TransmittalPurpose; // วัตถุประสงค์การส่ง
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
remarks?: string; // หมายเหตุเพิ่มเติม
|
||||
|
||||
@IsArray()
|
||||
@IsInt({ each: true })
|
||||
@IsNotEmpty()
|
||||
itemIds!: number[]; // ID ของเอกสาร (Correspondence IDs) ที่จะแนบไปใน Transmittal นี้
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
IsInt,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { TransmittalPurpose } from './create-transmittal.dto';
|
||||
|
||||
export class SearchTransmittalDto {
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
@IsNotEmpty()
|
||||
projectId!: number; // บังคับระบุ Project
|
||||
|
||||
@IsEnum(TransmittalPurpose)
|
||||
@IsOptional()
|
||||
purpose?: TransmittalPurpose;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
search?: string; // ค้นหาจากเลขที่เอกสาร หรือ remarks
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
page: number = 1;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
pageSize: number = 20;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateTransmittalDto } from './create-transmittal.dto';
|
||||
|
||||
export class UpdateTransmittalDto extends PartialType(CreateTransmittalDto) {}
|
||||
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Transmittal } from './transmittal.entity';
|
||||
import { Correspondence } from '../../correspondence/entities/correspondence.entity';
|
||||
|
||||
@Entity('transmittal_items')
|
||||
export class TransmittalItem {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column({ name: 'transmittal_id' })
|
||||
transmittalId!: number;
|
||||
|
||||
@Column({ name: 'item_correspondence_id' })
|
||||
itemCorrespondenceId!: number;
|
||||
|
||||
@Column({ default: 1 })
|
||||
quantity!: number;
|
||||
|
||||
@Column({ length: 255, nullable: true })
|
||||
remarks?: string;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(() => Transmittal, (t) => t.items, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'transmittal_id' })
|
||||
transmittal!: Transmittal;
|
||||
|
||||
@ManyToOne(() => Correspondence)
|
||||
@JoinColumn({ name: 'item_correspondence_id' })
|
||||
itemDocument!: Correspondence;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryColumn,
|
||||
Column,
|
||||
OneToOne,
|
||||
JoinColumn,
|
||||
OneToMany,
|
||||
} from 'typeorm';
|
||||
import { Correspondence } from '../../correspondence/entities/correspondence.entity';
|
||||
import { TransmittalItem } from './transmittal-item.entity';
|
||||
|
||||
@Entity('transmittals')
|
||||
export class Transmittal {
|
||||
@PrimaryColumn({ name: 'correspondence_id' })
|
||||
correspondenceId!: number;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['FOR_APPROVAL', 'FOR_INFORMATION', 'FOR_REVIEW', 'OTHER'],
|
||||
nullable: true,
|
||||
})
|
||||
purpose?: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
remarks?: string;
|
||||
|
||||
// Relations
|
||||
@OneToOne(() => Correspondence)
|
||||
@JoinColumn({ name: 'correspondence_id' })
|
||||
correspondence!: Correspondence;
|
||||
|
||||
@OneToMany(() => TransmittalItem, (item) => item.transmittal, {
|
||||
cascade: true,
|
||||
})
|
||||
items!: TransmittalItem[];
|
||||
}
|
||||
53
backend/src/modules/transmittal/transmittal.controller.ts
Normal file
53
backend/src/modules/transmittal/transmittal.controller.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
ParseIntPipe,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
|
||||
import { TransmittalService } from './transmittal.service';
|
||||
import { CreateTransmittalDto } from './dto/create-transmittal.dto';
|
||||
import { SearchTransmittalDto } from './dto/search-transmittal.dto'; // เดี๋ยวสร้าง DTO นี้เพิ่มให้ครับถ้ายังไม่มี
|
||||
import { User } from '../user/entities/user.entity';
|
||||
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { RbacGuard } from '../../common/guards/rbac.guard';
|
||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
||||
import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
||||
|
||||
@ApiTags('Transmittals')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, RbacGuard)
|
||||
@Controller('transmittals')
|
||||
export class TransmittalController {
|
||||
constructor(private readonly transmittalService: TransmittalService) {}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create new Transmittal' })
|
||||
@RequirePermission('transmittal.create') // สิทธิ์ ID 40
|
||||
create(@Body() createDto: CreateTransmittalDto, @CurrentUser() user: User) {
|
||||
return this.transmittalService.create(createDto, user);
|
||||
}
|
||||
|
||||
// เพิ่ม Endpoint พื้นฐานสำหรับการค้นหา (Optional)
|
||||
/*
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Search Transmittals' })
|
||||
@RequirePermission('document.view')
|
||||
findAll(@Query() searchDto: SearchTransmittalDto) {
|
||||
// return this.transmittalService.findAll(searchDto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get Transmittal details' })
|
||||
@RequirePermission('document.view')
|
||||
findOne(@Param('id', ParseIntPipe) id: number) {
|
||||
// return this.transmittalService.findOne(id);
|
||||
}
|
||||
*/
|
||||
}
|
||||
19
backend/src/modules/transmittal/transmittal.module.ts
Normal file
19
backend/src/modules/transmittal/transmittal.module.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Transmittal } from './entities/transmittal.entity';
|
||||
import { TransmittalItem } from './entities/transmittal-item.entity';
|
||||
import { Correspondence } from '../correspondence/entities/correspondence.entity';
|
||||
import { TransmittalService } from './transmittal.service';
|
||||
import { TransmittalController } from './transmittal.controller';
|
||||
import { DocumentNumberingModule } from '../document-numbering/document-numbering.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Transmittal, TransmittalItem, Correspondence]),
|
||||
DocumentNumberingModule,
|
||||
],
|
||||
controllers: [TransmittalController],
|
||||
providers: [TransmittalService],
|
||||
exports: [TransmittalService],
|
||||
})
|
||||
export class TransmittalModule {}
|
||||
95
backend/src/modules/transmittal/transmittal.service.ts
Normal file
95
backend/src/modules/transmittal/transmittal.service.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource, In } from 'typeorm';
|
||||
|
||||
import { Transmittal } from './entities/transmittal.entity';
|
||||
import { TransmittalItem } from './entities/transmittal-item.entity';
|
||||
import { Correspondence } from '../correspondence/entities/correspondence.entity';
|
||||
import { CreateTransmittalDto } from './dto/create-transmittal.dto'; // ต้องสร้าง DTO
|
||||
import { User } from '../user/entities/user.entity';
|
||||
import { DocumentNumberingService } from '../document-numbering/document-numbering.service';
|
||||
|
||||
@Injectable()
|
||||
export class TransmittalService {
|
||||
constructor(
|
||||
@InjectRepository(Transmittal)
|
||||
private transmittalRepo: Repository<Transmittal>,
|
||||
@InjectRepository(TransmittalItem)
|
||||
private transmittalItemRepo: Repository<TransmittalItem>,
|
||||
@InjectRepository(Correspondence)
|
||||
private correspondenceRepo: Repository<Correspondence>,
|
||||
private numberingService: DocumentNumberingService,
|
||||
private dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
async create(createDto: CreateTransmittalDto, user: User) {
|
||||
// ✅ FIX: ตรวจสอบว่า User มีสังกัดองค์กรหรือไม่
|
||||
if (!user.primaryOrganizationId) {
|
||||
throw new BadRequestException(
|
||||
'User must belong to an organization to create documents',
|
||||
);
|
||||
}
|
||||
const userOrgId = user.primaryOrganizationId; // TypeScript จะรู้ว่าเป็น number แล้ว
|
||||
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
// 1. Generate Document Number
|
||||
const transmittalTypeId = 3; // TODO: ควรดึง ID จริงจาก DB หรือ Config
|
||||
const orgCode = 'ORG'; // TODO: Fetch real ORG Code
|
||||
|
||||
const docNumber = await this.numberingService.generateNextNumber(
|
||||
createDto.projectId,
|
||||
userOrgId, // ✅ ส่งค่าที่เช็คแล้ว
|
||||
transmittalTypeId,
|
||||
new Date().getFullYear(),
|
||||
{ TYPE_CODE: 'TR', ORG_CODE: orgCode },
|
||||
);
|
||||
|
||||
// 2. Create Correspondence (Header)
|
||||
const correspondence = queryRunner.manager.create(Correspondence, {
|
||||
correspondenceNumber: docNumber,
|
||||
correspondenceTypeId: transmittalTypeId,
|
||||
projectId: createDto.projectId,
|
||||
originatorId: userOrgId, // ✅ ส่งค่าที่เช็คแล้ว
|
||||
isInternal: false,
|
||||
createdBy: user.user_id,
|
||||
});
|
||||
const savedCorr = await queryRunner.manager.save(correspondence);
|
||||
|
||||
// 3. Create Transmittal (Detail)
|
||||
const transmittal = queryRunner.manager.create(Transmittal, {
|
||||
correspondenceId: savedCorr.id,
|
||||
purpose: createDto.purpose,
|
||||
remarks: createDto.remarks,
|
||||
});
|
||||
await queryRunner.manager.save(transmittal);
|
||||
|
||||
// 4. Link Items (Documents being sent)
|
||||
if (createDto.itemIds && createDto.itemIds.length > 0) {
|
||||
const items = createDto.itemIds.map((itemId) =>
|
||||
queryRunner.manager.create(TransmittalItem, {
|
||||
transmittalId: savedCorr.id,
|
||||
itemCorrespondenceId: itemId,
|
||||
quantity: 1,
|
||||
}),
|
||||
);
|
||||
await queryRunner.manager.save(items);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
return { ...savedCorr, transmittal };
|
||||
} catch (err) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw err;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user