This commit is contained in:
@@ -7,7 +7,9 @@ import {
|
||||
JoinColumn,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
OneToOne,
|
||||
} from 'typeorm';
|
||||
import { RfaRevision } from '../../rfa/entities/rfa-revision.entity';
|
||||
import { Correspondence } from './correspondence.entity';
|
||||
import { CorrespondenceStatus } from './correspondence-status.entity';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
@@ -107,4 +109,8 @@ export class CorrespondenceRevision {
|
||||
@ManyToOne(() => User)
|
||||
@JoinColumn({ name: 'created_by' })
|
||||
creator?: User;
|
||||
|
||||
// Added inverse relation for CTI mapping to subclasses (RFA)
|
||||
@OneToOne(() => RfaRevision, (rfaRev) => rfaRev.correspondenceRevision)
|
||||
rfaRevision?: RfaRevision;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ export class ImportCorrespondenceDto {
|
||||
@IsOptional()
|
||||
received_date?: string;
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
discipline_id?: number;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
body?: string;
|
||||
|
||||
@@ -127,22 +127,25 @@ export class MigrationService {
|
||||
correspondenceNumber: dto.document_number,
|
||||
correspondenceTypeId: typeId,
|
||||
projectId: project.id,
|
||||
disciplineId: dto.discipline_id || undefined,
|
||||
isInternal: false,
|
||||
createdBy: userId,
|
||||
});
|
||||
await queryRunner.manager.save(correspondence);
|
||||
} else if (dto.discipline_id && !correspondence.disciplineId) {
|
||||
// อัพเดต discipline_id หากเอกสารเดิมยังไม่มี
|
||||
correspondence.disciplineId = dto.discipline_id;
|
||||
await queryRunner.manager.save(correspondence);
|
||||
}
|
||||
|
||||
// 4. File Handling
|
||||
// We will map the source file and create an Attachment record using FileStorageService
|
||||
// For legacy migrations, we pass document_number mapping logic or basic processing
|
||||
let attachmentId: number | null = null;
|
||||
if (dto.source_file_path) {
|
||||
try {
|
||||
const attachment = await this.fileStorageService.importStagingFile(
|
||||
dto.source_file_path,
|
||||
userId,
|
||||
{ documentType: dto.category } // use category from DTO directly
|
||||
{ documentType: dto.category }
|
||||
);
|
||||
attachmentId = attachment.id;
|
||||
} catch (fileError: unknown) {
|
||||
@@ -163,7 +166,6 @@ export class MigrationService {
|
||||
}
|
||||
);
|
||||
|
||||
// Determine revision number. Support mapping multiple batches to the same document number by incrementing revision.
|
||||
const revNum = revisionCount;
|
||||
const revision = queryRunner.manager.create(CorrespondenceRevision, {
|
||||
correspondenceId: correspondence.id,
|
||||
@@ -173,15 +175,16 @@ export class MigrationService {
|
||||
statusId: status.id,
|
||||
subject: dto.title,
|
||||
description: 'Migrated from legacy system via Auto Ingest',
|
||||
body: dto.body || undefined, // Map from DTO
|
||||
details: {
|
||||
...dto.details,
|
||||
ai_confidence: dto.ai_confidence,
|
||||
ai_issues: dto.ai_issues as unknown,
|
||||
source_file_path: dto.source_file_path,
|
||||
attachment_id: attachmentId, // Link attachment ID if successful
|
||||
attachment_id: attachmentId,
|
||||
},
|
||||
schemaVersion: 1,
|
||||
createdBy: userId, // Bot ID
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
if (revisionCount > 0) {
|
||||
|
||||
@@ -1,39 +1,27 @@
|
||||
// File: src/modules/rfa/entities/rfa-revision.entity.ts
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
PrimaryColumn,
|
||||
OneToOne,
|
||||
} from 'typeorm';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { CorrespondenceRevision } from '../../correspondence/entities/correspondence-revision.entity';
|
||||
import { RfaApproveCode } from './rfa-approve-code.entity';
|
||||
import { RfaItem } from './rfa-item.entity';
|
||||
import { RfaStatusCode } from './rfa-status-code.entity';
|
||||
import { RfaWorkflow } from './rfa-workflow.entity';
|
||||
import { Rfa } from './rfa.entity';
|
||||
|
||||
@Entity('rfa_revisions')
|
||||
@Unique(['rfaId', 'revisionNumber'])
|
||||
@Unique(['rfaId', 'isCurrent'])
|
||||
export class RfaRevision {
|
||||
@PrimaryGeneratedColumn()
|
||||
@PrimaryColumn()
|
||||
id!: number;
|
||||
|
||||
@Column({ name: 'rfa_id' })
|
||||
rfaId!: number;
|
||||
|
||||
@Column({ name: 'revision_number' })
|
||||
revisionNumber!: number;
|
||||
|
||||
@Column({ name: 'revision_label', length: 10, nullable: true })
|
||||
revisionLabel?: string;
|
||||
|
||||
@Column({ name: 'is_current', default: false })
|
||||
isCurrent!: boolean;
|
||||
@OneToOne(() => CorrespondenceRevision, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'id' })
|
||||
correspondenceRevision!: CorrespondenceRevision;
|
||||
|
||||
@Column({ name: 'rfa_status_code_id' })
|
||||
rfaStatusCodeId!: number;
|
||||
@@ -41,33 +29,9 @@ export class RfaRevision {
|
||||
@Column({ name: 'rfa_approve_code_id', nullable: true })
|
||||
rfaApproveCodeId?: number;
|
||||
|
||||
@Column({ length: 500 })
|
||||
subject!: string;
|
||||
|
||||
@Column({ name: 'document_date', type: 'date', nullable: true })
|
||||
documentDate?: Date;
|
||||
|
||||
@Column({ name: 'issued_date', type: 'date', nullable: true })
|
||||
issuedDate?: Date;
|
||||
|
||||
@Column({ name: 'received_date', type: 'datetime', nullable: true })
|
||||
receivedDate?: Date;
|
||||
|
||||
@Column({ name: 'due_date', type: 'datetime', nullable: true })
|
||||
dueDate?: Date;
|
||||
|
||||
@Column({ name: 'approved_date', type: 'date', nullable: true })
|
||||
approvedDate?: Date;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
body?: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
remarks?: string;
|
||||
|
||||
// --- JSON & Schema Section ---
|
||||
|
||||
@Column({ type: 'json', nullable: true })
|
||||
@@ -87,23 +51,8 @@ export class RfaRevision {
|
||||
})
|
||||
vRefDrawingCount?: number;
|
||||
|
||||
// --- Timestamp ---
|
||||
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
createdAt!: Date;
|
||||
|
||||
@Column({ name: 'created_by', nullable: true })
|
||||
createdBy?: number;
|
||||
|
||||
@Column({ name: 'updated_by', nullable: true })
|
||||
updatedBy?: number;
|
||||
|
||||
// --- Relations ---
|
||||
|
||||
@ManyToOne(() => Rfa)
|
||||
@JoinColumn({ name: 'rfa_id' })
|
||||
rfa!: Rfa;
|
||||
|
||||
@ManyToOne(() => RfaStatusCode)
|
||||
@JoinColumn({ name: 'rfa_status_code_id' })
|
||||
statusCode!: RfaStatusCode;
|
||||
@@ -112,10 +61,6 @@ export class RfaRevision {
|
||||
@JoinColumn({ name: 'rfa_approve_code_id' })
|
||||
approveCode?: RfaApproveCode;
|
||||
|
||||
@ManyToOne(() => User)
|
||||
@JoinColumn({ name: 'created_by' })
|
||||
creator?: User;
|
||||
|
||||
@OneToMany(() => RfaItem, (item) => item.rfaRevision, { cascade: true })
|
||||
items!: RfaItem[];
|
||||
|
||||
|
||||
@@ -5,14 +5,12 @@ import {
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryColumn,
|
||||
OneToOne,
|
||||
} from 'typeorm';
|
||||
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { Correspondence } from '../../correspondence/entities/correspondence.entity'; // Import
|
||||
import { RfaRevision } from './rfa-revision.entity';
|
||||
import { RfaType } from './rfa-type.entity';
|
||||
|
||||
@Entity('rfas')
|
||||
@@ -45,6 +43,5 @@ export class Rfa {
|
||||
@JoinColumn({ name: 'created_by' })
|
||||
creator?: User;
|
||||
|
||||
@OneToMany(() => RfaRevision, (revision) => revision.rfa)
|
||||
revisions!: RfaRevision[];
|
||||
// Revisions are accessed via correspondence.revisions -> rfaRevision
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { RfaApproveCode } from './entities/rfa-approve-code.entity';
|
||||
import { RfaRevision } from './entities/rfa-revision.entity';
|
||||
import { RfaStatusCode } from './entities/rfa-status-code.entity';
|
||||
import { Rfa } from './entities/rfa.entity';
|
||||
import { CorrespondenceRevision } from '../correspondence/entities/correspondence-revision.entity';
|
||||
|
||||
// DTOs
|
||||
import { WorkflowTransitionDto } from '../workflow-engine/dto/workflow-transition.dto';
|
||||
@@ -27,6 +28,8 @@ export class RfaWorkflowService {
|
||||
private readonly rfaRepo: Repository<Rfa>,
|
||||
@InjectRepository(RfaRevision)
|
||||
private readonly revisionRepo: Repository<RfaRevision>,
|
||||
@InjectRepository(CorrespondenceRevision)
|
||||
private readonly corrRevisionRepo: Repository<CorrespondenceRevision>,
|
||||
@InjectRepository(RfaStatusCode)
|
||||
private readonly statusRepo: Repository<RfaStatusCode>,
|
||||
@InjectRepository(RfaApproveCode)
|
||||
@@ -44,16 +47,16 @@ export class RfaWorkflowService {
|
||||
|
||||
try {
|
||||
// 1. ดึงข้อมูล Revision ปัจจุบัน
|
||||
const revision = await this.revisionRepo.findOne({
|
||||
where: { id: rfaId, isCurrent: true },
|
||||
const corrRevision = await this.corrRevisionRepo.findOne({
|
||||
where: { correspondenceId: rfaId, isCurrent: true },
|
||||
relations: [
|
||||
'rfa',
|
||||
'rfa.correspondence',
|
||||
'rfa.correspondence.discipline',
|
||||
'rfaRevision',
|
||||
'correspondence',
|
||||
'correspondence.discipline',
|
||||
],
|
||||
});
|
||||
|
||||
if (!revision) {
|
||||
if (!corrRevision || !corrRevision.rfaRevision) {
|
||||
throw new NotFoundException(
|
||||
`Current Revision for RFA ID ${rfaId} not found`
|
||||
);
|
||||
@@ -61,8 +64,8 @@ export class RfaWorkflowService {
|
||||
|
||||
// 2. สร้าง Context (ข้อมูลประกอบการตัดสินใจ)
|
||||
const context = {
|
||||
rfaType: revision.rfa.rfaTypeId,
|
||||
discipline: revision.rfa.correspondence?.discipline,
|
||||
rfaType: corrRevision.correspondence?.correspondenceTypeId,
|
||||
discipline: corrRevision.correspondence?.discipline,
|
||||
ownerId: userId,
|
||||
// อาจเพิ่มเงื่อนไขอื่นๆ เช่น จำนวนวัน, ความเร่งด่วน
|
||||
};
|
||||
@@ -72,7 +75,7 @@ export class RfaWorkflowService {
|
||||
const instance = await this.workflowEngine.createInstance(
|
||||
this.WORKFLOW_CODE,
|
||||
'rfa_revision',
|
||||
revision.id.toString(),
|
||||
corrRevision.id.toString(),
|
||||
context
|
||||
);
|
||||
|
||||
@@ -87,7 +90,7 @@ export class RfaWorkflowService {
|
||||
|
||||
// 5. Sync สถานะกลับตาราง RFA Revision
|
||||
await this.syncStatus(
|
||||
revision,
|
||||
corrRevision.rfaRevision,
|
||||
transitionResult.nextState,
|
||||
undefined,
|
||||
queryRunner
|
||||
@@ -132,13 +135,13 @@ export class RfaWorkflowService {
|
||||
// 2. Sync สถานะกลับตารางเดิม
|
||||
const instance = await this.workflowEngine.getInstanceById(instanceId);
|
||||
if (instance && instance.entityType === 'rfa_revision') {
|
||||
const revision = await this.revisionRepo.findOne({
|
||||
const rfaRev = await this.revisionRepo.findOne({
|
||||
where: { id: parseInt(instance.entityId) },
|
||||
});
|
||||
if (revision) {
|
||||
if (rfaRev) {
|
||||
// เช็คว่า Action นี้มีการระบุ Approve Code มาใน Payload หรือไม่ (เช่น '1A', '3R')
|
||||
const approveCodeStr = dto.payload?.approveCode;
|
||||
await this.syncStatus(revision, result.nextState, approveCodeStr);
|
||||
await this.syncStatus(rfaRev, result.nextState, approveCodeStr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
// Entities
|
||||
import { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity';
|
||||
import { Correspondence } from '../correspondence/entities/correspondence.entity';
|
||||
import { CorrespondenceRevision } from '../correspondence/entities/correspondence-revision.entity';
|
||||
import { CorrespondenceStatus } from '../correspondence/entities/correspondence-status.entity';
|
||||
import { CorrespondenceRecipient } from '../correspondence/entities/correspondence-recipient.entity';
|
||||
import { RoutingTemplate } from '../correspondence/entities/routing-template.entity';
|
||||
import { RoutingTemplateStep } from '../correspondence/entities/routing-template-step.entity';
|
||||
@@ -41,6 +43,8 @@ import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module'
|
||||
RfaStatusCode,
|
||||
RfaApproveCode,
|
||||
Correspondence,
|
||||
CorrespondenceRevision,
|
||||
CorrespondenceStatus,
|
||||
ShopDrawingRevision,
|
||||
RfaWorkflow,
|
||||
RfaWorkflowTemplate,
|
||||
|
||||
@@ -14,6 +14,8 @@ import { DataSource, In, Repository } from 'typeorm';
|
||||
// Entities
|
||||
import { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity';
|
||||
import { Correspondence } from '../correspondence/entities/correspondence.entity';
|
||||
import { CorrespondenceRevision } from '../correspondence/entities/correspondence-revision.entity';
|
||||
import { CorrespondenceStatus } from '../correspondence/entities/correspondence-status.entity';
|
||||
import { RoutingTemplate } from '../correspondence/entities/routing-template.entity';
|
||||
import { RoutingTemplateStep } from '../correspondence/entities/routing-template-step.entity';
|
||||
import { ShopDrawingRevision } from '../drawing/entities/shop-drawing-revision.entity';
|
||||
@@ -54,6 +56,10 @@ export class RfaService {
|
||||
private correspondenceRepo: Repository<Correspondence>,
|
||||
@InjectRepository(RfaType)
|
||||
private rfaTypeRepo: Repository<RfaType>,
|
||||
@InjectRepository(CorrespondenceRevision)
|
||||
private corrRevRepo: Repository<CorrespondenceRevision>,
|
||||
@InjectRepository(CorrespondenceStatus)
|
||||
private corrStatusRepo: Repository<CorrespondenceStatus>,
|
||||
@InjectRepository(RfaStatusCode)
|
||||
private rfaStatusRepo: Repository<RfaStatusCode>,
|
||||
@InjectRepository(RfaApproveCode)
|
||||
@@ -120,6 +126,18 @@ export class RfaService {
|
||||
},
|
||||
});
|
||||
|
||||
// Get Generic Draft Status for Correspondence
|
||||
const corrStatusDraft = await queryRunner.manager.findOne(
|
||||
CorrespondenceStatus,
|
||||
{
|
||||
where: { statusCode: 'DRAFT' },
|
||||
}
|
||||
);
|
||||
if (!corrStatusDraft)
|
||||
throw new InternalServerErrorException(
|
||||
'Correspondence Status DRAFT not found'
|
||||
);
|
||||
|
||||
// 1. Create Correspondence Record
|
||||
const correspondence = queryRunner.manager.create(Correspondence, {
|
||||
correspondenceNumber: docNumber.number,
|
||||
@@ -134,20 +152,19 @@ export class RfaService {
|
||||
|
||||
// 2. Create Rfa Master Record
|
||||
const rfa = queryRunner.manager.create(Rfa, {
|
||||
id: savedCorr.id, // ✅ CTI Key share
|
||||
rfaTypeId: createDto.rfaTypeId,
|
||||
createdBy: user.user_id,
|
||||
disciplineId: createDto.disciplineId, // ✅ Add disciplineId
|
||||
});
|
||||
const savedRfa = await queryRunner.manager.save(rfa);
|
||||
|
||||
// 3. Create First Revision (Draft)
|
||||
const rfaRevision = queryRunner.manager.create(RfaRevision, {
|
||||
// 3. Create First Correspondence Revision
|
||||
const corrRevision = queryRunner.manager.create(CorrespondenceRevision, {
|
||||
correspondenceId: savedCorr.id,
|
||||
rfaId: savedRfa.id,
|
||||
revisionNumber: 0,
|
||||
revisionLabel: '0',
|
||||
isCurrent: true,
|
||||
rfaStatusCodeId: statusDraft.id,
|
||||
statusId: corrStatusDraft.id,
|
||||
subject: createDto.subject,
|
||||
body: createDto.body,
|
||||
remarks: createDto.remarks,
|
||||
@@ -157,6 +174,14 @@ export class RfaService {
|
||||
: new Date(),
|
||||
dueDate: createDto.dueDate ? new Date(createDto.dueDate) : undefined,
|
||||
createdBy: user.user_id,
|
||||
schemaVersion: 1,
|
||||
});
|
||||
const savedCorrRev = await queryRunner.manager.save(corrRevision);
|
||||
|
||||
// 4. Create First RFA Revision (CTI Extends CorrespondenceRevision)
|
||||
const rfaRevision = queryRunner.manager.create(RfaRevision, {
|
||||
id: savedCorrRev.id, // ✅ Matches correspondence revision id
|
||||
rfaStatusCodeId: statusDraft.id,
|
||||
details: createDto.details,
|
||||
schemaVersion: 1,
|
||||
});
|
||||
@@ -246,11 +271,12 @@ export class RfaService {
|
||||
const queryBuilder = this.rfaRepo
|
||||
.createQueryBuilder('rfa')
|
||||
.leftJoinAndSelect('rfa.correspondence', 'corr')
|
||||
.leftJoinAndSelect('rfa.revisions', 'rev')
|
||||
.leftJoinAndSelect('corr.revisions', 'corrRev')
|
||||
.leftJoinAndSelect('corrRev.rfaRevision', 'rfaRev')
|
||||
.leftJoinAndSelect('corr.project', 'project')
|
||||
.leftJoinAndSelect('corr.discipline', 'discipline')
|
||||
.leftJoinAndSelect('rev.statusCode', 'status')
|
||||
.leftJoinAndSelect('rev.items', 'items')
|
||||
.leftJoinAndSelect('rfaRev.statusCode', 'status')
|
||||
.leftJoinAndSelect('rfaRev.items', 'items')
|
||||
.leftJoinAndSelect('items.shopDrawingRevision', 'sdRev')
|
||||
.leftJoinAndSelect('sdRev.attachments', 'attachments');
|
||||
|
||||
@@ -258,9 +284,11 @@ export class RfaService {
|
||||
const revStatus = query.revisionStatus || 'CURRENT';
|
||||
|
||||
if (revStatus === 'CURRENT') {
|
||||
queryBuilder.where('rev.isCurrent = :isCurrent', { isCurrent: true });
|
||||
queryBuilder.where('corrRev.isCurrent = :isCurrent', { isCurrent: true });
|
||||
} else if (revStatus === 'OLD') {
|
||||
queryBuilder.where('rev.isCurrent = :isCurrent', { isCurrent: false });
|
||||
queryBuilder.where('corrRev.isCurrent = :isCurrent', {
|
||||
isCurrent: false,
|
||||
});
|
||||
}
|
||||
// If 'ALL', no filter
|
||||
|
||||
@@ -274,7 +302,7 @@ export class RfaService {
|
||||
|
||||
if (search) {
|
||||
queryBuilder.andWhere(
|
||||
'(corr.correspondenceNumber LIKE :search OR rev.subject LIKE :search)',
|
||||
'(corr.correspondenceNumber LIKE :search OR corrRev.subject LIKE :search)',
|
||||
{ search: `%${search}%` }
|
||||
);
|
||||
}
|
||||
@@ -289,8 +317,20 @@ export class RfaService {
|
||||
`[DEBUG] RFA findAll: Found ${total} items. Query: ${JSON.stringify(query)}`
|
||||
);
|
||||
|
||||
// Map `revisions` property back to the expected payload for the frontend
|
||||
const mappedItems = items.map((rfa) => {
|
||||
const mappedRfa = { ...rfa } as any;
|
||||
mappedRfa.revisions =
|
||||
rfa.correspondence?.revisions?.map((cr) => ({
|
||||
...cr,
|
||||
...(cr.rfaRevision || {}),
|
||||
id: cr.rfaRevision?.id || cr.id,
|
||||
})) || [];
|
||||
return mappedRfa;
|
||||
});
|
||||
|
||||
return {
|
||||
data: items,
|
||||
data: mappedItems,
|
||||
meta: {
|
||||
total,
|
||||
page,
|
||||
@@ -300,21 +340,22 @@ export class RfaService {
|
||||
};
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
async findOne(id: number, rawEntities = false) {
|
||||
const rfa = await this.rfaRepo.findOne({
|
||||
where: { id },
|
||||
relations: [
|
||||
'correspondence', // ✅ Add relation to master correspondence
|
||||
'correspondence',
|
||||
'rfaType',
|
||||
'revisions',
|
||||
'revisions.statusCode',
|
||||
'revisions.approveCode',
|
||||
'revisions.items',
|
||||
'revisions.items.shopDrawingRevision',
|
||||
'revisions.items.shopDrawingRevision.shopDrawing',
|
||||
'correspondence.revisions',
|
||||
'correspondence.revisions.rfaRevision',
|
||||
'correspondence.revisions.rfaRevision.statusCode',
|
||||
'correspondence.revisions.rfaRevision.approveCode',
|
||||
'correspondence.revisions.rfaRevision.items',
|
||||
'correspondence.revisions.rfaRevision.items.shopDrawingRevision',
|
||||
'correspondence.revisions.rfaRevision.items.shopDrawingRevision.shopDrawing',
|
||||
],
|
||||
order: {
|
||||
revisions: { revisionNumber: 'DESC' },
|
||||
correspondence: { revisions: { revisionNumber: 'DESC' } },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -322,16 +363,33 @@ export class RfaService {
|
||||
throw new NotFoundException(`RFA ID ${id} not found`);
|
||||
}
|
||||
|
||||
return rfa;
|
||||
if (rawEntities) {
|
||||
return rfa;
|
||||
}
|
||||
|
||||
// Map to structure expected by frontend DTO
|
||||
const mappedRfa = { ...rfa } as any;
|
||||
mappedRfa.revisions =
|
||||
rfa.correspondence?.revisions?.map((cr) => ({
|
||||
...cr,
|
||||
...(cr.rfaRevision || {}),
|
||||
id: cr.rfaRevision?.id || cr.id,
|
||||
})) || [];
|
||||
|
||||
return mappedRfa;
|
||||
}
|
||||
|
||||
async submit(rfaId: number, templateId: number, user: User) {
|
||||
const rfa = await this.findOne(rfaId);
|
||||
const currentRevision = rfa.revisions.find((r) => r.isCurrent);
|
||||
|
||||
if (!currentRevision)
|
||||
const rfa = await this.findOne(rfaId, true);
|
||||
const currentCorrRev = rfa.correspondence?.revisions?.find(
|
||||
(r: any) => r.isCurrent
|
||||
);
|
||||
if (!currentCorrRev || !currentCorrRev.rfaRevision)
|
||||
throw new NotFoundException('Current revision not found');
|
||||
if (currentRevision.statusCode.statusCode !== 'DFT') {
|
||||
|
||||
const currentRfaRev = currentCorrRev.rfaRevision;
|
||||
|
||||
if (currentRfaRev.statusCode.statusCode !== 'DFT') {
|
||||
throw new BadRequestException('Only DRAFT documents can be submitted');
|
||||
}
|
||||
|
||||
@@ -366,9 +424,10 @@ export class RfaService {
|
||||
|
||||
try {
|
||||
// Update Revision Status
|
||||
currentRevision.rfaStatusCodeId = statusForApprove.id;
|
||||
currentRevision.issuedDate = new Date();
|
||||
await queryRunner.manager.save(currentRevision);
|
||||
currentRfaRev.rfaStatusCodeId = statusForApprove.id;
|
||||
currentCorrRev.issuedDate = new Date();
|
||||
await queryRunner.manager.save(currentRfaRev);
|
||||
await queryRunner.manager.save(currentCorrRev);
|
||||
|
||||
// Create First Routing Step
|
||||
const firstStep = steps[0];
|
||||
@@ -395,7 +454,7 @@ export class RfaService {
|
||||
if (recipientUserId) {
|
||||
await this.notificationService.send({
|
||||
userId: recipientUserId,
|
||||
title: `RFA Submitted: ${currentRevision.subject}`,
|
||||
title: `RFA Submitted: ${currentCorrRev.subject}`,
|
||||
message: `RFA ${rfa.correspondence.correspondenceNumber} submitted for approval.`,
|
||||
type: 'SYSTEM',
|
||||
entityType: 'rfa',
|
||||
@@ -415,13 +474,15 @@ 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)
|
||||
const rfa = await this.findOne(rfaId, true);
|
||||
const currentCorrRev = rfa.correspondence?.revisions?.find(
|
||||
(r: any) => r.isCurrent
|
||||
);
|
||||
if (!currentCorrRev || !currentCorrRev.rfaRevision)
|
||||
throw new NotFoundException('Current revision not found');
|
||||
|
||||
const currentRfaRev = currentCorrRev.rfaRevision;
|
||||
|
||||
const currentRouting = await this.routingRepo.findOne({
|
||||
where: {
|
||||
correspondenceId: rfa.correspondence.id, // ✅ Use master correspondence id
|
||||
@@ -509,16 +570,16 @@ export class RfaService {
|
||||
},
|
||||
}); // Logic Map Code อย่างง่าย
|
||||
if (approveCode) {
|
||||
currentRevision.rfaApproveCodeId = approveCode.id;
|
||||
currentRevision.approvedDate = new Date();
|
||||
currentRfaRev.rfaApproveCodeId = approveCode.id;
|
||||
currentRfaRev.approvedDate = new Date();
|
||||
}
|
||||
} else {
|
||||
const rejectCode = await this.rfaApproveRepo.findOne({
|
||||
where: { approveCode: '4X' },
|
||||
});
|
||||
if (rejectCode) currentRevision.rfaApproveCodeId = rejectCode.id;
|
||||
if (rejectCode) currentRfaRev.rfaApproveCodeId = rejectCode.id;
|
||||
}
|
||||
await queryRunner.manager.save(currentRevision);
|
||||
await queryRunner.manager.save(currentRfaRev);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
const fs = require('fs');
|
||||
const config = $('Set Configuration').first().json.config;
|
||||
|
||||
// Check file mount and inputs
|
||||
try {
|
||||
if (!fs.existsSync(config.EXCEL_FILE)) {
|
||||
throw new Error(`Excel file not found at: ${config.EXCEL_FILE}`);
|
||||
}
|
||||
if (!fs.existsSync(config.SOURCE_PDF_DIR)) {
|
||||
throw new Error(`PDF Source directory not found at: ${config.SOURCE_PDF_DIR}`);
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(config.SOURCE_PDF_DIR);
|
||||
|
||||
// Check write permission to log path
|
||||
fs.writeFileSync(`${config.LOG_PATH}/.preflight_ok`, new Date().toISOString());
|
||||
|
||||
// Grab categories out of the previous node (Fetch Categories) if available
|
||||
// otherwise use fallback array
|
||||
let categories = ['Correspondence','RFA','Drawing','Transmittal','Report','Other'];
|
||||
try {
|
||||
const upstreamData = $('Fetch Categories').first()?.json?.data;
|
||||
if (upstreamData && Array.isArray(upstreamData)) {
|
||||
categories = upstreamData.map(c => c.name || c.type || c); // very loose mapping depending on API response
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
// Grab existing tags from Fetch Tags node
|
||||
let existingTags = [];
|
||||
try {
|
||||
const tagData = $('Fetch Tags').first()?.json?.data || [];
|
||||
existingTags = Array.isArray(tagData) ? tagData.map(t => t.tag_name || t.name || '').filter(Boolean) : [];
|
||||
} catch(e) {}
|
||||
|
||||
return [{ json: {
|
||||
preflight_ok: true,
|
||||
pdf_count_in_source: files.length,
|
||||
excel_target: config.EXCEL_FILE,
|
||||
system_categories: categories,
|
||||
existing_tags: existingTags,
|
||||
timestamp: new Date().toISOString()
|
||||
}}];
|
||||
} catch (err) {
|
||||
throw new Error(`Pre-flight check failed: ${err.message}`);
|
||||
}
|
||||
@@ -622,47 +622,28 @@ SET NULL - INDEX (is_active) - INDEX (email) ** Relationships **: - Parent: orga
|
||||
|
||||
**Purpose**: Child table storing revision history of RFAs (1:N)
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| ----------- | --------- | --------------------------- | ------------------ |
|
||||
| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique revision ID |
|
||||
|
||||
| rfa_id | INT | NOT NULL, FK | Master RFA ID |
|
||||
| revision_number | INT | NOT NULL | Revision sequence (0, 1, 2...) |
|
||||
| revision_label | VARCHAR(10) | NULL | Display revision (A, B, 1.1...) |
|
||||
| is_current | BOOLEAN | DEFAULT FALSE | Current revision flag |
|
||||
| rfa_status_code_id | INT | NOT NULL, FK | Current RFA status |
|
||||
| rfa_approve_code_id | INT | NULL, FK | Approval result code |
|
||||
| title | VARCHAR(255) | NOT NULL | RFA title |
|
||||
| document_date | DATE | NULL | Document date |
|
||||
| issued_date | DATE | NULL | Issue date for approval |
|
||||
| received_date | DATETIME | NULL | Received date |
|
||||
| approved_date | DATE | NULL | Approval date |
|
||||
| description | TEXT | NULL | Revision description |
|
||||
| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | Revision creation timestamp |
|
||||
| created_by | INT | NULL, FK | User who created revision |
|
||||
| updated_by | INT | NULL, FK | User who last updated |
|
||||
| details | JSON | NULL | Type-specific details (e.g., RFI questions) |
|
||||
| v_ref_drawing_count | INT | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column ดึง Drawing Count จาก JSON details เพื่อทำ Index |
|
||||
| schema_version | INT | DEFAULT 1 | Version of the schema used with this details |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| ------------------- | --------- | --------------------------------- | ----------------------------------------------------------- |
|
||||
| id | INT | PK, FK | Master Revision ID (Shared with correspondence_revisions) |
|
||||
| rfa_status_code_id | INT | NOT NULL, FK | Current RFA status |
|
||||
| rfa_approve_code_id | INT | NULL, FK | Approval result code |
|
||||
| details | JSON | NULL | Type-specific details (e.g., RFI questions) |
|
||||
| v_ref_drawing_count | INT | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column ดึง Drawing Count จาก JSON details เพื่อทำ Index |
|
||||
| schema_version | INT | DEFAULT 1 | Version of the schema used with this details |
|
||||
|
||||
**Indexes**:
|
||||
|
||||
* PRIMARY KEY (id)
|
||||
* FOREIGN KEY (rfa_id) REFERENCES rfas(id) ON DELETE CASCADE
|
||||
* FOREIGN KEY (id) REFERENCES correspondence_revisions(id) ON DELETE CASCADE
|
||||
* FOREIGN KEY (rfa_status_code_id) REFERENCES rfa_status_codes(id)
|
||||
* FOREIGN KEY (rfa_approve_code_id) REFERENCES rfa_approve_codes(id) ON DELETE SET NULL
|
||||
* FOREIGN KEY (created_by) REFERENCES users(user_id) ON DELETE SET NULL
|
||||
* FOREIGN KEY (updated_by) REFERENCES users(user_id) ON DELETE SET NULL
|
||||
* UNIQUE KEY (rfa_id, revision_number)
|
||||
* UNIQUE KEY (rfa_id, is_current)
|
||||
* INDEX (rfa_status_code_id)
|
||||
* INDEX (rfa_approve_code_id)
|
||||
* INDEX (is_current)
|
||||
* INDEX (v_ref_drawing_count): ตัวอย่างการ Index ข้อมูลตัวเลขใน JSON
|
||||
|
||||
**Relationships**:
|
||||
|
||||
* Parent: correspondences, rfas, rfa_status_codes, rfa_approve_codes, users
|
||||
* Parent: correspondence_revisions, rfas, rfa_status_codes, rfa_approve_codes
|
||||
* Children: rfa_items
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
const config = $('Set Configuration').first().json.config;
|
||||
const fallbackState = $('Check Fallback State').first()?.json || { is_fallback_active: false, recent_error_count: 0 };
|
||||
const isFallback = fallbackState.is_fallback_active || false;
|
||||
const model = isFallback ? config.OLLAMA_MODEL_FALLBACK : config.OLLAMA_MODEL_PRIMARY;
|
||||
|
||||
// Read DB Context
|
||||
const dbContext = $('Fetch DB Context').all().map(i => i.json);
|
||||
const dbProjects = dbContext.filter(d => d.type === 'projects').map(d => ({id: d.id, code: d.text1, name: d.text2}));
|
||||
const dbDisciplines = dbContext.filter(d => d.type === 'disciplines').map(d => ({id: d.id, th: d.text1, en: d.text2}));
|
||||
const dbOrgs = dbContext.filter(d => d.type === 'organizations').map(d => ({id: d.id, name: d.text1, code: d.text2}));
|
||||
const dbTags = dbContext.filter(d => d.type === 'tags').map(d => ({id: d.id, name: d.text1}));
|
||||
const dbCorrTypes = dbContext.filter(d => d.type === 'correspondence_types').map(d => ({id: d.id, code: d.text1, name: d.text2}));
|
||||
|
||||
let systemCategories = ['Correspondence','RFA','Drawing','Transmittal','Report','Other'];
|
||||
try { systemCategories = $('File Mount Check').first().json.system_categories || systemCategories; } catch (e) {}
|
||||
|
||||
const pdfItems = $('Extract PDF Text').all();
|
||||
// File Validator passes all original Excel JSON fields through (sender, receiver, project_code, etc.)
|
||||
// Read PDF File overwrites the JSON with binary data, so we must go back one step
|
||||
const metaItems = $('File Validator').all();
|
||||
|
||||
return pdfItems.map((pdfItem, i) => {
|
||||
const item = metaItems[i] || pdfItem;
|
||||
|
||||
const docNum = String(item.json.document_number || '');
|
||||
const title = String(item.json.title || '');
|
||||
const legacyNum = String(item.json.legacy_number || '');
|
||||
const issuedDate = String(item.json.issued_date || '');
|
||||
const receivedDate = String(item.json.received_date || '');
|
||||
const corrType = String(item.json.correspondence_type || '');
|
||||
const senderCode = String(item.json.sender || '');
|
||||
const receiverCode = String(item.json.receiver || '');
|
||||
const projectCode = String(item.json.project_code || '');
|
||||
|
||||
// JavaScript pre-mapping
|
||||
const findOrgId = (code) => {
|
||||
if (!code) return null;
|
||||
const match = dbOrgs.find(o => o.code === code || o.name === code);
|
||||
return match ? match.id : null;
|
||||
};
|
||||
|
||||
const findProjectId = (code) => {
|
||||
if (!code) return config.PROJECT_ID; // Fallback to config
|
||||
const match = dbProjects.find(p => p.code === code || p.name === code);
|
||||
return match ? match.id : config.PROJECT_ID;
|
||||
};
|
||||
|
||||
const senderId = findOrgId(senderCode);
|
||||
const receiverId = findOrgId(receiverCode);
|
||||
const projectId = findProjectId(projectCode);
|
||||
// Excel corrType is likely already the ID based on requirements, but fallback matching to ID if needed
|
||||
const corrMatch = dbCorrTypes.find(c => String(c.id) === corrType || c.code === corrType || c.name === corrType);
|
||||
const corrTypeId = corrMatch ? corrMatch.id : (isNaN(parseInt(corrType)) ? null : parseInt(corrType));
|
||||
|
||||
const isRFA = docNum.includes('-RFA-') || title.toLowerCase().includes('rfa');
|
||||
|
||||
const systemPrompt = `You are an expert Document Controller for a construction project (LCBP3) in Thailand.
|
||||
The documents are primarily in THAI and ENGLISH.
|
||||
Your task is to classify documents and extract metadata from OCR text.
|
||||
Respond ONLY with valid JSON.`;
|
||||
|
||||
// Use pdfItem for the OCR extracted data, NOT the metaItem
|
||||
const pdfText = String(pdfItem.json.data || '').substring(0, 3500).replace(/[^a-zA-Z0-9ก-๙\s\.\/\-:\[\]\(\)]/g, ' ');
|
||||
|
||||
const userPrompt = `Analyze this document:
|
||||
[EXCEL METADATA]
|
||||
Document Number: ${docNum || 'Not provided'}
|
||||
Title: ${title || 'Not provided'}
|
||||
Issued Date: ${issuedDate || 'Not provided'}
|
||||
Received Date: ${receivedDate || 'Not provided'}
|
||||
|
||||
[DATABASE REFERENCES]
|
||||
Disciplines: ${JSON.stringify(dbDisciplines)}
|
||||
Tags: ${JSON.stringify(dbTags)}
|
||||
|
||||
[OCR TEXT EXTRACTION]
|
||||
${pdfText}
|
||||
|
||||
Rules:
|
||||
1. Category: Must be one of ${JSON.stringify(systemCategories)}. If Document Number contains "-RFA-", category MUST be "RFA".
|
||||
2. Respond with EXACTLY 8 fields in JSON format:
|
||||
- "discipline_id": Find 'id' from Disciplines array analyzing text to match 'th' or 'en'. If no match, use ID=64 (from contract LCBP3-C2).
|
||||
- "subject": Document subject. If OCR is close to EXCEL METADATA Title, use EXCEL METADATA.
|
||||
- "issued_date": Verify from OCR text if it matches ${issuedDate}, format YYYY-MM-DD.
|
||||
- "received_date": Verify from OCR text. If empty, default to issued_date.
|
||||
- "status": Extract status (e.g., For Information, Approve, Reject, Resubmit). This will be exported as "remark".
|
||||
- "summary": 4-5 lines of Thai summary from OCR. This will be exported as "body".
|
||||
- "tags": REQUIRED. Identify 2-5 main topics/themes from the document (from Title, subject matter, and OCR text). For each topic, return an object with:
|
||||
* "tag_name": short topic name in Thai (2-5 words), e.g. "คอนกรีตผสม", "ทดสอบวัสดุ"
|
||||
* "description": one sentence in Thai describing this topic (use key point details). e.g. "การทดสอบค่า slump ของคอนกรีตผสมที่หน้างาน"
|
||||
Return as: [{"tag_name": "...", "description": "..."}, ...]
|
||||
- "key_points": Array of 3-5 string key points extracted from the document (in Thai).
|
||||
|
||||
3. IMPORTANT: You MUST REPLACE the 'null' values in the template below with the actual Integer IDs or text you found. DO NOT reply with literal 'null' if you found a match!
|
||||
|
||||
Respond ONLY with this EXACT JSON structure:
|
||||
{
|
||||
"discipline_id": 64,
|
||||
"subject": "${title}",
|
||||
"issued_date": "${issuedDate}",
|
||||
"received_date": "${receivedDate || issuedDate}",
|
||||
"status": null,
|
||||
"summary": "สรุปเนื้อหา 4-5 บรรทัด...",
|
||||
"tags": [{"tag_name": "ชื่อหัวข้อ", "description": "คำอธิบาย key point ของหัวข้อนี้"}],
|
||||
"key_points": ["จุดสำคัญที่ 1", "จุดสำคัญที่ 2", "จุดสำคัญที่ 3"],
|
||||
"category": "${isRFA ? 'RFA' : 'Correspondence'}",
|
||||
"confidence": 0.95
|
||||
}`;
|
||||
|
||||
return {
|
||||
json: {
|
||||
...item.json,
|
||||
active_model: model,
|
||||
is_fallback: isFallback,
|
||||
system_categories: systemCategories,
|
||||
pre_mapped: {
|
||||
project_id: projectId,
|
||||
sender_id: senderId,
|
||||
receiver_id: receiverId,
|
||||
correspondence_type_id: corrTypeId
|
||||
},
|
||||
_debug_mapping: {
|
||||
excel_project_code: projectCode,
|
||||
excel_sender: senderCode,
|
||||
excel_receiver: receiverCode,
|
||||
excel_corr_type: corrType,
|
||||
matched_project: dbProjects.find(p => p.code === projectCode || p.name === projectCode) || null,
|
||||
first_org_sample: dbOrgs[0] || null
|
||||
},
|
||||
ollama_payload: {
|
||||
model: model,
|
||||
prompt: `${systemPrompt}\n\n${userPrompt}`,
|
||||
stream: false,
|
||||
format: 'json',
|
||||
options: {
|
||||
temperature: 0.1,
|
||||
num_ctx: 8192
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
Organizations: [
|
||||
{
|
||||
id: 1,
|
||||
organization_name: 'การท่าเรือแห่งประเทศไทย',
|
||||
organization_code: 'กทท.'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
organization_name: 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3',
|
||||
organization_code: 'สคฉ.3'
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
organization_name: 'ตรวจรับพัสดุ ที่ปรึกษาควบคุมงาน',
|
||||
organization_code: 'สคฉ.3-01'
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
organization_name: 'ตรวจรับพัสดุ งานทางทะเล',
|
||||
organization_code: 'สคฉ.3-02'
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
organization_name: 'ตรวจรับพัสดุ อาคารและระบบสาธารณูปโภค',
|
||||
organization_code: 'สคฉ.3-03'
|
||||
}
|
||||
]
|
||||
Projects: [
|
||||
{
|
||||
id: 1,
|
||||
project_code: 'LCBP3',
|
||||
project_name: 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 1-4)'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
project_code: 'LCBP3-C1',
|
||||
project_name: 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 1) งานก่อสร้างงานทางทะเล'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
project_code: 'LCBP3-C2',
|
||||
project_name: 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 2) งานก่อสร้างอาคาร ท่าเทียบเรือ ระบบถนน และระบบสาธารณูปโภค'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
project_code: 'LCBP3-C3',
|
||||
project_name: 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 3) งานก่อสร้าง'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
project_code: 'LCBP3-C4',
|
||||
project_name: 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 4) งานก่อสร้าง'
|
||||
}
|
||||
]
|
||||
Correspondence Types: [
|
||||
{ id: 1, type_code: 'RFA', type_name: 'Request for Approval' },
|
||||
{ id: 2, type_code: 'RFI', type_name: 'Request for Information' },
|
||||
{ id: 3, type_code: 'TRANSMITTAL', type_name: 'Transmittal' },
|
||||
{ id: 4, type_code: 'EMAIL', type_name: 'Email' },
|
||||
{ id: 5, type_code: 'INSTRUCTION', type_name: 'Instruction' }
|
||||
]
|
||||
@@ -0,0 +1,9 @@
|
||||
SELECT 'projects' as type, id, project_code as text1, project_name as text2 FROM projects
|
||||
UNION ALL
|
||||
SELECT 'disciplines' as type, id, code_name_th as text1, code_name_en as text2 FROM disciplines
|
||||
UNION ALL
|
||||
SELECT 'organizations' as type, id, organization_name as text1, organization_code as text2 FROM organizations
|
||||
UNION ALL
|
||||
SELECT 'tags' as type, id, tag_name as text1, description as text2 FROM tags
|
||||
UNION ALL
|
||||
SELECT 'correspondence_types' as type, id, type_code as text1, type_name as text2 FROM correspondence_types
|
||||
@@ -440,47 +440,22 @@ CREATE TABLE rfas (
|
||||
|
||||
-- ตาราง "ลูก" เก็บประวัติ (Revisions) ของ rfas (1:N)
|
||||
CREATE TABLE rfa_revisions (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของ Revision',
|
||||
rfa_id INT NOT NULL COMMENT 'Master ID ของ RFA',
|
||||
revision_number INT NOT NULL COMMENT 'หมายเลข Revision (0, 1, 2...)',
|
||||
revision_label VARCHAR(10) COMMENT 'Revision ที่แสดง (เช่น A, B, 1.1)',
|
||||
is_current BOOLEAN DEFAULT FALSE COMMENT '(1 = Revision ปัจจุบัน)',
|
||||
-- ข้อมูลเฉพาะของ RFA Revision ที่ซับซ้อน
|
||||
id INT PRIMARY KEY COMMENT 'ID (แชร์กับ correspondence_revisions)',
|
||||
rfa_status_code_id INT NOT NULL COMMENT 'สถานะ RFA',
|
||||
rfa_approve_code_id INT COMMENT 'ผลการอนุมัติ',
|
||||
subject VARCHAR(500) NOT NULL COMMENT 'หัวข้อเรื่อง',
|
||||
description TEXT COMMENT 'คำอธิบายการแก้ไขใน Revision นี้',
|
||||
body TEXT NULL COMMENT 'เนื้อความ (ถ้ามี)',
|
||||
remarks TEXT COMMENT 'หมายเหตุ',
|
||||
document_date DATE COMMENT 'วันที่ในเอกสาร',
|
||||
issued_date DATE COMMENT 'วันที่ส่งขออนุมัติ',
|
||||
received_date DATETIME COMMENT 'วันที่ลงรับเอกสาร',
|
||||
due_date DATETIME COMMENT 'วันที่ครบกำหนด',
|
||||
approved_date DATE COMMENT 'วันที่อนุมัติ',
|
||||
-- Standard Meta Columns
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้างเอกสาร',
|
||||
created_by INT COMMENT 'ผู้สร้าง',
|
||||
updated_by INT COMMENT 'ผู้แก้ไขล่าสุด',
|
||||
-- ส่วนของ JSON และ Schema Version
|
||||
details JSON NULL COMMENT 'RFA Specific Details',
|
||||
schema_version INT DEFAULT 1 COMMENT 'Version ของ JSON Schema',
|
||||
-- Generated Virtual Columns (ดึงค่าจาก JSON โดยอัตโนมัติ)
|
||||
v_ref_drawing_count INT GENERATED ALWAYS AS (
|
||||
JSON_UNQUOTE(
|
||||
JSON_EXTRACT(details, '$.drawingCount')
|
||||
)
|
||||
) VIRTUAL,
|
||||
FOREIGN KEY (rfa_id) REFERENCES rfas (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (id) REFERENCES correspondence_revisions (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (rfa_status_code_id) REFERENCES rfa_status_codes (id),
|
||||
FOREIGN KEY (rfa_approve_code_id) REFERENCES rfa_approve_codes (id) ON DELETE
|
||||
SET NULL,
|
||||
FOREIGN KEY (created_by) REFERENCES users (user_id) ON DELETE
|
||||
SET NULL,
|
||||
FOREIGN KEY (updated_by) REFERENCES users (user_id) ON DELETE
|
||||
SET NULL,
|
||||
UNIQUE KEY uq_rr_rev_number (rfa_id, revision_number),
|
||||
UNIQUE KEY uq_rr_current (rfa_id, is_current)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง "ลูก" เก็บประวัติ (Revisions) ของ rfas (1 :N)';
|
||||
SET NULL
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางขยายของ correspondence_revisions สำหรับ RFA (1:1)';
|
||||
|
||||
-- ตารางเชื่อมระหว่าง rfa_revisions (ที่เป็นประเภท DWG) กับ shop_drawing_revisions (M:N)
|
||||
CREATE TABLE rfa_items (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
-- รัน: หลังจาก 02-schema-tables.sql เสร็จ
|
||||
-- ==========================================================
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
SET time_zone = '+07:00';
|
||||
|
||||
-- ============================================================
|
||||
@@ -176,12 +177,12 @@ SELECT r.id AS rfa_id,
|
||||
c.originator_id,
|
||||
org.organization_name AS originator_name,
|
||||
rr.id AS revision_id,
|
||||
rr.revision_number,
|
||||
rr.revision_label,
|
||||
rr.subject,
|
||||
rr.document_date,
|
||||
rr.issued_date,
|
||||
rr.received_date,
|
||||
cr.revision_number,
|
||||
cr.revision_label,
|
||||
cr.subject,
|
||||
cr.document_date,
|
||||
cr.issued_date,
|
||||
cr.received_date,
|
||||
rr.approved_date,
|
||||
rr.rfa_status_code_id,
|
||||
rsc.status_code AS rfa_status_code,
|
||||
@@ -189,21 +190,22 @@ SELECT r.id AS rfa_id,
|
||||
rr.rfa_approve_code_id,
|
||||
rac.approve_code AS rfa_approve_code,
|
||||
rac.approve_name AS rfa_approve_name,
|
||||
rr.created_by,
|
||||
cr.created_by,
|
||||
u.username AS created_by_username,
|
||||
rr.created_at AS revision_created_at
|
||||
cr.created_at AS revision_created_at
|
||||
FROM rfas r
|
||||
INNER JOIN rfa_types rt ON r.rfa_type_id = rt.id
|
||||
INNER JOIN rfa_revisions rr ON r.id = rr.rfa_id -- RFA uses shared primary key with correspondences (1:1)
|
||||
INNER JOIN correspondences c ON r.id = c.id -- [FIX 1] เพิ่มการ Join ตาราง disciplines
|
||||
INNER JOIN correspondences c ON r.id = c.id
|
||||
INNER JOIN correspondence_revisions cr ON c.id = cr.correspondence_id
|
||||
INNER JOIN rfa_revisions rr ON cr.id = rr.id -- RFA uses shared primary key with correspondence_revisions (1:1)
|
||||
-- [FIX 1] เพิ่มการ Join ตาราง disciplines
|
||||
LEFT JOIN disciplines d ON c.discipline_id = d.id
|
||||
INNER JOIN projects p ON c.project_id = p.id
|
||||
INNER JOIN organizations org ON c.originator_id = org.id
|
||||
INNER JOIN rfa_status_codes rsc ON rr.rfa_status_code_id = rsc.id
|
||||
LEFT JOIN rfa_approve_codes rac ON rr.rfa_approve_code_id = rac.id
|
||||
LEFT JOIN users u ON rr.created_by = u.user_id
|
||||
WHERE rr.is_current = TRUE
|
||||
AND r.deleted_at IS NULL
|
||||
LEFT JOIN users u ON cr.created_by = u.user_id
|
||||
WHERE cr.is_current = TRUE
|
||||
AND c.deleted_at IS NULL;
|
||||
|
||||
-- View แสดงความสัมพันธ์ทั้งหมดระหว่าง Contract, Project, และ Organization
|
||||
@@ -249,7 +251,7 @@ SELECT -- 1. Workflow Instance Info
|
||||
ELSE 'N/A'
|
||||
END AS document_number,
|
||||
CASE
|
||||
WHEN wi.entity_type = 'rfa_revision' THEN rfa_rev.subject
|
||||
WHEN wi.entity_type = 'rfa_revision' THEN rfa_corr_rev.subject
|
||||
WHEN wi.entity_type = 'circulation' THEN circ.circulation_subject
|
||||
WHEN wi.entity_type = 'correspondence_revision' THEN corr_rev.subject
|
||||
ELSE 'Unknown Document'
|
||||
@@ -262,7 +264,8 @@ FROM workflow_instances wi
|
||||
JOIN workflow_definitions wd ON wi.definition_id = wd.id -- 5. Joins for RFA (ซับซ้อนหน่อยเพราะ RFA ผูกกับ Correspondence อีกที)
|
||||
LEFT JOIN rfa_revisions rfa_rev ON wi.entity_type = 'rfa_revision'
|
||||
AND wi.entity_id = CAST(rfa_rev.id AS CHAR)
|
||||
LEFT JOIN correspondences rfa_corr ON rfa_rev.id = rfa_corr.id -- 6. Joins for Circulation
|
||||
LEFT JOIN correspondence_revisions rfa_corr_rev ON rfa_rev.id = rfa_corr_rev.id
|
||||
LEFT JOIN correspondences rfa_corr ON rfa_corr_rev.correspondence_id = rfa_corr.id -- 6. Joins for Circulation
|
||||
LEFT JOIN circulations circ ON wi.entity_type = 'circulation'
|
||||
AND wi.entity_id = CAST(circ.id AS CHAR) -- 7. Joins for Correspondence
|
||||
LEFT JOIN correspondence_revisions corr_rev ON wi.entity_type = 'correspondence_revision'
|
||||
@@ -497,9 +500,7 @@ CREATE INDEX idx_corr_revisions_current_status ON correspondence_revisions (is_c
|
||||
CREATE INDEX idx_corr_revisions_correspondence_current ON correspondence_revisions (correspondence_id, is_current);
|
||||
|
||||
-- Indexes for v_current_rfas performance
|
||||
CREATE INDEX idx_rfa_revisions_current_status ON rfa_revisions (is_current, rfa_status_code_id);
|
||||
|
||||
CREATE INDEX idx_rfa_revisions_rfa_current ON rfa_revisions (rfa_id, is_current);
|
||||
CREATE INDEX idx_rfa_revisions_status ON rfa_revisions (rfa_status_code_id);
|
||||
|
||||
-- Indexes for document statistics performance
|
||||
CREATE INDEX idx_correspondences_project_type ON correspondences (project_id, correspondence_type_id);
|
||||
@@ -513,4 +514,3 @@ CREATE INDEX IDX_AUDIT_STATUS ON document_number_audit (STATUS);
|
||||
CREATE INDEX IDX_AUDIT_OPERATION ON document_number_audit (operation);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
|
||||
@@ -1924,56 +1924,6 @@ VALUES (
|
||||
NULL
|
||||
);
|
||||
|
||||
INSERT INTO `rfas` (
|
||||
`id`,
|
||||
`rfa_type_id`,
|
||||
`created_at`,
|
||||
`created_by`,
|
||||
`deleted_at`
|
||||
)
|
||||
VALUES (2, 68, '2025-12-06 05:40:02', 1, NULL);
|
||||
|
||||
INSERT INTO `rfa_revisions` (
|
||||
`id`,
|
||||
`rfa_id`,
|
||||
`revision_number`,
|
||||
`revision_label`,
|
||||
`is_current`,
|
||||
`rfa_status_code_id`,
|
||||
`rfa_approve_code_id`,
|
||||
`subject`,
|
||||
`document_date`,
|
||||
`issued_date`,
|
||||
`received_date`,
|
||||
`approved_date`,
|
||||
`description`,
|
||||
`details`,
|
||||
`schema_version`,
|
||||
`created_at`,
|
||||
`created_by`,
|
||||
`updated_by`
|
||||
)
|
||||
VALUES (
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
'A',
|
||||
0,
|
||||
2,
|
||||
NULL,
|
||||
'ขออนุมัติผลการทดสอบเสาเข็มแบบ Dynamic Load Test สำหรับงานเสาเข็มตอกของอาคารสถานีไฟฟ้าย่อย 22 kV. No. 6',
|
||||
'2025-12-03',
|
||||
'2025-12-04',
|
||||
'2025-12-04 12:40:19',
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
1,
|
||||
'2025-12-06 05:41:25',
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- 20. Workflow Definitions (Unified Workflow Engine)
|
||||
-- ==========================================================
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "03-data-and-storage",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": ".mountcheck.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"mysql2": "^3.19.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
async function test() {
|
||||
const connection = await mysql.createConnection({
|
||||
host: '192.168.10.8',
|
||||
port: 3306,
|
||||
user: 'migration_bot',
|
||||
password: 'Center2025',
|
||||
database: 'lcbp3'
|
||||
});
|
||||
|
||||
try {
|
||||
const [orgs] = await connection.execute('SELECT id, organization_name, organization_code FROM organizations');
|
||||
console.log('Organizations:', orgs.slice(0, 5));
|
||||
|
||||
const [projects] = await connection.execute('SELECT id, project_code, project_name FROM projects');
|
||||
console.log('Projects:', projects.slice(0, 5));
|
||||
|
||||
const [corrTypes] = await connection.execute('SELECT id, type_code, type_name FROM correspondence_types');
|
||||
console.log('Correspondence Types:', corrTypes.slice(0, 5));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
|
||||
test();
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user