260330:1011 Addied correspondence_revieion_attcahments table table #01
This commit is contained in:
@@ -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
|
||||
|
||||
+40
@@ -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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user