260322:1648 Correct Coresspondence / Doing RFA / Correct CI
CI Pipeline / build (push) Failing after 12m41s
Build and Deploy / deploy (push) Failing after 2m44s

This commit is contained in:
admin
2026-03-22 16:48:12 +07:00
parent e5deedb42e
commit 11984bfa29
683 changed files with 105251 additions and 29068 deletions
@@ -116,7 +116,10 @@ export class DocumentNumberingController {
year: dto.year,
customTokens: dto.customTokens,
});
console.log('[DocumentNumberingController] Preview result:', JSON.stringify(result));
// console.log(
// '[DocumentNumberingController] Preview result:',
// JSON.stringify(result)
// );
return result;
}
}
@@ -1,4 +1,4 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { Controller, Get } from '@nestjs/common';
import { MetricsService } from '../services/metrics.service';
// import { PermissionGuard } from '../../auth/guards/permission.guard';
// import { Permissions } from '../../auth/decorators/permissions.decorator';
@@ -10,7 +10,7 @@ export class NumberingMetricsController {
@Get()
// @Permissions('system.view_logs')
async getMetrics() {
getMetrics() {
// Determine how to return metrics.
// Standard Prometheus metrics are usually exposed via a separate /metrics endpoint processing all metrics.
// If the frontend needs JSON data, we might need to query the current values from the registry or metrics service.
@@ -1,4 +1,5 @@
import { Test, TestingModule } from '@nestjs/testing';
import { Repository } from 'typeorm';
import { DocumentNumberingService } from './services/document-numbering.service';
import { CounterService } from './services/counter.service';
import { ReservationService } from './services/reservation.service';
@@ -124,8 +125,8 @@ describe('DocumentNumberingService', () => {
expect(result).toHaveProperty('number');
expect(result).toHaveProperty('auditId');
expect(result.number).toBe('DOC-0001');
expect(counterService.incrementCounter).toHaveBeenCalled();
expect(formatService.format).toHaveBeenCalled();
expect(counterService.incrementCounter as jest.Mock).toHaveBeenCalled();
expect(formatService.format as jest.Mock).toHaveBeenCalled();
});
it('should throw error when increment fails', async () => {
@@ -142,7 +143,9 @@ describe('DocumentNumberingService', () => {
describe('Admin Operations', () => {
it('voidAndReplace should verify audit log exists', async () => {
const auditRepo = module.get(getRepositoryToken(DocumentNumberAudit));
const auditRepo = module.get<Repository<DocumentNumberAudit>>(
getRepositoryToken(DocumentNumberAudit)
);
(auditRepo.findOne as jest.Mock).mockResolvedValue({
documentNumber: 'DOC-001',
counterKey: JSON.stringify({ projectId: 1, correspondenceTypeId: 1 }),
@@ -156,11 +159,13 @@ describe('DocumentNumberingService', () => {
replace: false,
});
expect(result.status).toBe('VOIDED');
expect(auditRepo.save).toHaveBeenCalled();
expect(auditRepo.save as jest.Mock).toHaveBeenCalled();
});
it('cancelNumber should log cancellation', async () => {
const auditRepo = module.get(getRepositoryToken(DocumentNumberAudit));
const auditRepo = module.get<Repository<DocumentNumberAudit>>(
getRepositoryToken(DocumentNumberAudit)
);
(auditRepo.findOne as jest.Mock).mockResolvedValue({
documentNumber: 'DOC-002',
counterKey: {},
@@ -173,7 +178,7 @@ describe('DocumentNumberingService', () => {
projectId: 1,
});
expect(result.status).toBe('CANCELLED');
expect(auditRepo.save).toHaveBeenCalled();
expect(auditRepo.save as jest.Mock).toHaveBeenCalled();
});
});
});
@@ -25,7 +25,7 @@ export class DocumentNumberAudit {
documentNumber!: string;
@Column({ name: 'counter_key', type: 'json' })
counterKey!: Record<string, unknown> | unknown;
counterKey!: Record<string, unknown>;
@Column({ name: 'template_used', length: 200 })
templateUsed!: string;
@@ -1,17 +1,6 @@
import {
Injectable,
Logger,
BadRequestException,
NotFoundException,
} from '@nestjs/common';
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import { InjectRepository, InjectEntityManager } from '@nestjs/typeorm';
import {
Repository,
EntityManager,
In,
IsNull,
Equal,
} from 'typeorm';
import { Repository, EntityManager, IsNull, Equal } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { DocumentNumberFormat } from '../entities/document-number-format.entity';
@@ -127,18 +116,20 @@ export class DocumentNumberingService {
const sequence = await this.counterService.incrementCounter(key);
// 4. Format Number
const { previewNumber: documentNumber } = await this.formatService.format({
projectId: ctx.projectId,
correspondenceTypeId: ctx.typeId,
subTypeId: ctx.subTypeId,
rfaTypeId: ctx.rfaTypeId,
disciplineId: ctx.disciplineId,
sequence: sequence,
resetScope: resetScope,
year: currentYear,
originatorOrganizationId: ctx.originatorOrganizationId,
recipientOrganizationId: ctx.recipientOrganizationId,
});
const { previewNumber: documentNumber } = await this.formatService.format(
{
projectId: ctx.projectId,
correspondenceTypeId: ctx.typeId,
subTypeId: ctx.subTypeId,
rfaTypeId: ctx.rfaTypeId,
disciplineId: ctx.disciplineId,
sequence: sequence,
resetScope: resetScope,
year: currentYear,
originatorOrganizationId: ctx.originatorOrganizationId,
recipientOrganizationId: ctx.recipientOrganizationId,
}
);
// 5. Audit Log
const audit = await this.logAudit({
@@ -197,9 +188,11 @@ export class DocumentNumberingService {
return this.reservationService.cancel(token, userId);
}
async previewNumber(
ctx: GenerateNumberContext
): Promise<{ previewNumber: string; nextSequence: number; isDefault: boolean }> {
async previewNumber(ctx: GenerateNumberContext): Promise<{
previewNumber: string;
nextSequence: number;
isDefault: boolean;
}> {
const currentYear = new Date().getFullYear();
const resetScope = `YEAR_${currentYear}`;
@@ -247,13 +240,15 @@ export class DocumentNumberingService {
// --- Admin / Legacy ---
async getTemplates() {
async getTemplates(): Promise<DocumentNumberFormat[]> {
return this.formatRepo.find({
relations: ['project', 'correspondenceType'],
});
}
async getTemplatesByProject(projectId: number | string) {
async getTemplatesByProject(
projectId: number | string
): Promise<DocumentNumberFormat[]> {
const internalId = await this.uuidResolver.resolveProjectId(projectId);
return this.formatRepo.find({
where: { projectId: internalId },
@@ -263,10 +258,10 @@ export class DocumentNumberingService {
async saveTemplate(
dto: Partial<DocumentNumberFormat> & { projectId?: number | string }
) {
): Promise<DocumentNumberFormat> {
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);
@@ -277,12 +272,16 @@ export class DocumentNumberingService {
const existing = await this.formatRepo.findOne({
where: {
projectId: Number(dto.projectId),
correspondenceTypeId: dto.correspondenceTypeId ? Equal(dto.correspondenceTypeId) : IsNull(),
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.`);
this.logger.log(
`Found existing template ID: ${existing.id} for business key, updating instead of creating.`
);
dto.id = existing.id;
}
}
@@ -290,8 +289,11 @@ export class DocumentNumberingService {
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);
} catch (e: unknown) {
this.logger.error(
`Failed to save numbering template: ${e instanceof Error ? e.message : String(e)}`,
e instanceof Error ? e.stack : undefined
);
throw e;
}
}
@@ -344,7 +346,7 @@ export class DocumentNumberingService {
// Create a void audit anyway if possible?
await this.logAudit({
documentNumber: dto.documentNumber,
counterKey: {}, // Unknown
counterKey: {},
templateUsed: 'VOID_UNKNOWN',
context: { userId: 0, ipAddress: '0.0.0.0' }, // System
isSuccess: true,
@@ -377,19 +379,20 @@ export class DocumentNumberingService {
// But we can reconstruct it.
let context: GenerateNumberContext;
try {
const rawKey = lastAudit.counterKey;
const key =
typeof lastAudit.counterKey === 'string'
? JSON.parse(lastAudit.counterKey)
: lastAudit.counterKey;
typeof rawKey === 'string'
? (JSON.parse(rawKey) as Record<string, unknown>)
: rawKey;
context = {
projectId: key.projectId,
typeId: key.correspondenceTypeId,
subTypeId: key.subTypeId,
rfaTypeId: key.rfaTypeId,
disciplineId: key.disciplineId,
originatorOrganizationId: key.originatorOrganizationId || 0,
recipientOrganizationId: key.recipientOrganizationId || 0,
projectId: Number(key.projectId),
typeId: Number(key.correspondenceTypeId),
subTypeId: Number(key.subTypeId),
rfaTypeId: Number(key.rfaTypeId),
disciplineId: Number(key.disciplineId),
originatorOrganizationId: Number(key.originatorOrganizationId) || 0,
recipientOrganizationId: Number(key.recipientOrganizationId) || 0,
userId: 0, // System replacement
};
@@ -527,9 +530,11 @@ export class DocumentNumberingService {
errorMessage: err.message || 'Unknown Error',
errorType: this.mapErrorType(err),
contextData: {
...(typeof ctx === 'object' && ctx !== null ? ctx : {}),
...(typeof ctx === 'object' && ctx !== null
? (ctx as Record<string, unknown>)
: {}),
operation,
} as Record<string, unknown>,
},
});
await this.errorRepo.save(errEntity);
} catch (e) {
@@ -39,13 +39,21 @@ export class FormatService {
private disciplineRepo: Repository<Discipline>
) {}
async format(options: FormatOptions): Promise<{ previewNumber: string; isDefault: boolean }> {
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);
const previewNumber = this.replaceTokens(template, tokens, options.sequence);
console.log(`[FormatService] Generated: "${previewNumber}" | Template: "${template}" | isDefault: ${isDefault}`);
const previewNumber = this.replaceTokens(
template,
tokens,
options.sequence
);
// console.log(
// `[FormatService] Generated: "${previewNumber}" | Template: "${template}" | isDefault: ${isDefault}`
// );
return { previewNumber, isDefault };
}
@@ -134,7 +142,7 @@ export class FormatService {
}
const seqMatch = result.match(/{SEQ:(\d+)}/);
if (seqMatch) {
const padding = parseInt(seqMatch[1], 10);
const padding = Number(seqMatch[1]);
result = result.replace(
seqMatch[0],
sequence.toString().padStart(padding, '0')
@@ -1,4 +1,4 @@
import { Injectable, Logger } from '@nestjs/common';
import { Injectable, _Logger } from '@nestjs/common';
import { Counter, Gauge, Histogram } from 'prom-client';
import { InjectMetric } from '@willsoto/nestjs-prometheus';
@@ -73,7 +73,7 @@ export class ReservationService {
Date.now() + this.RESERVATION_TTL_MINUTES * 60 * 1000
);
const reservation = await this.reservationRepo.save({
const _reservation = await this.reservationRepo.save({
token,
documentNumber,
status: ReservationStatus.RESERVED,
@@ -1,4 +1,4 @@
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { Injectable, Logger, _NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { DocumentNumberFormat } from '../entities/document-number-format.entity';