251215:1719 Docunment Number Rule not correct
Some checks failed
Spec Validation / validate-markdown (push) Has been cancelled
Spec Validation / validate-diagrams (push) Has been cancelled
Spec Validation / check-todos (push) Has been cancelled

This commit is contained in:
admin
2025-12-15 17:19:40 +07:00
parent ec35521258
commit 78370fb590
23 changed files with 1461 additions and 1609 deletions

View File

@@ -1,15 +1,34 @@
import apiClient from '@/lib/api/client';
// Types
export interface NumberingTemplate {
templateId: number;
projectId?: number;
documentTypeId?: string;
documentTypeName: string;
disciplineCode?: string;
templateFormat: string;
exampleNumber: string;
currentNumber: number;
resetAnnually: boolean;
id?: number; // Backend uses 'id'
templateId?: number; // Legacy, optional
projectId: number;
correspondenceTypeId: number;
correspondenceType?: { typeCode: string; typeName: string }; // Relation
documentTypeName?: string; // Optional (joined)
disciplineId: number;
discipline?: { disciplineCode: string; disciplineName: string }; // Relation
disciplineCode?: string; // Optional (joined)
formatTemplate: string; // Backend uses 'formatTemplate'
templateFormat?: string; // Legacy alias
exampleNumber?: string;
paddingLength: number;
resetAnnually: boolean;
isActive: boolean;
createdAt?: string;
updatedAt?: string;
}
export interface NumberingTemplateDto {
projectId: number;
correspondenceTypeId: number;
disciplineId?: number; // 0 = All
formatTemplate: string;
exampleNumber?: string;
paddingLength: number;
resetAnnually: boolean;
isActive: boolean;
}
@@ -23,138 +42,82 @@ export interface NumberSequence {
updatedAt: string;
}
// Mock Data
const mockTemplates: NumberingTemplate[] = [
{
templateId: 1,
projectId: 1,
documentTypeName: 'Correspondence',
disciplineCode: '',
templateFormat: '{ORIGINATOR}-{RECIPIENT}-{SEQ:4}-{YEAR:B.E.}',
exampleNumber: 'PAT-CN-0001-2568',
currentNumber: 142,
resetAnnually: true,
paddingLength: 4,
isActive: true,
},
{
templateId: 2,
projectId: 1,
documentTypeName: 'RFA',
disciplineCode: 'STR',
templateFormat: '{PROJECT}-{CORR_TYPE}-{DISCIPLINE}-{RFA_TYPE}-{SEQ:4}-{REV}',
exampleNumber: 'LCBP3-RFA-STR-SDW-0056-A',
currentNumber: 56,
resetAnnually: true,
paddingLength: 4,
isActive: true,
},
{
templateId: 3,
projectId: 2,
documentTypeName: 'Maintenance Request',
disciplineCode: '',
templateFormat: 'MAINT-{SEQ:4}',
exampleNumber: 'MAINT-0001',
currentNumber: 1,
resetAnnually: true,
paddingLength: 4,
isActive: true,
},
];
const mockSequences: NumberSequence[] = [
{
sequenceId: 1,
year: 2025,
organizationCode: 'PAT',
currentNumber: 142,
lastGeneratedNumber: 'PAT-CORR-2025-0142',
updatedAt: new Date().toISOString(),
},
{
sequenceId: 2,
year: 2025,
disciplineCode: 'STR',
currentNumber: 56,
lastGeneratedNumber: 'RFA-STR-2025-0056',
updatedAt: new Date().toISOString(),
},
];
export const numberingApi = {
getTemplates: async (): Promise<NumberingTemplate[]> => {
return new Promise((resolve) => {
setTimeout(() => resolve([...mockTemplates]), 500);
});
const res = await apiClient.get<NumberingTemplate[]>('/admin/document-numbering/templates');
return res.data.map(t => ({
...t,
templateId: t.id,
templateFormat: t.formatTemplate,
// Map joined data if available, else placeholders
documentTypeName: t.correspondenceType?.typeCode || 'UNKNOWN',
disciplineCode: t.discipline?.disciplineCode || 'ALL',
}));
},
getTemplate: async (id: number): Promise<NumberingTemplate | undefined> => {
return new Promise((resolve) => {
setTimeout(() => resolve(mockTemplates.find(t => t.templateId === id)), 300);
});
// Currently no single get endpoint
const templates = await numberingApi.getTemplates();
return templates.find(t => t.id === id);
},
saveTemplate: async (template: Partial<NumberingTemplate>): Promise<NumberingTemplate> => {
return new Promise((resolve) => {
setTimeout(() => {
if (template.templateId) {
// Update
const index = mockTemplates.findIndex(t => t.templateId === template.templateId);
if (index !== -1) {
mockTemplates[index] = { ...mockTemplates[index], ...template } as NumberingTemplate;
resolve(mockTemplates[index]);
}
} else {
// Create
const newTemplate: NumberingTemplate = {
templateId: Math.floor(Math.random() * 1000),
documentTypeName: 'New Type',
isActive: true,
currentNumber: 0,
exampleNumber: 'PREVIEW',
templateFormat: template.templateFormat || '',
disciplineCode: template.disciplineCode,
paddingLength: template.paddingLength ?? 4,
resetAnnually: template.resetAnnually ?? true,
...template
} as NumberingTemplate;
mockTemplates.push(newTemplate);
resolve(newTemplate);
}
}, 500);
});
// Map frontend interface to backend entity DTO
const payload = {
id: template.id || template.templateId, // Update if ID exists
projectId: template.projectId,
correspondenceTypeId: template.correspondenceTypeId,
disciplineId: template.disciplineId || 0,
formatTemplate: template.templateFormat || template.formatTemplate,
exampleNumber: template.exampleNumber,
paddingLength: template.paddingLength,
resetAnnually: template.resetAnnually,
isActive: template.isActive ?? true
};
const res = await apiClient.post<NumberingTemplate>('/admin/document-numbering/templates', payload);
return res.data;
},
getSequences: async (): Promise<NumberSequence[]> => {
// TODO: Implement backend endpoint for sequences list
return new Promise((resolve) => {
setTimeout(() => resolve([...mockSequences]), 500);
setTimeout(() => resolve([]), 500);
});
},
generateTestNumber: async (templateId: number, context: { organizationId: string, disciplineId: string }): Promise<{ number: string }> => {
return new Promise((resolve) => {
setTimeout(() => {
const template = mockTemplates.find(t => t.templateId === templateId);
if (!template) return resolve({ number: 'ERROR' });
// Use preview endpoint
// We need to know projectId, typeId etc from template.
// But preview endpoint needs context.
// For now, let's just return a mock or call preview endpoint if we have enough info.
let format = template.templateFormat;
// Mock replacement
format = format.replace('{PROJECT}', 'LCBP3');
format = format.replace('{ORIGINATOR}', context.organizationId === '1' ? 'PAT' : 'CN');
format = format.replace('{RECIPIENT}', context.organizationId === '1' ? 'CN' : 'PAT');
format = format.replace('{CORR_TYPE}', template.documentTypeName === 'Correspondence' ? 'CORR' : 'RFA');
format = format.replace('{DISCIPLINE}', context.disciplineId === '1' ? 'STR' : (context.disciplineId === '2' ? 'ARC' : 'GEN'));
format = format.replace('{RFA_TYPE}', 'SDW'); // Mock
// eslint-disable-next-line no-console
console.log('Generating test number for:', templateId, context);
return new Promise((resolve) => resolve({ number: 'TEST-1234' }));
},
const year = new Date().getFullYear();
format = format.replace('{YEAR:A.D.}', year.toString());
format = format.replace('{YEAR:B.E.}', (year + 543).toString());
format = format.replace('{SEQ:4}', '0001');
format = format.replace('{REV}', 'A');
// --- Admin Tools ---
resolve({ number: format });
}, 800);
});
}
getMetrics: async (): Promise<{ audit: any[], errors: any[] }> => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const res = await apiClient.get<{ audit: any[], errors: any[] }>('/admin/document-numbering/metrics');
return res.data;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
manualOverride: async (data: any): Promise<void> => {
await apiClient.post('/admin/document-numbering/manual-override', data);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
voidAndReplace: async (data: any): Promise<string> => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const res = await apiClient.post<any>('/admin/document-numbering/void-and-replace', data);
return res.data;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
cancelNumber: async (data: any): Promise<void> => {
await apiClient.post('/admin/document-numbering/cancel', data);
},
};