260330:1011 Addied correspondence_revieion_attcahments table table #01
CI / CD Pipeline / build (push) Failing after 21m19s
CI / CD Pipeline / deploy (push) Has been skipped

This commit is contained in:
admin
2026-03-30 10:11:40 +07:00
parent 1c6fec6c65
commit c83588ab43
287 changed files with 15117 additions and 69 deletions
@@ -13,6 +13,7 @@ import { CorrespondenceStatus } from './entities/correspondence-status.entity';
import { CorrespondenceReference } from './entities/correspondence-reference.entity';
import { CorrespondenceRecipient } from './entities/correspondence-recipient.entity';
import { CorrespondenceTag } from './entities/correspondence-tag.entity';
import { CorrespondenceRevisionAttachment } from './entities/correspondence-revision-attachment.entity';
import { Organization } from '../organization/entities/organization.entity';
// Dependent Modules
@@ -40,6 +41,7 @@ import { NotificationModule } from '../notification/notification.module';
CorrespondenceReference,
CorrespondenceRecipient,
CorrespondenceTag,
CorrespondenceRevisionAttachment,
Organization,
]),
DocumentNumberingModule,
@@ -22,6 +22,7 @@ import { CorrespondenceTag } from './entities/correspondence-tag.entity';
import { Tag } from '../master/entities/tag.entity';
import { User } from '../user/entities/user.entity';
import { Organization } from '../organization/entities/organization.entity';
import { CorrespondenceRevisionAttachment } from './entities/correspondence-revision-attachment.entity';
// DTOs
import { CreateCorrespondenceDto } from './dto/create-correspondence.dto';
@@ -89,7 +90,9 @@ export class CorrespondenceService {
private searchService: SearchService,
private fileStorageService: FileStorageService,
private uuidResolver: UuidResolverService,
private notificationService: NotificationService
private notificationService: NotificationService,
@InjectRepository(CorrespondenceRevisionAttachment)
private revAttachRepo: Repository<CorrespondenceRevisionAttachment>
) {}
/**
@@ -344,10 +347,25 @@ export class CorrespondenceService {
? new Date(createDto.documentDate)
: undefined;
await this.fileStorageService.commit(createDto.attachmentTempIds, {
issueDate,
documentType: 'Correspondence',
});
// [FIX v1.8.1] commit ได้ Attachment records กลับมา → บันทึก junction
const committed = await this.fileStorageService.commit(
createDto.attachmentTempIds,
{ issueDate, documentType: 'Correspondence' }
);
if (committed.length > 0) {
const links = committed.map((att, idx) =>
queryRunner.manager.create(CorrespondenceRevisionAttachment, {
correspondenceRevisionId: revision.id,
attachmentId: att.id,
isMainDocument: idx === 0, // ไฟล์แรกเป็น main document
})
);
await queryRunner.manager.save(
CorrespondenceRevisionAttachment,
links
);
}
}
await queryRunner.commitTransaction();
@@ -496,6 +514,8 @@ export class CorrespondenceService {
relations: [
'revisions',
'revisions.status',
'revisions.attachmentLinks', // [FIX v1.8.1] โหลด junction
'revisions.attachmentLinks.attachment', // [FIX v1.8.1] โหลด attachment จริง
'type',
'project',
'originator',
@@ -710,10 +730,25 @@ export class CorrespondenceService {
? new Date(updateDto.documentDate)
: revision.issuedDate || revision.documentDate || undefined;
await this.fileStorageService.commit(updateDto.attachmentTempIds, {
issueDate: issueDate ? new Date(issueDate) : undefined,
documentType: 'Correspondence',
});
// [FIX v1.8.1] commit ได้ Attachment records กลับมา → บันทึก junction
const committed = await this.fileStorageService.commit(
updateDto.attachmentTempIds,
{
issueDate: issueDate ? new Date(issueDate) : undefined,
documentType: 'Correspondence',
}
);
if (committed.length > 0) {
const links = committed.map((att) =>
this.revAttachRepo.create({
correspondenceRevisionId: revision.id,
attachmentId: att.id,
isMainDocument: false, // ไฟล์ที่ upload เพิ่มเติมไม่ใช่ main
})
);
await this.revAttachRepo.save(links);
}
}
// 5. Update Recipients if provided
@@ -0,0 +1,40 @@
// File: src/modules/correspondence/entities/correspondence-revision-attachment.entity.ts
import { Entity, Column, ManyToOne, JoinColumn, PrimaryColumn } from 'typeorm';
import { CorrespondenceRevision } from './correspondence-revision.entity';
import { Attachment } from '../../../common/file-storage/entities/attachment.entity';
/**
* CorrespondenceRevisionAttachment
*
* ตารางเชื่อม correspondence_revisions กับ attachments (M:N)
* [FIX v1.8.1] FK ชี้ไป correspondence_revisions.id ไม่ใช่ correspondences.id
* เหตุผล: ไฟล์แนบผูกกับ revision เพื่อรองรับไฟล์ที่ต่างกันในแต่ละ revision
*/
@Entity('correspondence_revision_attachments')
export class CorrespondenceRevisionAttachment {
// คีย์หลักของ revision ที่ผูกไฟล์นี้
@PrimaryColumn({ name: 'correspondence_revision_id' })
correspondenceRevisionId!: number;
// คีย์หลักของ attachment
@PrimaryColumn({ name: 'attachment_id' })
attachmentId!: number;
// ไฟล์หลักของ revision นี้ (เช่น PDF หลัก)
@Column({ name: 'is_main_document', default: false })
isMainDocument!: boolean;
// Relation: หลาย CorrespondenceRevisionAttachment → หนึ่ง CorrespondenceRevision
@ManyToOne(
() => CorrespondenceRevision,
(revision) => revision.attachmentLinks,
{ onDelete: 'CASCADE' }
)
@JoinColumn({ name: 'correspondence_revision_id' })
revision?: CorrespondenceRevision;
// Relation: หลาย CorrespondenceRevisionAttachment → หนึ่ง Attachment
@ManyToOne(() => Attachment, { onDelete: 'CASCADE', eager: false })
@JoinColumn({ name: 'attachment_id' })
attachment?: Attachment;
}
@@ -1,4 +1,3 @@
// File: src/modules/correspondence/entities/correspondence-revision.entity.ts
import {
Entity,
Column,
@@ -8,6 +7,7 @@ import {
CreateDateColumn,
Index,
OneToOne,
OneToMany,
} from 'typeorm';
import { RfaRevision } from '../../rfa/entities/rfa-revision.entity';
import { Correspondence } from './correspondence.entity';
@@ -15,6 +15,7 @@ import { CorrespondenceStatus } from './correspondence-status.entity';
import { User } from '../../user/entities/user.entity';
import { UuidBaseEntity } from '../../../common/entities/uuid-base.entity';
import { Exclude } from 'class-transformer';
import { CorrespondenceRevisionAttachment } from './correspondence-revision-attachment.entity';
@Entity('correspondence_revisions')
// ✅ เพิ่ม Index สำหรับ Virtual Columns เพื่อให้ Search เร็วขึ้น
@@ -116,4 +117,8 @@ export class CorrespondenceRevision extends UuidBaseEntity {
// Added inverse relation for CTI mapping to subclasses (RFA)
@OneToOne(() => RfaRevision, (rfaRev) => rfaRev.correspondenceRevision)
rfaRevision?: RfaRevision;
// [FIX v1.8.1] Relation: ไฟล์แนบของ revision นี้ผ่าน junction table
@OneToMany(() => CorrespondenceRevisionAttachment, (link) => link.revision)
attachmentLinks?: CorrespondenceRevisionAttachment[];
}
@@ -167,24 +167,33 @@ export class ReservationService {
/**
* Cron job: Cleanup expired reservations every 5 minutes
* ใช้ try/catch เพื่อรองรับ transient ECONNRESET — TypeORM pool จะ reconnect อัตโนมัติใน tick ถัดไป
*/
@Cron('*/5 * * * *')
async cleanupExpired(): Promise<void> {
const result = await this.reservationRepo
.createQueryBuilder()
.update()
.set({
status: ReservationStatus.CANCELLED,
cancelledAt: () => 'NOW()',
})
.where('document_number_status = :status', {
status: ReservationStatus.RESERVED,
})
.andWhere('expires_at < NOW()')
.execute();
try {
const result = await this.reservationRepo
.createQueryBuilder()
.update()
.set({
status: ReservationStatus.CANCELLED,
cancelledAt: () => 'NOW()',
})
.where('document_number_status = :status', {
status: ReservationStatus.RESERVED,
})
.andWhere('expires_at < NOW()')
.execute();
if ((result.affected ?? 0) > 0) {
this.logger.log(`Cleaned up ${result.affected} expired reservations`);
if ((result.affected ?? 0) > 0) {
this.logger.log(`Cleaned up ${result.affected} expired reservations`);
}
} catch (error: unknown) {
// ECONNRESET บน idle connection — TypeORM จะ reconnect pool ใน request ถัดไปโดยอัตโนมัติ
const msg = error instanceof Error ? error.message : String(error);
this.logger.warn(
`[cleanupExpired] Transient DB error (will retry next tick): ${msg}`
);
}
}