260321:1700 Correct Coresspondence / Doing RFA

This commit is contained in:
admin
2026-03-21 17:00:41 +07:00
parent dcf55f4d08
commit 03d16cfd64
57 changed files with 1923 additions and 663 deletions
@@ -32,7 +32,7 @@ export class DocumentNumberingAdminController {
@Get('templates')
@ApiOperation({ summary: 'Get all document numbering templates' })
@RequirePermission('system.manage_settings')
@RequirePermission('numbering.view_formats')
async getTemplates(@Query('projectId') projectId?: number) {
if (projectId) {
return this.service.getTemplatesByProject(projectId);
@@ -42,7 +42,7 @@ export class DocumentNumberingAdminController {
@Post('templates')
@ApiOperation({ summary: 'Create or Update a numbering template' })
@RequirePermission('system.manage_settings')
@RequirePermission('numbering.manage_formats')
async saveTemplate(
@Body() dto: Partial<DocumentNumberFormat> & { projectId?: number | string }
) {
@@ -51,7 +51,7 @@ export class DocumentNumberingAdminController {
@Delete('templates/:id')
@ApiOperation({ summary: 'Delete a numbering template' })
@RequirePermission('system.manage_settings')
@RequirePermission('numbering.manage_formats')
async deleteTemplate(@Param('id', ParseIntPipe) id: number) {
await this.service.deleteTemplate(id);
return { success: true };
@@ -78,7 +78,7 @@ export class DocumentNumberingAdminController {
@ApiOperation({
summary: 'Manually override or set a document number counter',
})
@RequirePermission('system.manage_settings')
@RequirePermission('numbering.manage_formats')
async manualOverride(
@Body() dto: ManualOverrideDto,
@CurrentUser() user: User
@@ -88,7 +88,7 @@ export class DocumentNumberingAdminController {
@Post('void-and-replace')
@ApiOperation({ summary: 'Void a number and replace with a new generation' })
@RequirePermission('system.manage_settings')
@RequirePermission('numbering.manage_formats')
async voidAndReplace(
@Body()
dto: {
@@ -104,7 +104,7 @@ export class DocumentNumberingAdminController {
@Post('cancel')
@ApiOperation({ summary: 'Cancel/Skip a specific document number' })
@RequirePermission('system.manage_settings')
@RequirePermission('numbering.manage_formats')
async cancelNumber(
@Body()
dto: {
@@ -119,7 +119,7 @@ export class DocumentNumberingAdminController {
@Post('bulk-import')
@ApiOperation({ summary: 'Bulk import/set document number counters' })
@RequirePermission('system.manage_settings')
@RequirePermission('numbering.manage_formats')
async bulkImport(@Body() items: ManualOverrideDto[]) {
return this.service.bulkImport(items);
}
@@ -68,7 +68,7 @@ export class DocumentNumberingController {
@Patch('counters/:id')
@ApiOperation({ summary: 'Update counter sequence value (Admin only)' })
@RequirePermission('system.manage_settings')
@RequirePermission('numbering.manage_formats')
async updateCounter(
@Param('id', ParseIntPipe) id: number,
@Body('sequence') sequence: number
@@ -105,7 +105,7 @@ export class DocumentNumberingController {
)
: undefined;
return this.numberingService.previewNumber({
const result = await this.numberingService.previewNumber({
projectId: resolvedProjectId,
originatorOrganizationId: resolvedOriginatorId,
typeId: dto.correspondenceTypeId,
@@ -116,5 +116,7 @@ export class DocumentNumberingController {
year: dto.year,
customTokens: dto.customTokens,
});
console.log('[DocumentNumberingController] Preview result:', JSON.stringify(result));
return result;
}
}
@@ -14,7 +14,7 @@ import { Project } from '../../project/entities/project.entity';
import { CorrespondenceType } from '../../correspondence/entities/correspondence-type.entity';
@Entity('document_number_formats')
@Unique(['projectId', 'correspondenceTypeId'])
@Unique(['projectId', 'correspondenceTypeId', 'disciplineId'])
export class DocumentNumberFormat {
@PrimaryGeneratedColumn()
id!: number;
@@ -25,6 +25,9 @@ export class DocumentNumberFormat {
@Column({ name: 'correspondence_type_id', nullable: true })
correspondenceTypeId?: number;
@Column({ name: 'discipline_id', default: 0 })
disciplineId!: number;
@Column({ name: 'format_string', length: 100 })
formatTemplate!: string;
@@ -35,6 +38,9 @@ export class DocumentNumberFormat {
@Column({ name: 'reset_annually', default: true })
resetSequenceYearly!: boolean;
@Column({ name: 'is_active', default: 1 })
isActive!: number;
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
@@ -5,7 +5,13 @@ import {
NotFoundException,
} from '@nestjs/common';
import { InjectRepository, InjectEntityManager } from '@nestjs/typeorm';
import { Repository, EntityManager } from 'typeorm';
import {
Repository,
EntityManager,
In,
IsNull,
Equal,
} from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { DocumentNumberFormat } from '../entities/document-number-format.entity';
@@ -121,7 +127,7 @@ export class DocumentNumberingService {
const sequence = await this.counterService.incrementCounter(key);
// 4. Format Number
const documentNumber = await this.formatService.format({
const { previewNumber: documentNumber } = await this.formatService.format({
projectId: ctx.projectId,
correspondenceTypeId: ctx.typeId,
subTypeId: ctx.subTypeId,
@@ -193,7 +199,7 @@ export class DocumentNumberingService {
async previewNumber(
ctx: GenerateNumberContext
): Promise<{ previewNumber: string; nextSequence: number }> {
): Promise<{ previewNumber: string; nextSequence: number; isDefault: boolean }> {
const currentYear = new Date().getFullYear();
const resetScope = `YEAR_${currentYear}`;
@@ -211,7 +217,7 @@ export class DocumentNumberingService {
const currentSeq = await this.counterService.getCurrentCounter(key);
const nextSequence = currentSeq + 1;
const previewNumber = await this.formatService.format({
const { previewNumber, isDefault } = await this.formatService.format({
projectId: ctx.projectId,
correspondenceTypeId: ctx.typeId,
subTypeId: ctx.subTypeId,
@@ -224,7 +230,7 @@ export class DocumentNumberingService {
recipientOrganizationId: ctx.recipientOrganizationId,
});
return { previewNumber, nextSequence };
return { previewNumber, nextSequence, isDefault };
}
/**
@@ -258,10 +264,36 @@ export class DocumentNumberingService {
async saveTemplate(
dto: Partial<DocumentNumberFormat> & { projectId?: number | string }
) {
if (dto.projectId) {
dto.projectId = await this.uuidResolver.resolveProjectId(dto.projectId);
try {
this.logger.log(`Saving numbering template: ${JSON.stringify(dto)}`);
// Resolve project ID if it's a UUID/String
if (dto.projectId && typeof dto.projectId === 'string') {
dto.projectId = await this.uuidResolver.resolveProjectId(dto.projectId);
}
// Upsert logic: If no ID provided, check for existing template with same business key
if (!dto.id) {
const existing = await this.formatRepo.findOne({
where: {
projectId: Number(dto.projectId),
correspondenceTypeId: dto.correspondenceTypeId ? Equal(dto.correspondenceTypeId) : IsNull(),
disciplineId: dto.disciplineId || 0,
},
});
if (existing) {
this.logger.log(`Found existing template ID: ${existing.id} for business key, updating instead of creating.`);
dto.id = existing.id;
}
}
const result = await this.formatRepo.save(dto);
this.logger.log(`Successfully saved template ID: ${result.id}`);
return result;
} catch (e: any) {
this.logger.error(`Failed to save numbering template: ${e.message}`, e.stack);
throw e;
}
return this.formatRepo.save(dto);
}
async deleteTemplate(id: number) {
@@ -39,12 +39,14 @@ export class FormatService {
private disciplineRepo: Repository<Discipline>
) {}
async format(options: FormatOptions): Promise<string> {
const { template } = await this.resolveFormatAndScope(options);
async format(options: FormatOptions): Promise<{ previewNumber: string; isDefault: boolean }> {
const { template, isDefault } = await this.resolveFormatAndScope(options);
const currentYear = options.year || new Date().getFullYear();
const tokens = await this.resolveTokens(options, currentYear);
return this.replaceTokens(template, tokens, options.sequence);
const previewNumber = this.replaceTokens(template, tokens, options.sequence);
console.log(`[FormatService] Generated: "${previewNumber}" | Template: "${template}" | isDefault: ${isDefault}`);
return { previewNumber, isDefault };
}
// --- Helpers ---
@@ -52,6 +54,7 @@ export class FormatService {
private async resolveFormatAndScope(options: FormatOptions): Promise<{
template: string;
resetSequenceYearly: boolean;
isDefault: boolean;
}> {
// 1. Specific Format
const specificFormat = await this.formatRepo.findOne({
@@ -64,6 +67,7 @@ export class FormatService {
return {
template: specificFormat.formatTemplate,
resetSequenceYearly: specificFormat.resetSequenceYearly,
isDefault: false,
};
// 2. Default Format
@@ -74,12 +78,14 @@ export class FormatService {
return {
template: defaultFormat.formatTemplate,
resetSequenceYearly: defaultFormat.resetSequenceYearly,
isDefault: true,
};
// 3. Fallback
return {
template: '{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}',
resetSequenceYearly: true,
isDefault: true,
};
}
@@ -61,7 +61,7 @@ export class ReservationService {
const sequence = await this.counterService.incrementCounter(counterKey);
// Format document number
const documentNumber = await this.formatService.format({
const { previewNumber: documentNumber } = await this.formatService.format({
...dto,
sequence,
resetScope: counterKey.resetScope,