260322:1648 Correct Coresspondence / Doing RFA / Correct CI
This commit is contained in:
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user