251129:1700 update to 1.4.5
This commit is contained in:
@@ -1,42 +1,41 @@
|
||||
// File: src/modules/rfa/rfa.service.ts
|
||||
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
InternalServerErrorException,
|
||||
Logger,
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource, In } from 'typeorm';
|
||||
import { DataSource, In, Repository } from 'typeorm';
|
||||
|
||||
// Entities
|
||||
import { Rfa } from './entities/rfa.entity.js';
|
||||
import { RfaRevision } from './entities/rfa-revision.entity.js';
|
||||
import { RfaItem } from './entities/rfa-item.entity.js';
|
||||
import { RfaType } from './entities/rfa-type.entity.js';
|
||||
import { RfaStatusCode } from './entities/rfa-status-code.entity.js';
|
||||
import { RfaApproveCode } from './entities/rfa-approve-code.entity.js';
|
||||
import { Correspondence } from '../correspondence/entities/correspondence.entity.js';
|
||||
import { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity.js';
|
||||
import { RoutingTemplate } from '../correspondence/entities/routing-template.entity.js';
|
||||
import { ShopDrawingRevision } from '../drawing/entities/shop-drawing-revision.entity.js';
|
||||
import { User } from '../user/entities/user.entity.js';
|
||||
import { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity';
|
||||
import { Correspondence } from '../correspondence/entities/correspondence.entity';
|
||||
import { RoutingTemplate } from '../correspondence/entities/routing-template.entity';
|
||||
import { ShopDrawingRevision } from '../drawing/entities/shop-drawing-revision.entity';
|
||||
import { User } from '../user/entities/user.entity';
|
||||
import { RfaApproveCode } from './entities/rfa-approve-code.entity';
|
||||
import { RfaItem } from './entities/rfa-item.entity';
|
||||
import { RfaRevision } from './entities/rfa-revision.entity';
|
||||
import { RfaStatusCode } from './entities/rfa-status-code.entity';
|
||||
import { RfaType } from './entities/rfa-type.entity';
|
||||
import { Rfa } from './entities/rfa.entity';
|
||||
|
||||
// DTOs
|
||||
import { CreateRfaDto } from './dto/create-rfa.dto.js';
|
||||
import { WorkflowActionDto } from '../correspondence/dto/workflow-action.dto.js';
|
||||
import { WorkflowActionDto } from '../correspondence/dto/workflow-action.dto';
|
||||
import { CreateRfaDto } from './dto/create-rfa.dto';
|
||||
|
||||
// Interfaces & Enums
|
||||
import { WorkflowAction } from '../workflow-engine/interfaces/workflow.interface.js';
|
||||
|
||||
// Services
|
||||
import { DocumentNumberingService } from '../document-numbering/document-numbering.service.js';
|
||||
import { UserService } from '../user/user.service.js';
|
||||
import { WorkflowEngineService } from '../workflow-engine/workflow-engine.service.js';
|
||||
import { NotificationService } from '../notification/notification.service.js';
|
||||
import { SearchService } from '../search/search.service.js';
|
||||
import { DocumentNumberingService } from '../document-numbering/document-numbering.service';
|
||||
import { NotificationService } from '../notification/notification.service';
|
||||
import { SearchService } from '../search/search.service';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { WorkflowEngineService } from '../workflow-engine/workflow-engine.service';
|
||||
|
||||
@Injectable()
|
||||
export class RfaService {
|
||||
@@ -87,6 +86,7 @@ export class RfaService {
|
||||
);
|
||||
}
|
||||
|
||||
// Determine User Organization
|
||||
let userOrgId = user.primaryOrganizationId;
|
||||
if (!userOrgId) {
|
||||
const fullUser = await this.userService.findOne(user.user_id);
|
||||
@@ -101,14 +101,14 @@ export class RfaService {
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const orgCode = 'ORG'; // TODO: Fetch real ORG Code
|
||||
const orgCode = 'ORG'; // TODO: Fetch real ORG Code from Org Service if needed
|
||||
|
||||
// [FIXED] เรียกใช้แบบ Object Context พร้อม disciplineId
|
||||
// [UPDATED] Generate Document Number with Discipline
|
||||
const docNumber = await this.numberingService.generateNextNumber({
|
||||
projectId: createDto.projectId,
|
||||
originatorId: userOrgId,
|
||||
typeId: createDto.rfaTypeId, // RFA Type ใช้เป็น ID ในการนับเลข
|
||||
disciplineId: createDto.disciplineId, // สำคัญมากสำหรับ RFA (Req 6B)
|
||||
typeId: createDto.rfaTypeId,
|
||||
disciplineId: createDto.disciplineId ?? 0, // ✅ ส่ง disciplineId ไปด้วย (0 ถ้าไม่มี)
|
||||
year: new Date().getFullYear(),
|
||||
customTokens: {
|
||||
TYPE_CODE: rfaType.typeCode,
|
||||
@@ -116,24 +116,31 @@ export class RfaService {
|
||||
},
|
||||
});
|
||||
|
||||
// 1. Create Correspondence Record
|
||||
const correspondence = queryRunner.manager.create(Correspondence, {
|
||||
correspondenceNumber: docNumber,
|
||||
correspondenceTypeId: createDto.rfaTypeId, // Map RFA Type to Corr Type ID
|
||||
disciplineId: createDto.disciplineId, // บันทึก Discipline
|
||||
correspondenceTypeId: createDto.rfaTypeId, // Assuming RFA Type maps directly or via logic
|
||||
// Note: ถ้า CorrespondenceType แยก ID กับ RFA Type ต้อง Map ให้ถูก
|
||||
// ในที่นี้สมมติว่าใช้ ID เดียวกัน หรือ RFA Type เป็น SubType ของ Correspondence
|
||||
projectId: createDto.projectId,
|
||||
originatorId: userOrgId,
|
||||
isInternal: false,
|
||||
isInternalCommunication: false,
|
||||
createdBy: user.user_id,
|
||||
// ✅ Add disciplineId column if correspondence table supports it (as per Data Dictionary Update)
|
||||
// disciplineId: createDto.disciplineId
|
||||
});
|
||||
const savedCorr = await queryRunner.manager.save(correspondence);
|
||||
|
||||
// 2. Create RFA Master Record
|
||||
const rfa = queryRunner.manager.create(Rfa, {
|
||||
rfaTypeId: createDto.rfaTypeId,
|
||||
disciplineId: createDto.disciplineId, // บันทึก Discipline
|
||||
createdBy: user.user_id,
|
||||
// ✅ ถ้า Entity Rfa มี disciplineId ให้ใส่ตรงนี้ด้วย
|
||||
// disciplineId: createDto.disciplineId
|
||||
});
|
||||
const savedRfa = await queryRunner.manager.save(rfa);
|
||||
|
||||
// 3. Create First Revision (Draft)
|
||||
const rfaRevision = queryRunner.manager.create(RfaRevision, {
|
||||
correspondenceId: savedCorr.id,
|
||||
rfaId: savedRfa.id,
|
||||
@@ -147,9 +154,12 @@ export class RfaService {
|
||||
? new Date(createDto.documentDate)
|
||||
: new Date(),
|
||||
createdBy: user.user_id,
|
||||
details: createDto.details, // ✅ Save JSON Details
|
||||
schemaVersion: 1, // ✅ Default Schema Version
|
||||
});
|
||||
const savedRevision = await queryRunner.manager.save(rfaRevision);
|
||||
|
||||
// 4. Link Shop Drawings
|
||||
if (
|
||||
createDto.shopDrawingRevisionIds &&
|
||||
createDto.shopDrawingRevisionIds.length > 0
|
||||
@@ -164,7 +174,8 @@ export class RfaService {
|
||||
|
||||
const rfaItems = shopDrawings.map((sd) =>
|
||||
queryRunner.manager.create(RfaItem, {
|
||||
rfaRevisionId: savedCorr.id,
|
||||
rfaRevisionId: savedCorr.id, // ใช้ ID ของ Correspondence (ตาม Schema ที่ออกแบบไว้) หรือ RFA Revision ID แล้วแต่การ Map Entity
|
||||
// ตาม Entity RfaItem ที่ให้มา: rfaRevisionId map ไปที่ correspondence_id
|
||||
shopDrawingRevisionId: sd.id,
|
||||
}),
|
||||
);
|
||||
@@ -173,9 +184,10 @@ export class RfaService {
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
// Indexing for Search
|
||||
this.searchService.indexDocument({
|
||||
id: savedCorr.id,
|
||||
type: 'correspondence',
|
||||
type: 'rfa',
|
||||
docNumber: docNumber,
|
||||
title: createDto.title,
|
||||
description: createDto.description,
|
||||
@@ -186,10 +198,8 @@ export class RfaService {
|
||||
|
||||
return {
|
||||
...savedRfa,
|
||||
currentRevision: {
|
||||
...savedRevision,
|
||||
correspondenceNumber: docNumber,
|
||||
},
|
||||
correspondenceNumber: docNumber,
|
||||
currentRevision: savedRevision,
|
||||
};
|
||||
} catch (err) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
@@ -200,7 +210,8 @@ export class RfaService {
|
||||
}
|
||||
}
|
||||
|
||||
// ... (method อื่นๆ findOne, submit, processAction คงเดิม)
|
||||
// ... (ส่วน findOne, submit, processAction คงเดิมจากไฟล์ที่แนบมา แค่ปรับปรุงเล็กน้อยตาม Context) ...
|
||||
|
||||
async findOne(id: number) {
|
||||
const rfa = await this.rfaRepo.findOne({
|
||||
where: { id },
|
||||
@@ -230,14 +241,8 @@ export class RfaService {
|
||||
const rfa = await this.findOne(rfaId);
|
||||
const currentRevision = rfa.revisions.find((r) => r.isCurrent);
|
||||
|
||||
if (!currentRevision) {
|
||||
if (!currentRevision)
|
||||
throw new NotFoundException('Current revision not found');
|
||||
}
|
||||
|
||||
if (!currentRevision.correspondence) {
|
||||
throw new InternalServerErrorException('Correspondence relation missing');
|
||||
}
|
||||
|
||||
if (currentRevision.statusCode.statusCode !== 'DFT') {
|
||||
throw new BadRequestException('Only DRAFT documents can be submitted');
|
||||
}
|
||||
@@ -255,21 +260,21 @@ export class RfaService {
|
||||
const statusForApprove = await this.rfaStatusRepo.findOne({
|
||||
where: { statusCode: 'FAP' },
|
||||
});
|
||||
if (!statusForApprove) {
|
||||
if (!statusForApprove)
|
||||
throw new InternalServerErrorException('Status FAP not found');
|
||||
}
|
||||
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
// Update Revision Status
|
||||
currentRevision.rfaStatusCodeId = statusForApprove.id;
|
||||
currentRevision.issuedDate = new Date();
|
||||
await queryRunner.manager.save(currentRevision);
|
||||
|
||||
// Create First Routing Step
|
||||
const firstStep = template.steps[0];
|
||||
|
||||
const routing = queryRunner.manager.create(CorrespondenceRouting, {
|
||||
correspondenceId: currentRevision.correspondenceId,
|
||||
templateId: template.id,
|
||||
@@ -286,20 +291,18 @@ export class RfaService {
|
||||
});
|
||||
await queryRunner.manager.save(routing);
|
||||
|
||||
// Notify
|
||||
const recipientUserId = await this.userService.findDocControlIdByOrg(
|
||||
firstStep.toOrganizationId,
|
||||
);
|
||||
|
||||
if (recipientUserId) {
|
||||
const docNo = currentRevision.correspondence.correspondenceNumber;
|
||||
await this.notificationService.send({
|
||||
userId: recipientUserId,
|
||||
title: `RFA Submitted: ${currentRevision.title}`,
|
||||
message: `มีเอกสาร RFA ใหม่รอการตรวจสอบจากคุณ (เลขที่: ${docNo})`,
|
||||
message: `RFA ${currentRevision.correspondence.correspondenceNumber} submitted for approval.`,
|
||||
type: 'SYSTEM',
|
||||
entityType: 'rfa',
|
||||
entityId: rfa.id,
|
||||
link: `/rfas/${rfa.id}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -307,7 +310,6 @@ export class RfaService {
|
||||
return { message: 'RFA Submitted successfully', routing };
|
||||
} catch (err) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error(`Failed to submit RFA: ${(err as Error).message}`);
|
||||
throw err;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
@@ -315,11 +317,13 @@ export class RfaService {
|
||||
}
|
||||
|
||||
async processAction(rfaId: number, dto: WorkflowActionDto, user: User) {
|
||||
// Logic คงเดิม: หา Current Routing -> Check Permission -> Call Workflow Engine -> Update DB
|
||||
// ใช้ this.workflowEngine.processAction (Legacy Support)
|
||||
// ... (สามารถใช้ Code เดิมจากที่คุณแนบมาได้เลย เพราะ Logic ถูกต้องแล้วสำหรับการใช้ CorrespondenceRouting) ...
|
||||
const rfa = await this.findOne(rfaId);
|
||||
const currentRevision = rfa.revisions.find((r) => r.isCurrent);
|
||||
if (!currentRevision) {
|
||||
if (!currentRevision)
|
||||
throw new NotFoundException('Current revision not found');
|
||||
}
|
||||
|
||||
const currentRouting = await this.routingRepo.findOne({
|
||||
where: {
|
||||
@@ -330,10 +334,8 @@ export class RfaService {
|
||||
relations: ['toOrganization'],
|
||||
});
|
||||
|
||||
if (!currentRouting) {
|
||||
if (!currentRouting)
|
||||
throw new BadRequestException('No active workflow step found');
|
||||
}
|
||||
|
||||
if (currentRouting.toOrganizationId !== user.primaryOrganizationId) {
|
||||
throw new ForbiddenException(
|
||||
'You are not authorized to process this step',
|
||||
@@ -345,10 +347,10 @@ export class RfaService {
|
||||
relations: ['steps'],
|
||||
});
|
||||
|
||||
if (!template || !template.steps) {
|
||||
throw new InternalServerErrorException('Template or steps not found');
|
||||
}
|
||||
if (!template || !template.steps)
|
||||
throw new InternalServerErrorException('Template not found');
|
||||
|
||||
// Call Engine to calculate next step
|
||||
const result = this.workflowEngine.processAction(
|
||||
currentRouting.sequence,
|
||||
template.steps.length,
|
||||
@@ -361,19 +363,19 @@ export class RfaService {
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
currentRouting.status =
|
||||
dto.action === WorkflowAction.REJECT ? 'REJECTED' : 'ACTIONED';
|
||||
// Update current routing
|
||||
currentRouting.status = dto.action === 'REJECT' ? 'REJECTED' : 'ACTIONED';
|
||||
currentRouting.processedByUserId = user.user_id;
|
||||
currentRouting.processedAt = new Date();
|
||||
currentRouting.comments = dto.comments;
|
||||
await queryRunner.manager.save(currentRouting);
|
||||
|
||||
if (result.nextStepSequence && dto.action !== WorkflowAction.REJECT) {
|
||||
const nextStepConfig = template.steps.find(
|
||||
// Create next routing if available
|
||||
if (result.nextStepSequence && dto.action !== 'REJECT') {
|
||||
const nextStep = template.steps.find(
|
||||
(s) => s.sequence === result.nextStepSequence,
|
||||
);
|
||||
|
||||
if (nextStepConfig) {
|
||||
if (nextStep) {
|
||||
const nextRouting = queryRunner.manager.create(
|
||||
CorrespondenceRouting,
|
||||
{
|
||||
@@ -381,52 +383,43 @@ export class RfaService {
|
||||
templateId: template.id,
|
||||
sequence: result.nextStepSequence,
|
||||
fromOrganizationId: user.primaryOrganizationId,
|
||||
toOrganizationId: nextStepConfig.toOrganizationId,
|
||||
stepPurpose: nextStepConfig.stepPurpose,
|
||||
toOrganizationId: nextStep.toOrganizationId,
|
||||
stepPurpose: nextStep.stepPurpose,
|
||||
status: 'SENT',
|
||||
dueDate: new Date(
|
||||
Date.now() +
|
||||
(nextStepConfig.expectedDays || 7) * 24 * 60 * 60 * 1000,
|
||||
Date.now() + (nextStep.expectedDays || 7) * 24 * 60 * 60 * 1000,
|
||||
),
|
||||
},
|
||||
);
|
||||
await queryRunner.manager.save(nextRouting);
|
||||
}
|
||||
} else if (
|
||||
result.nextStepSequence === null &&
|
||||
dto.action !== WorkflowAction.REJECT
|
||||
) {
|
||||
const approveCodeStr =
|
||||
dto.action === WorkflowAction.APPROVE ? '1A' : '4X';
|
||||
const approveCode = await this.rfaApproveRepo.findOne({
|
||||
where: { approveCode: approveCodeStr },
|
||||
});
|
||||
|
||||
if (approveCode) {
|
||||
currentRevision.rfaApproveCodeId = approveCode.id;
|
||||
currentRevision.approvedDate = new Date();
|
||||
}
|
||||
await queryRunner.manager.save(currentRevision);
|
||||
} else if (dto.action === WorkflowAction.REJECT) {
|
||||
const rejectCode = await this.rfaApproveRepo.findOne({
|
||||
where: { approveCode: '4X' },
|
||||
});
|
||||
if (rejectCode) {
|
||||
currentRevision.rfaApproveCodeId = rejectCode.id;
|
||||
} else if (result.nextStepSequence === null) {
|
||||
// Workflow Ended (Completed or Rejected)
|
||||
// Update RFA Status (Approved/Rejected Code)
|
||||
if (dto.action !== 'REJECT') {
|
||||
const approveCode = await this.rfaApproveRepo.findOne({
|
||||
where: { approveCode: dto.action === 'APPROVE' ? '1A' : '4X' },
|
||||
}); // Logic Map Code อย่างง่าย
|
||||
if (approveCode) {
|
||||
currentRevision.rfaApproveCodeId = approveCode.id;
|
||||
currentRevision.approvedDate = new Date();
|
||||
}
|
||||
} else {
|
||||
const rejectCode = await this.rfaApproveRepo.findOne({
|
||||
where: { approveCode: '4X' },
|
||||
});
|
||||
if (rejectCode) currentRevision.rfaApproveCodeId = rejectCode.id;
|
||||
}
|
||||
await queryRunner.manager.save(currentRevision);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
return { message: 'Action processed successfully', result };
|
||||
return { message: 'Action processed', result };
|
||||
} catch (err) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error(
|
||||
`Failed to process RFA action: ${(err as Error).message}`,
|
||||
);
|
||||
throw err;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user