251213:1509 Docunment Number Businee Rule not correct
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
ParseIntPipe,
|
||||
Query,
|
||||
Delete,
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiTags,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
import { CorrespondenceService } from './correspondence.service';
|
||||
import { CorrespondenceWorkflowService } from './correspondence-workflow.service';
|
||||
import { CreateCorrespondenceDto } from './dto/create-correspondence.dto';
|
||||
import { UpdateCorrespondenceDto } from './dto/update-correspondence.dto';
|
||||
import { SubmitCorrespondenceDto } from './dto/submit-correspondence.dto';
|
||||
import { WorkflowActionDto } from './dto/workflow-action.dto';
|
||||
import { AddReferenceDto } from './dto/add-reference.dto';
|
||||
@@ -92,6 +94,23 @@ export class CorrespondenceController {
|
||||
);
|
||||
}
|
||||
|
||||
@Post('preview-number')
|
||||
@ApiOperation({ summary: 'Preview next document number' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Return preview number and status.',
|
||||
})
|
||||
@RequirePermission('correspondence.create')
|
||||
previewNumber(
|
||||
@Body() createDto: CreateCorrespondenceDto,
|
||||
@Request() req: Request & { user: unknown }
|
||||
) {
|
||||
return this.correspondenceService.previewDocumentNumber(
|
||||
createDto,
|
||||
req.user as Parameters<typeof this.correspondenceService.create>[1]
|
||||
);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Search correspondences' })
|
||||
@ApiResponse({ status: 200, description: 'Return list of correspondences.' })
|
||||
@@ -140,6 +159,26 @@ export class CorrespondenceController {
|
||||
return this.correspondenceService.findOne(id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@ApiOperation({ summary: 'Update correspondence (Draft only)' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Correspondence updated successfully.',
|
||||
})
|
||||
@RequirePermission('correspondence.create') // Assuming create permission is enough for draft update, or add 'correspondence.edit'
|
||||
@Audit('correspondence.update', 'correspondence')
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() updateDto: UpdateCorrespondenceDto,
|
||||
@Request() req: Request & { user: unknown }
|
||||
) {
|
||||
return this.correspondenceService.update(
|
||||
id,
|
||||
updateDto,
|
||||
req.user as Parameters<typeof this.correspondenceService.create>[1]
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id/references')
|
||||
@ApiOperation({ summary: 'Get referenced documents' })
|
||||
@ApiResponse({
|
||||
|
||||
@@ -10,6 +10,8 @@ import { CorrespondenceRevision } from './entities/correspondence-revision.entit
|
||||
import { CorrespondenceType } from './entities/correspondence-type.entity';
|
||||
import { CorrespondenceStatus } from './entities/correspondence-status.entity';
|
||||
import { CorrespondenceReference } from './entities/correspondence-reference.entity';
|
||||
import { CorrespondenceRecipient } from './entities/correspondence-recipient.entity';
|
||||
import { Organization } from '../organization/entities/organization.entity';
|
||||
|
||||
// Dependent Modules
|
||||
import { DocumentNumberingModule } from '../document-numbering/document-numbering.module';
|
||||
@@ -32,6 +34,8 @@ import { SearchModule } from '../search/search.module';
|
||||
CorrespondenceType,
|
||||
CorrespondenceStatus,
|
||||
CorrespondenceReference,
|
||||
CorrespondenceRecipient,
|
||||
Organization,
|
||||
]),
|
||||
DocumentNumberingModule,
|
||||
JsonSchemaModule,
|
||||
|
||||
@@ -118,8 +118,8 @@ describe('CorrespondenceService', () => {
|
||||
describe('findAll', () => {
|
||||
it('should return correspondences array', async () => {
|
||||
const result = await service.findAll({ projectId: 1 });
|
||||
expect(Array.isArray(result)).toBeTruthy();
|
||||
expect(result).toBeDefined();
|
||||
expect(Array.isArray(result.data)).toBeTruthy();
|
||||
expect(result.meta).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,12 +17,16 @@ import { CorrespondenceRevision } from './entities/correspondence-revision.entit
|
||||
import { CorrespondenceType } from './entities/correspondence-type.entity';
|
||||
import { CorrespondenceStatus } from './entities/correspondence-status.entity';
|
||||
import { CorrespondenceReference } from './entities/correspondence-reference.entity';
|
||||
import { CorrespondenceRecipient } from './entities/correspondence-recipient.entity';
|
||||
import { User } from '../user/entities/user.entity';
|
||||
import { Organization } from '../organization/entities/organization.entity';
|
||||
|
||||
// DTOs
|
||||
import { CreateCorrespondenceDto } from './dto/create-correspondence.dto';
|
||||
import { UpdateCorrespondenceDto } from './dto/update-correspondence.dto';
|
||||
import { AddReferenceDto } from './dto/add-reference.dto';
|
||||
import { SearchCorrespondenceDto } from './dto/search-correspondence.dto';
|
||||
import { DeepPartial } from 'typeorm';
|
||||
|
||||
// Services
|
||||
import { DocumentNumberingService } from '../document-numbering/document-numbering.service';
|
||||
@@ -52,6 +56,8 @@ export class CorrespondenceService {
|
||||
private statusRepo: Repository<CorrespondenceStatus>,
|
||||
@InjectRepository(CorrespondenceReference)
|
||||
private referenceRepo: Repository<CorrespondenceReference>,
|
||||
@InjectRepository(Organization)
|
||||
private orgRepo: Repository<Organization>,
|
||||
|
||||
private numberingService: DocumentNumberingService,
|
||||
private jsonSchemaService: JsonSchemaService,
|
||||
@@ -121,10 +127,17 @@ export class CorrespondenceService {
|
||||
try {
|
||||
const orgCode = 'ORG'; // TODO: Fetch real ORG Code from Organization Entity
|
||||
|
||||
// Extract recipient organization from details
|
||||
const recipientOrganizationId = createDto.details?.to_organization_id as
|
||||
| number
|
||||
| undefined;
|
||||
// [v1.5.1] Extract recipient organization from recipients array (Primary TO)
|
||||
const toRecipient = createDto.recipients?.find((r) => r.type === 'TO');
|
||||
const recipientOrganizationId = toRecipient?.organizationId;
|
||||
|
||||
let recipientCode = '';
|
||||
if (recipientOrganizationId) {
|
||||
const recOrg = await this.orgRepo.findOne({
|
||||
where: { id: recipientOrganizationId },
|
||||
});
|
||||
if (recOrg) recipientCode = recOrg.organizationCode;
|
||||
}
|
||||
|
||||
const docNumber = await this.numberingService.generateNextNumber({
|
||||
projectId: createDto.projectId,
|
||||
@@ -137,6 +150,8 @@ export class CorrespondenceService {
|
||||
customTokens: {
|
||||
TYPE_CODE: type.typeCode,
|
||||
ORG_CODE: orgCode,
|
||||
RECIPIENT_CODE: recipientCode,
|
||||
REC_CODE: recipientCode,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -157,13 +172,29 @@ export class CorrespondenceService {
|
||||
revisionLabel: 'A',
|
||||
isCurrent: true,
|
||||
statusId: statusDraft.id,
|
||||
title: createDto.title,
|
||||
subject: createDto.subject,
|
||||
body: createDto.body,
|
||||
remarks: createDto.remarks,
|
||||
dueDate: createDto.dueDate ? new Date(createDto.dueDate) : undefined,
|
||||
description: createDto.description,
|
||||
details: createDto.details,
|
||||
createdBy: user.user_id,
|
||||
schemaVersion: 1,
|
||||
});
|
||||
await queryRunner.manager.save(revision);
|
||||
|
||||
// Save Recipients
|
||||
if (createDto.recipients && createDto.recipients.length > 0) {
|
||||
const recipients = createDto.recipients.map((r) =>
|
||||
queryRunner.manager.create(CorrespondenceRecipient, {
|
||||
correspondenceId: savedCorr.id,
|
||||
recipientOrganizationId: r.organizationId,
|
||||
recipientType: r.type,
|
||||
})
|
||||
);
|
||||
await queryRunner.manager.save(recipients);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
// Start Workflow Instance (non-blocking)
|
||||
@@ -190,7 +221,7 @@ export class CorrespondenceService {
|
||||
id: savedCorr.id,
|
||||
type: 'correspondence',
|
||||
docNumber: docNumber,
|
||||
title: createDto.title,
|
||||
title: createDto.subject,
|
||||
description: createDto.description,
|
||||
status: 'DRAFT',
|
||||
projectId: createDto.projectId,
|
||||
@@ -256,7 +287,7 @@ export class CorrespondenceService {
|
||||
|
||||
if (search) {
|
||||
query.andWhere(
|
||||
'(corr.correspondenceNumber LIKE :search OR rev.title LIKE :search)',
|
||||
'(corr.correspondenceNumber LIKE :search OR rev.subject LIKE :search)',
|
||||
{ search: `%${search}%` }
|
||||
);
|
||||
}
|
||||
@@ -286,6 +317,8 @@ export class CorrespondenceService {
|
||||
'type',
|
||||
'project',
|
||||
'originator',
|
||||
'recipients',
|
||||
'recipients.recipientOrganization', // [v1.5.1] Fixed relation name
|
||||
],
|
||||
});
|
||||
|
||||
@@ -352,4 +385,181 @@ export class CorrespondenceService {
|
||||
|
||||
return { outgoing, incoming };
|
||||
}
|
||||
|
||||
async update(id: number, updateDto: UpdateCorrespondenceDto, user: User) {
|
||||
// 1. Find Current Revision
|
||||
const revision = await this.revisionRepo.findOne({
|
||||
where: {
|
||||
correspondenceId: id,
|
||||
isCurrent: true,
|
||||
},
|
||||
relations: ['correspondence'],
|
||||
});
|
||||
|
||||
if (!revision) {
|
||||
throw new NotFoundException(
|
||||
`Current revision for correspondence ${id} not found`
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Check Permission
|
||||
if (revision.statusId) {
|
||||
const status = await this.statusRepo.findOne({
|
||||
where: { id: revision.statusId },
|
||||
});
|
||||
if (status && status.statusCode !== 'DRAFT') {
|
||||
throw new BadRequestException('Only DRAFT documents can be updated');
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Update Correspondence Entity if needed
|
||||
const correspondenceUpdate: DeepPartial<Correspondence> = {};
|
||||
if (updateDto.disciplineId)
|
||||
correspondenceUpdate.disciplineId = updateDto.disciplineId;
|
||||
if (updateDto.projectId)
|
||||
correspondenceUpdate.projectId = updateDto.projectId;
|
||||
|
||||
if (Object.keys(correspondenceUpdate).length > 0) {
|
||||
await this.correspondenceRepo.update(id, correspondenceUpdate);
|
||||
}
|
||||
|
||||
// 4. Update Revision Entity
|
||||
const revisionUpdate: DeepPartial<CorrespondenceRevision> = {};
|
||||
if (updateDto.subject) revisionUpdate.subject = updateDto.subject;
|
||||
if (updateDto.body) revisionUpdate.body = updateDto.body;
|
||||
if (updateDto.remarks) revisionUpdate.remarks = updateDto.remarks;
|
||||
// Format Date correctly if string
|
||||
if (updateDto.dueDate) revisionUpdate.dueDate = new Date(updateDto.dueDate);
|
||||
if (updateDto.description)
|
||||
revisionUpdate.description = updateDto.description;
|
||||
if (updateDto.details) revisionUpdate.details = updateDto.details;
|
||||
|
||||
if (Object.keys(revisionUpdate).length > 0) {
|
||||
await this.revisionRepo.update(revision.id, revisionUpdate);
|
||||
}
|
||||
|
||||
// 5. Update Recipients if provided
|
||||
if (updateDto.recipients) {
|
||||
const recipientRepo = this.dataSource.getRepository(
|
||||
CorrespondenceRecipient
|
||||
);
|
||||
await recipientRepo.delete({ correspondenceId: id });
|
||||
|
||||
const newRecipients = updateDto.recipients.map((r) =>
|
||||
recipientRepo.create({
|
||||
correspondenceId: id,
|
||||
recipientOrganizationId: r.organizationId,
|
||||
recipientType: r.type,
|
||||
})
|
||||
);
|
||||
await recipientRepo.save(newRecipients);
|
||||
}
|
||||
|
||||
// 6. Regenerate Document Number if structural fields changed (Recipient, Discipline, Type, Project)
|
||||
// AND it is a DRAFT.
|
||||
const hasRecipientChange = !!updateDto.recipients?.find(
|
||||
(r) => r.type === 'TO'
|
||||
);
|
||||
const hasStructureChange =
|
||||
updateDto.typeId ||
|
||||
updateDto.disciplineId ||
|
||||
updateDto.projectId ||
|
||||
hasRecipientChange;
|
||||
|
||||
if (hasStructureChange) {
|
||||
// Re-fetch fresh data for context
|
||||
const freshCorr = await this.correspondenceRepo.findOne({
|
||||
where: { id },
|
||||
relations: ['type', 'recipients', 'recipients.recipientOrganization'],
|
||||
});
|
||||
|
||||
if (freshCorr) {
|
||||
const toRecipient = freshCorr.recipients?.find(
|
||||
(r) => r.recipientType === 'TO'
|
||||
);
|
||||
const recipientOrganizationId = toRecipient?.recipientOrganizationId;
|
||||
const type = freshCorr.type;
|
||||
|
||||
let recipientCode = '';
|
||||
if (toRecipient?.recipientOrganization) {
|
||||
recipientCode = toRecipient.recipientOrganization.organizationCode;
|
||||
} else if (recipientOrganizationId) {
|
||||
// Fallback fetch if relation not loaded (though we added it)
|
||||
const recOrg = await this.orgRepo.findOne({
|
||||
where: { id: recipientOrganizationId },
|
||||
});
|
||||
if (recOrg) recipientCode = recOrg.organizationCode;
|
||||
}
|
||||
|
||||
const orgCode = 'ORG'; // Placeholder
|
||||
|
||||
const newDocNumber = await this.numberingService.generateNextNumber({
|
||||
projectId: freshCorr.projectId,
|
||||
originatorId: freshCorr.originatorId!,
|
||||
typeId: freshCorr.correspondenceTypeId,
|
||||
disciplineId: freshCorr.disciplineId,
|
||||
// Use undefined for subTypeId if not present implicitly
|
||||
year: new Date().getFullYear(),
|
||||
recipientOrganizationId: recipientOrganizationId ?? 0,
|
||||
customTokens: {
|
||||
TYPE_CODE: type?.typeCode || '',
|
||||
ORG_CODE: orgCode,
|
||||
RECIPIENT_CODE: recipientCode,
|
||||
REC_CODE: recipientCode,
|
||||
},
|
||||
});
|
||||
|
||||
await this.correspondenceRepo.update(id, {
|
||||
correspondenceNumber: newDocNumber,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return this.findOne(id);
|
||||
}
|
||||
|
||||
async previewDocumentNumber(createDto: CreateCorrespondenceDto, user: User) {
|
||||
const type = await this.typeRepo.findOne({
|
||||
where: { id: createDto.typeId },
|
||||
});
|
||||
if (!type) throw new NotFoundException('Document Type not found');
|
||||
|
||||
let userOrgId = user.primaryOrganizationId;
|
||||
if (!userOrgId) {
|
||||
const fullUser = await this.userService.findOne(user.user_id);
|
||||
if (fullUser) userOrgId = fullUser.primaryOrganizationId;
|
||||
}
|
||||
|
||||
if (createDto.originatorId && createDto.originatorId !== userOrgId) {
|
||||
// Allow impersonation for preview
|
||||
userOrgId = createDto.originatorId;
|
||||
}
|
||||
|
||||
// Extract recipient from recipients array
|
||||
const toRecipient = createDto.recipients?.find((r) => r.type === 'TO');
|
||||
const recipientOrganizationId = toRecipient?.organizationId;
|
||||
|
||||
let recipientCode = '';
|
||||
if (recipientOrganizationId) {
|
||||
const recOrg = await this.orgRepo.findOne({
|
||||
where: { id: recipientOrganizationId },
|
||||
});
|
||||
if (recOrg) recipientCode = recOrg.organizationCode;
|
||||
}
|
||||
|
||||
return this.numberingService.previewNextNumber({
|
||||
projectId: createDto.projectId,
|
||||
originatorId: userOrgId!,
|
||||
typeId: createDto.typeId,
|
||||
disciplineId: createDto.disciplineId,
|
||||
subTypeId: createDto.subTypeId,
|
||||
recipientOrganizationId,
|
||||
year: new Date().getFullYear(),
|
||||
customTokens: {
|
||||
TYPE_CODE: type.typeCode,
|
||||
RECIPIENT_CODE: recipientCode,
|
||||
REC_CODE: recipientCode,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
IsOptional,
|
||||
IsBoolean,
|
||||
IsObject,
|
||||
IsDateString,
|
||||
IsArray,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
@@ -30,12 +32,36 @@ export class CreateCorrespondenceDto {
|
||||
subTypeId?: number; // [Req 6B] ประเภทย่อย (เช่น MAT, SHP สำหรับ Transmittal/RFA)
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Correspondence Title',
|
||||
description: 'Correspondence Subject',
|
||||
example: 'Monthly Progress Report',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
title!: string;
|
||||
subject!: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Body/Content',
|
||||
example: '<p>...</p>',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
body?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Remarks',
|
||||
example: 'Note...',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
remarks?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Due Date',
|
||||
example: '2025-12-06T00:00:00Z',
|
||||
})
|
||||
@IsDateString()
|
||||
@IsOptional()
|
||||
dueDate?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Correspondence Description',
|
||||
@@ -66,4 +92,12 @@ export class CreateCorrespondenceDto {
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
originatorId?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Recipients',
|
||||
example: [{ organizationId: 1, type: 'TO' }],
|
||||
})
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
recipients?: { organizationId: number; type: 'TO' | 'CC' }[];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateCorrespondenceDto } from './create-correspondence.dto';
|
||||
|
||||
export class UpdateCorrespondenceDto extends PartialType(
|
||||
CreateCorrespondenceDto
|
||||
) {}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { Correspondence } from './correspondence.entity';
|
||||
import { Organization } from '../../organization/entities/organization.entity';
|
||||
|
||||
@Entity('correspondence_recipients')
|
||||
export class CorrespondenceRecipient {
|
||||
@PrimaryColumn({ name: 'correspondence_id' })
|
||||
correspondenceId!: number;
|
||||
|
||||
@PrimaryColumn({ name: 'recipient_organization_id' })
|
||||
recipientOrganizationId!: number;
|
||||
|
||||
@PrimaryColumn({ name: 'recipient_type', type: 'enum', enum: ['TO', 'CC'] })
|
||||
recipientType!: 'TO' | 'CC';
|
||||
|
||||
// Relations
|
||||
@ManyToOne(() => Correspondence, (corr) => corr.recipients, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'correspondence_id' })
|
||||
correspondence!: Correspondence;
|
||||
|
||||
@ManyToOne(() => Organization)
|
||||
@JoinColumn({ name: 'recipient_organization_id' })
|
||||
recipientOrganization!: Organization;
|
||||
}
|
||||
@@ -35,15 +35,24 @@ export class CorrespondenceRevision {
|
||||
@Column({ name: 'correspondence_status_id' })
|
||||
statusId!: number;
|
||||
|
||||
@Column({ length: 255 })
|
||||
title!: string;
|
||||
@Column({ length: 500 })
|
||||
subject!: string;
|
||||
|
||||
@Column({ name: 'description', type: 'text', nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
body?: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
remarks?: string;
|
||||
|
||||
@Column({ type: 'json', nullable: true })
|
||||
details?: any; // เก็บข้อมูลแบบ Dynamic ตาม Type
|
||||
|
||||
@Column({ name: 'schema_version', default: 1 })
|
||||
schemaVersion!: number;
|
||||
|
||||
// ✅ [New] Virtual Column: ดึง Project ID จาก JSON details
|
||||
@Column({
|
||||
name: 'v_ref_project_id',
|
||||
|
||||
@@ -12,7 +12,9 @@ import { Project } from '../../project/entities/project.entity';
|
||||
import { Organization } from '../../organization/entities/organization.entity';
|
||||
import { CorrespondenceType } from './correspondence-type.entity';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { CorrespondenceRevision } from './correspondence-revision.entity'; // เดี๋ยวสร้าง
|
||||
import { CorrespondenceRecipient } from './correspondence-recipient.entity';
|
||||
import { CorrespondenceRevision } from './correspondence-revision.entity';
|
||||
import { Discipline } from '../../master/entities/discipline.entity';
|
||||
|
||||
@Entity('correspondences')
|
||||
export class Correspondence {
|
||||
@@ -68,9 +70,9 @@ export class Correspondence {
|
||||
creator?: User;
|
||||
|
||||
// [New V1.5.1]
|
||||
@ManyToOne('Discipline')
|
||||
@ManyToOne(() => Discipline)
|
||||
@JoinColumn({ name: 'discipline_id' })
|
||||
discipline?: any; // Use 'any' or import Discipline entity if available to avoid circular dependency issues if not careful, but better to import.
|
||||
discipline?: Discipline;
|
||||
|
||||
// One Correspondence has Many Revisions
|
||||
@OneToMany(
|
||||
@@ -78,4 +80,11 @@ export class Correspondence {
|
||||
(revision) => revision.correspondence
|
||||
)
|
||||
revisions?: CorrespondenceRevision[];
|
||||
|
||||
@OneToMany(
|
||||
() => CorrespondenceRecipient,
|
||||
(recipient) => recipient.correspondence,
|
||||
{ cascade: true }
|
||||
)
|
||||
recipients?: CorrespondenceRecipient[];
|
||||
}
|
||||
|
||||
@@ -222,6 +222,53 @@ export class DocumentNumberingService implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview the next document number without incrementing the counter.
|
||||
* Returns the number and whether a custom template was found.
|
||||
*/
|
||||
async previewNextNumber(
|
||||
ctx: GenerateNumberContext
|
||||
): Promise<{ number: string; isDefaultTemplate: boolean }> {
|
||||
const year = ctx.year || new Date().getFullYear();
|
||||
const disciplineId = ctx.disciplineId || 0;
|
||||
|
||||
// 1. Resolve Tokens
|
||||
const tokens = await this.resolveTokens(ctx, year);
|
||||
|
||||
// 2. Get Format Template
|
||||
const { template, isDefault } = await this.getFormatTemplateWithMeta(
|
||||
ctx.projectId,
|
||||
ctx.typeId
|
||||
);
|
||||
|
||||
// 3. Get Current Counter (No Lock needed for preview)
|
||||
const recipientId = ctx.recipientOrganizationId ?? -1;
|
||||
const subTypeId = ctx.subTypeId ?? 0;
|
||||
const rfaTypeId = ctx.rfaTypeId ?? 0;
|
||||
|
||||
const counter = await this.counterRepo.findOne({
|
||||
where: {
|
||||
projectId: ctx.projectId,
|
||||
originatorId: ctx.originatorId,
|
||||
recipientOrganizationId: recipientId,
|
||||
typeId: ctx.typeId,
|
||||
subTypeId: subTypeId,
|
||||
rfaTypeId: rfaTypeId,
|
||||
disciplineId: disciplineId,
|
||||
year: year,
|
||||
},
|
||||
});
|
||||
|
||||
const nextSeq = (counter?.lastNumber || 0) + 1;
|
||||
|
||||
const generatedNumber = this.replaceTokens(template, tokens, nextSeq);
|
||||
|
||||
return {
|
||||
number: generatedNumber,
|
||||
isDefaultTemplate: isDefault,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: ดึงข้อมูล Code ต่างๆ จาก ID เพื่อนำมาแทนที่ใน Template
|
||||
*/
|
||||
@@ -239,17 +286,20 @@ export class DocumentNumberingService implements OnModuleInit, OnModuleDestroy {
|
||||
throw new NotFoundException('Project, Organization, or Type not found');
|
||||
}
|
||||
|
||||
let disciplineCode = '000';
|
||||
if (ctx.disciplineId) {
|
||||
// [v1.5.1] Support Custom Tokens Override
|
||||
const custom = ctx.customTokens || {};
|
||||
|
||||
let disciplineCode = custom.DISCIPLINE_CODE || '000';
|
||||
if (!custom.DISCIPLINE_CODE && ctx.disciplineId) {
|
||||
const discipline = await this.disciplineRepo.findOne({
|
||||
where: { id: ctx.disciplineId },
|
||||
});
|
||||
if (discipline) disciplineCode = discipline.disciplineCode;
|
||||
}
|
||||
|
||||
let subTypeCode = '00';
|
||||
let subTypeNumber = '00';
|
||||
if (ctx.subTypeId) {
|
||||
let subTypeCode = custom.SUB_TYPE_CODE || '00';
|
||||
let subTypeNumber = custom.SUB_TYPE_NUMBER || '00';
|
||||
if (!custom.SUB_TYPE_CODE && ctx.subTypeId) {
|
||||
const subType = await this.subTypeRepo.findOne({
|
||||
where: { id: ctx.subTypeId },
|
||||
});
|
||||
@@ -264,8 +314,12 @@ export class DocumentNumberingService implements OnModuleInit, OnModuleDestroy {
|
||||
const yearTh = (year + 543).toString();
|
||||
|
||||
// [v1.5.1] Resolve recipient organization
|
||||
let recipientCode = '';
|
||||
if (ctx.recipientOrganizationId && ctx.recipientOrganizationId > 0) {
|
||||
let recipientCode = custom.RECIPIENT_CODE || custom.REC_CODE || '';
|
||||
if (
|
||||
!recipientCode &&
|
||||
ctx.recipientOrganizationId &&
|
||||
ctx.recipientOrganizationId > 0
|
||||
) {
|
||||
const recipient = await this.orgRepo.findOne({
|
||||
where: { id: ctx.recipientOrganizationId },
|
||||
});
|
||||
@@ -288,17 +342,36 @@ export class DocumentNumberingService implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: หา Template จาก DB หรือใช้ Default
|
||||
* Helper: Find Template from DB or use Default (with metadata)
|
||||
*/
|
||||
private async getFormatTemplateWithMeta(
|
||||
projectId: number,
|
||||
typeId: number
|
||||
): Promise<{ template: string; isDefault: boolean }> {
|
||||
const format = await this.formatRepo.findOne({
|
||||
where: { projectId, correspondenceTypeId: typeId },
|
||||
});
|
||||
|
||||
if (format) {
|
||||
return { template: format.formatTemplate, isDefault: false };
|
||||
}
|
||||
|
||||
// Default Fallback Format
|
||||
return { template: '{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR}', isDefault: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy wrapper for backward compatibility
|
||||
*/
|
||||
private async getFormatTemplate(
|
||||
projectId: number,
|
||||
typeId: number
|
||||
): Promise<string> {
|
||||
const format = await this.formatRepo.findOne({
|
||||
where: { projectId, correspondenceTypeId: typeId },
|
||||
});
|
||||
// Default Fallback Format (ตาม Req 2.1)
|
||||
return format ? format.formatTemplate : '{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR}';
|
||||
const { template } = await this.getFormatTemplateWithMeta(
|
||||
projectId,
|
||||
typeId
|
||||
);
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,10 +411,6 @@ export class DocumentNumberingService implements OnModuleInit, OnModuleDestroy {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* [P0-4] Log successful number generation to audit table
|
||||
*/
|
||||
|
||||
/**
|
||||
* [P0-4] Log successful number generation to audit table
|
||||
*/
|
||||
|
||||
@@ -10,10 +10,25 @@ import {
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class CreateRfaRevisionDto {
|
||||
@ApiProperty({ description: 'RFA Title', example: 'RFA for Building A' })
|
||||
@ApiProperty({ description: 'RFA Subject', example: 'RFA for Building A' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
title!: string;
|
||||
subject!: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Body', example: '<p>...</p>' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
body?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Remarks', example: 'Note' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
remarks?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Due Date', example: '2025-12-06' })
|
||||
@IsDateString()
|
||||
@IsOptional()
|
||||
dueDate?: string;
|
||||
|
||||
@ApiProperty({ description: 'RFA Status Code ID', example: 1 })
|
||||
@IsInt()
|
||||
|
||||
@@ -35,7 +35,22 @@ export class CreateRfaDto {
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
title!: string;
|
||||
subject!: string;
|
||||
|
||||
@ApiProperty({ description: 'Body', required: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
body?: string;
|
||||
|
||||
@ApiProperty({ description: 'Remarks', required: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
remarks?: string;
|
||||
|
||||
@ApiProperty({ description: 'Due Date', required: false })
|
||||
@IsDateString()
|
||||
@IsOptional()
|
||||
dueDate?: string;
|
||||
|
||||
@ApiProperty({ description: 'รายละเอียดเพิ่มเติม', required: false })
|
||||
@IsString()
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ShopDrawingRevision } from '../../drawing/entities/shop-drawing-revisio
|
||||
|
||||
@Entity('rfa_items')
|
||||
export class RfaItem {
|
||||
@PrimaryColumn({ name: 'rfarev_correspondence_id' })
|
||||
@PrimaryColumn({ name: 'rfa_revision_id' })
|
||||
rfaRevisionId!: number;
|
||||
|
||||
@PrimaryColumn({ name: 'shop_drawing_revision_id' })
|
||||
@@ -14,11 +14,7 @@ export class RfaItem {
|
||||
@ManyToOne(() => RfaRevision, (rfaRev) => rfaRev.items, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'rfarev_correspondence_id' }) // Link to correspondence_id of the revision (as per SQL schema) OR id
|
||||
// Note: ตาม SQL Schema "rfarev_correspondence_id" FK ไปที่ correspondence_revisions(correspondence_id)
|
||||
// แต่เพื่อให้ TypeORM ใช้ง่าย ปกติเราจะ Link ไปที่ PK ของ RfaRevision
|
||||
// **แต่** ตาม SQL: FOREIGN KEY (rfarev_correspondence_id) REFERENCES correspondences(id)
|
||||
// ดังนั้นต้องระวังจุดนี้ ใน Service เราจะใช้ correspondenceId เป็น Key
|
||||
@JoinColumn({ name: 'rfa_revision_id' })
|
||||
rfaRevision!: RfaRevision;
|
||||
|
||||
@ManyToOne(() => ShopDrawingRevision)
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import { Correspondence } from '../../correspondence/entities/correspondence.entity';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { RfaApproveCode } from './rfa-approve-code.entity';
|
||||
import { RfaItem } from './rfa-item.entity';
|
||||
@@ -24,9 +23,6 @@ export class RfaRevision {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column({ name: 'correspondence_id' })
|
||||
correspondenceId!: number;
|
||||
|
||||
@Column({ name: 'rfa_id' })
|
||||
rfaId!: number;
|
||||
|
||||
@@ -45,8 +41,8 @@ export class RfaRevision {
|
||||
@Column({ name: 'rfa_approve_code_id', nullable: true })
|
||||
rfaApproveCodeId?: number;
|
||||
|
||||
@Column({ length: 255 })
|
||||
title!: string;
|
||||
@Column({ length: 500 })
|
||||
subject!: string;
|
||||
|
||||
@Column({ name: 'document_date', type: 'date', nullable: true })
|
||||
documentDate?: Date;
|
||||
@@ -57,12 +53,21 @@ export class RfaRevision {
|
||||
@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 })
|
||||
@@ -95,10 +100,6 @@ export class RfaRevision {
|
||||
|
||||
// --- Relations ---
|
||||
|
||||
@ManyToOne(() => Correspondence)
|
||||
@JoinColumn({ name: 'correspondence_id' })
|
||||
correspondence!: Correspondence;
|
||||
|
||||
@ManyToOne(() => Rfa)
|
||||
@JoinColumn({ name: 'rfa_id' })
|
||||
rfa!: Rfa;
|
||||
|
||||
@@ -6,18 +6,24 @@ import {
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
PrimaryColumn,
|
||||
OneToOne,
|
||||
} from 'typeorm';
|
||||
import { Discipline } from '../../master/entities/discipline.entity'; // Import ใหม่
|
||||
|
||||
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')
|
||||
export class Rfa {
|
||||
@PrimaryGeneratedColumn()
|
||||
@PrimaryColumn()
|
||||
id!: number;
|
||||
|
||||
@OneToOne(() => Correspondence, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'id' })
|
||||
correspondence!: Correspondence;
|
||||
|
||||
@Column({ name: 'rfa_type_id' })
|
||||
rfaTypeId!: number;
|
||||
|
||||
@@ -35,11 +41,6 @@ export class Rfa {
|
||||
@JoinColumn({ name: 'rfa_type_id' })
|
||||
rfaType!: RfaType;
|
||||
|
||||
// ✅ [NEW] Relation
|
||||
@ManyToOne(() => Discipline)
|
||||
@JoinColumn({ name: 'discipline_id' })
|
||||
discipline?: Discipline;
|
||||
|
||||
@ManyToOne(() => User)
|
||||
@JoinColumn({ name: 'created_by' })
|
||||
creator?: User;
|
||||
|
||||
@@ -31,7 +31,7 @@ export class RfaWorkflowService {
|
||||
private readonly statusRepo: Repository<RfaStatusCode>,
|
||||
@InjectRepository(RfaApproveCode)
|
||||
private readonly approveCodeRepo: Repository<RfaApproveCode>,
|
||||
private readonly dataSource: DataSource,
|
||||
private readonly dataSource: DataSource
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -46,19 +46,23 @@ export class RfaWorkflowService {
|
||||
// 1. ดึงข้อมูล Revision ปัจจุบัน
|
||||
const revision = await this.revisionRepo.findOne({
|
||||
where: { id: rfaId, isCurrent: true },
|
||||
relations: ['rfa'],
|
||||
relations: [
|
||||
'rfa',
|
||||
'rfa.correspondence',
|
||||
'rfa.correspondence.discipline',
|
||||
],
|
||||
});
|
||||
|
||||
if (!revision) {
|
||||
throw new NotFoundException(
|
||||
`Current Revision for RFA ID ${rfaId} not found`,
|
||||
`Current Revision for RFA ID ${rfaId} not found`
|
||||
);
|
||||
}
|
||||
|
||||
// 2. สร้าง Context (ข้อมูลประกอบการตัดสินใจ)
|
||||
const context = {
|
||||
rfaType: revision.rfa.rfaTypeId,
|
||||
discipline: revision.rfa.discipline,
|
||||
discipline: revision.rfa.correspondence?.discipline,
|
||||
ownerId: userId,
|
||||
// อาจเพิ่มเงื่อนไขอื่นๆ เช่น จำนวนวัน, ความเร่งด่วน
|
||||
};
|
||||
@@ -69,7 +73,7 @@ export class RfaWorkflowService {
|
||||
this.WORKFLOW_CODE,
|
||||
'rfa_revision',
|
||||
revision.id.toString(),
|
||||
context,
|
||||
context
|
||||
);
|
||||
|
||||
// 4. Auto Transition: SUBMIT
|
||||
@@ -78,7 +82,7 @@ export class RfaWorkflowService {
|
||||
'SUBMIT',
|
||||
userId,
|
||||
note || 'RFA Submitted',
|
||||
{},
|
||||
{}
|
||||
);
|
||||
|
||||
// 5. Sync สถานะกลับตาราง RFA Revision
|
||||
@@ -86,13 +90,13 @@ export class RfaWorkflowService {
|
||||
revision,
|
||||
transitionResult.nextState,
|
||||
undefined,
|
||||
queryRunner,
|
||||
queryRunner
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
this.logger.log(
|
||||
`Started workflow for RFA #${rfaId} (Instance: ${instance.id})`,
|
||||
`Started workflow for RFA #${rfaId} (Instance: ${instance.id})`
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -114,7 +118,7 @@ export class RfaWorkflowService {
|
||||
async processAction(
|
||||
instanceId: string,
|
||||
userId: number,
|
||||
dto: WorkflowTransitionDto,
|
||||
dto: WorkflowTransitionDto
|
||||
) {
|
||||
// 1. ส่งคำสั่งให้ Engine ประมวลผล
|
||||
const result = await this.workflowEngine.processTransition(
|
||||
@@ -122,7 +126,7 @@ export class RfaWorkflowService {
|
||||
dto.action,
|
||||
userId,
|
||||
dto.comment,
|
||||
dto.payload,
|
||||
dto.payload
|
||||
);
|
||||
|
||||
// 2. Sync สถานะกลับตารางเดิม
|
||||
@@ -148,7 +152,7 @@ export class RfaWorkflowService {
|
||||
revision: RfaRevision,
|
||||
workflowState: string,
|
||||
approveCodeStr?: string, // เช่น '1A', '1C'
|
||||
queryRunner?: any,
|
||||
queryRunner?: any
|
||||
) {
|
||||
// 1. Map Workflow State -> RFA Status Code (DFT, FAP, FCO...)
|
||||
const statusMap: Record<string, string> = {
|
||||
@@ -187,7 +191,7 @@ export class RfaWorkflowService {
|
||||
await manager.save(revision);
|
||||
|
||||
this.logger.log(
|
||||
`Synced RFA Status Revision ${revision.id}: State=${workflowState} -> Status=${targetStatusCode}, AppCode=${approveCodeStr}`,
|
||||
`Synced RFA Status Revision ${revision.id}: State=${workflowState} -> Status=${targetStatusCode}, AppCode=${approveCodeStr}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
// Entities
|
||||
import { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity';
|
||||
import { Correspondence } from '../correspondence/entities/correspondence.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';
|
||||
import { ShopDrawingRevision } from '../drawing/entities/shop-drawing-revision.entity';
|
||||
@@ -47,6 +48,7 @@ import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module'
|
||||
CorrespondenceRouting,
|
||||
RoutingTemplate,
|
||||
RoutingTemplateStep,
|
||||
CorrespondenceRecipient,
|
||||
]),
|
||||
DocumentNumberingModule,
|
||||
UserModule,
|
||||
|
||||
@@ -148,11 +148,14 @@ export class RfaService {
|
||||
revisionLabel: '0',
|
||||
isCurrent: true,
|
||||
rfaStatusCodeId: statusDraft.id,
|
||||
title: createDto.title,
|
||||
subject: createDto.subject,
|
||||
body: createDto.body,
|
||||
remarks: createDto.remarks,
|
||||
description: createDto.description,
|
||||
documentDate: createDto.documentDate
|
||||
? new Date(createDto.documentDate)
|
||||
: new Date(),
|
||||
dueDate: createDto.dueDate ? new Date(createDto.dueDate) : undefined,
|
||||
createdBy: user.user_id,
|
||||
details: createDto.details,
|
||||
schemaVersion: 1,
|
||||
@@ -209,7 +212,7 @@ export class RfaService {
|
||||
id: savedCorr.id,
|
||||
type: 'rfa',
|
||||
docNumber: docNumber,
|
||||
title: createDto.title,
|
||||
title: createDto.subject,
|
||||
description: createDto.description,
|
||||
status: 'DRAFT',
|
||||
projectId: createDto.projectId,
|
||||
@@ -242,10 +245,10 @@ export class RfaService {
|
||||
// [Force Rebuild]
|
||||
const queryBuilder = this.rfaRepo
|
||||
.createQueryBuilder('rfa')
|
||||
.leftJoinAndSelect('rfa.correspondence', 'corr')
|
||||
.leftJoinAndSelect('rfa.revisions', 'rev')
|
||||
.leftJoinAndSelect('rev.correspondence', 'corr')
|
||||
.leftJoinAndSelect('corr.project', 'project')
|
||||
.leftJoinAndSelect('rfa.discipline', 'discipline')
|
||||
.leftJoinAndSelect('corr.discipline', 'discipline')
|
||||
.leftJoinAndSelect('rev.statusCode', 'status')
|
||||
.leftJoinAndSelect('rev.items', 'items')
|
||||
.leftJoinAndSelect('items.shopDrawingRevision', 'sdRev')
|
||||
@@ -271,7 +274,7 @@ export class RfaService {
|
||||
|
||||
if (search) {
|
||||
queryBuilder.andWhere(
|
||||
'(corr.correspondenceNumber LIKE :search OR rev.title LIKE :search)',
|
||||
'(corr.correspondenceNumber LIKE :search OR rev.subject LIKE :search)',
|
||||
{ search: `%${search}%` }
|
||||
);
|
||||
}
|
||||
@@ -301,11 +304,11 @@ export class RfaService {
|
||||
const rfa = await this.rfaRepo.findOne({
|
||||
where: { id },
|
||||
relations: [
|
||||
'correspondence', // ✅ Add relation to master correspondence
|
||||
'rfaType',
|
||||
'revisions',
|
||||
'revisions.statusCode',
|
||||
'revisions.approveCode',
|
||||
'revisions.correspondence',
|
||||
'revisions.items',
|
||||
'revisions.items.shopDrawingRevision',
|
||||
'revisions.items.shopDrawingRevision.shopDrawing',
|
||||
@@ -370,7 +373,7 @@ export class RfaService {
|
||||
// Create First Routing Step
|
||||
const firstStep = steps[0];
|
||||
const routing = queryRunner.manager.create(CorrespondenceRouting, {
|
||||
correspondenceId: currentRevision.correspondenceId,
|
||||
correspondenceId: rfa.correspondence.id, // ✅ Use master correspondence id
|
||||
templateId: template.id,
|
||||
sequence: 1,
|
||||
fromOrganizationId: user.primaryOrganizationId,
|
||||
@@ -392,8 +395,8 @@ export class RfaService {
|
||||
if (recipientUserId) {
|
||||
await this.notificationService.send({
|
||||
userId: recipientUserId,
|
||||
title: `RFA Submitted: ${currentRevision.title}`,
|
||||
message: `RFA ${currentRevision.correspondence.correspondenceNumber} submitted for approval.`,
|
||||
title: `RFA Submitted: ${currentRevision.subject}`,
|
||||
message: `RFA ${rfa.correspondence.correspondenceNumber} submitted for approval.`,
|
||||
type: 'SYSTEM',
|
||||
entityType: 'rfa',
|
||||
entityId: rfa.id,
|
||||
@@ -421,7 +424,7 @@ export class RfaService {
|
||||
|
||||
const currentRouting = await this.routingRepo.findOne({
|
||||
where: {
|
||||
correspondenceId: currentRevision.correspondenceId,
|
||||
correspondenceId: rfa.correspondence.id, // ✅ Use master correspondence id
|
||||
status: 'SENT',
|
||||
},
|
||||
order: { sequence: 'DESC' },
|
||||
@@ -482,7 +485,7 @@ export class RfaService {
|
||||
const nextRouting = queryRunner.manager.create(
|
||||
CorrespondenceRouting,
|
||||
{
|
||||
correspondenceId: currentRevision.correspondenceId,
|
||||
correspondenceId: rfa.correspondence.id, // ✅ Use master correspondence id
|
||||
templateId: template.id,
|
||||
sequence: result.nextStepSequence,
|
||||
fromOrganizationId: user.primaryOrganizationId,
|
||||
|
||||
Reference in New Issue
Block a user