278 lines
9.6 KiB
TypeScript
278 lines
9.6 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
import { InvoiceFormat, ValidationLevel } from '../../../ts/interfaces/common.js';
|
|
import { CorpusLoader } from '../../helpers/corpus.loader.js';
|
|
import { PerformanceTracker } from '../../helpers/performance.tracker.js';
|
|
import * as path from 'path';
|
|
|
|
/**
|
|
* Test ID: CORP-05
|
|
* Test Description: FatturaPA Corpus Processing
|
|
* Priority: Medium
|
|
*
|
|
* This test validates processing of Italian FatturaPA format files,
|
|
* including structure validation and conversion capabilities.
|
|
*/
|
|
|
|
tap.test('CORP-05: FatturaPA Corpus Processing - should process Italian FatturaPA files', async () => {
|
|
// Load FatturaPA test files
|
|
const fatturapaFiles = await CorpusLoader.loadCategory('FATTURAPA_OFFICIAL');
|
|
|
|
// Handle case where no files are found
|
|
if (fatturapaFiles.length === 0) {
|
|
console.log('⚠ No FatturaPA files found in corpus - skipping test');
|
|
return;
|
|
}
|
|
|
|
console.log(`Testing ${fatturapaFiles.length} FatturaPA files`);
|
|
|
|
const results = {
|
|
total: fatturapaFiles.length,
|
|
successful: 0,
|
|
failed: 0,
|
|
parseErrors: 0,
|
|
validationErrors: 0,
|
|
documentTypes: new Map<string, number>(),
|
|
transmissionFormats: new Map<string, number>(),
|
|
processingTimes: [] as number[]
|
|
};
|
|
|
|
const failures: Array<{
|
|
file: string;
|
|
error: string;
|
|
type: 'parse' | 'validation' | 'format';
|
|
}> = [];
|
|
|
|
// Italian-specific validation patterns
|
|
const italianValidations = {
|
|
vatNumber: /^IT\d{11}$/,
|
|
fiscalCode: /^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$/,
|
|
invoiceNumber: /^\w+\/\d{4}$/, // Common format: PREFIX/YEAR
|
|
codiceDestinatario: /^[A-Z0-9]{6,7}$/,
|
|
pecEmail: /^[a-zA-Z0-9._%+-]+@pec\.[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
|
};
|
|
|
|
for (const file of fatturapaFiles) {
|
|
try {
|
|
const xmlBuffer = await CorpusLoader.loadFile(file.path);
|
|
const xmlString = xmlBuffer.toString('utf-8');
|
|
|
|
// Track performance
|
|
const { result: invoice, metric } = await PerformanceTracker.track(
|
|
'fatturapa-processing',
|
|
async () => {
|
|
const einvoice = new EInvoice();
|
|
|
|
// FatturaPA has specific XML structure
|
|
if (xmlString.includes('FatturaElettronica')) {
|
|
// Process as FatturaPA
|
|
await einvoice.fromXmlString(xmlString);
|
|
einvoice.metadata = {
|
|
...einvoice.metadata,
|
|
format: InvoiceFormat.FATTURAPA
|
|
};
|
|
} else {
|
|
throw new Error('Not a valid FatturaPA file');
|
|
}
|
|
|
|
return einvoice;
|
|
},
|
|
{ file: file.path, size: file.size }
|
|
);
|
|
|
|
results.processingTimes.push(metric.duration);
|
|
|
|
// Extract FatturaPA specific information
|
|
const formatMatch = xmlString.match(/<FormatoTrasmissione>([^<]+)<\/FormatoTrasmissione>/);
|
|
const typeMatch = xmlString.match(/<TipoDocumento>([^<]+)<\/TipoDocumento>/);
|
|
|
|
if (formatMatch) {
|
|
const format = formatMatch[1];
|
|
results.transmissionFormats.set(format, (results.transmissionFormats.get(format) || 0) + 1);
|
|
}
|
|
|
|
if (typeMatch) {
|
|
const docType = typeMatch[1];
|
|
results.documentTypes.set(docType, (results.documentTypes.get(docType) || 0) + 1);
|
|
}
|
|
|
|
// Validate Italian-specific fields
|
|
const vatMatch = xmlString.match(/<IdCodice>(\d{11})<\/IdCodice>/);
|
|
const cfMatch = xmlString.match(/<CodiceFiscale>([A-Z0-9]{16})<\/CodiceFiscale>/);
|
|
const destMatch = xmlString.match(/<CodiceDestinatario>([A-Z0-9]{6,7})<\/CodiceDestinatario>/);
|
|
|
|
let italianFieldsValid = true;
|
|
|
|
if (vatMatch && !italianValidations.vatNumber.test('IT' + vatMatch[1])) {
|
|
italianFieldsValid = false;
|
|
console.log(` - Invalid VAT number format: ${vatMatch[1]}`);
|
|
}
|
|
|
|
if (cfMatch && !italianValidations.fiscalCode.test(cfMatch[1])) {
|
|
italianFieldsValid = false;
|
|
console.log(` - Invalid Codice Fiscale format: ${cfMatch[1]}`);
|
|
}
|
|
|
|
if (destMatch && !italianValidations.codiceDestinatario.test(destMatch[1])) {
|
|
italianFieldsValid = false;
|
|
console.log(` - Invalid Codice Destinatario: ${destMatch[1]}`);
|
|
}
|
|
|
|
// Validate the parsed invoice
|
|
try {
|
|
const validationResult = await invoice.validate(ValidationLevel.BUSINESS);
|
|
|
|
if (validationResult.valid && italianFieldsValid) {
|
|
results.successful++;
|
|
console.log(`✓ ${path.basename(file.path)}: Successfully processed`);
|
|
|
|
// Log key information
|
|
if (formatMatch) {
|
|
console.log(` - Transmission format: ${formatMatch[1]}`);
|
|
}
|
|
if (typeMatch) {
|
|
const docTypeMap: Record<string, string> = {
|
|
'TD01': 'Fattura',
|
|
'TD02': 'Acconto/Anticipo',
|
|
'TD03': 'Acconto/Anticipo su parcella',
|
|
'TD04': 'Nota di Credito',
|
|
'TD05': 'Nota di Debito',
|
|
'TD06': 'Parcella'
|
|
};
|
|
console.log(` - Document type: ${docTypeMap[typeMatch[1]] || typeMatch[1]}`);
|
|
}
|
|
} else {
|
|
results.validationErrors++;
|
|
failures.push({
|
|
file: path.basename(file.path),
|
|
error: validationResult.errors?.[0]?.message || 'Validation failed',
|
|
type: 'validation'
|
|
});
|
|
}
|
|
} catch (validationError: any) {
|
|
results.validationErrors++;
|
|
failures.push({
|
|
file: path.basename(file.path),
|
|
error: validationError.message,
|
|
type: 'validation'
|
|
});
|
|
}
|
|
|
|
} catch (error: any) {
|
|
results.failed++;
|
|
|
|
if (error.message.includes('Not a valid FatturaPA')) {
|
|
failures.push({
|
|
file: path.basename(file.path),
|
|
error: 'Invalid FatturaPA format',
|
|
type: 'format'
|
|
});
|
|
} else {
|
|
results.parseErrors++;
|
|
failures.push({
|
|
file: path.basename(file.path),
|
|
error: error.message,
|
|
type: 'parse'
|
|
});
|
|
}
|
|
|
|
console.log(`✗ ${path.basename(file.path)}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Summary report
|
|
console.log('\n=== FatturaPA Corpus Processing Summary ===');
|
|
console.log(`Total files: ${results.total}`);
|
|
console.log(`Successful: ${results.successful} (${(results.successful/results.total*100).toFixed(1)}%)`);
|
|
console.log(`Failed: ${results.failed}`);
|
|
console.log(` - Parse errors: ${results.parseErrors}`);
|
|
console.log(` - Validation errors: ${results.validationErrors}`);
|
|
|
|
console.log('\nTransmission Formats:');
|
|
results.transmissionFormats.forEach((count, format) => {
|
|
const formatMap: Record<string, string> = {
|
|
'FPA12': 'Pubblica Amministrazione',
|
|
'FPR12': 'Privati',
|
|
'SDI11': 'Sistema di Interscambio v1.1'
|
|
};
|
|
console.log(` - ${format}: ${formatMap[format] || format} (${count} files)`);
|
|
});
|
|
|
|
console.log('\nDocument Types:');
|
|
results.documentTypes.forEach((count, type) => {
|
|
const typeMap: Record<string, string> = {
|
|
'TD01': 'Fattura (Invoice)',
|
|
'TD02': 'Acconto/Anticipo (Advance)',
|
|
'TD03': 'Acconto/Anticipo su parcella',
|
|
'TD04': 'Nota di Credito (Credit Note)',
|
|
'TD05': 'Nota di Debito (Debit Note)',
|
|
'TD06': 'Parcella (Fee Note)'
|
|
};
|
|
console.log(` - ${type}: ${typeMap[type] || type} (${count} files)`);
|
|
});
|
|
|
|
if (failures.length > 0) {
|
|
console.log('\nFailure Details:');
|
|
failures.forEach(f => {
|
|
console.log(` ${f.file} [${f.type}]: ${f.error}`);
|
|
});
|
|
}
|
|
|
|
// Performance metrics
|
|
if (results.processingTimes.length > 0) {
|
|
const avgTime = results.processingTimes.reduce((a, b) => a + b, 0) / results.processingTimes.length;
|
|
const minTime = Math.min(...results.processingTimes);
|
|
const maxTime = Math.max(...results.processingTimes);
|
|
|
|
console.log('\nPerformance Metrics:');
|
|
console.log(` Average processing time: ${avgTime.toFixed(2)}ms`);
|
|
console.log(` Min time: ${minTime.toFixed(2)}ms`);
|
|
console.log(` Max time: ${maxTime.toFixed(2)}ms`);
|
|
}
|
|
|
|
// FatturaPA specific features validation
|
|
if (results.successful > 0 && fatturapaFiles.length > 0) {
|
|
// Test a sample file for specific features
|
|
const sampleFile = fatturapaFiles[0];
|
|
const xmlBuffer = await CorpusLoader.loadFile(sampleFile.path);
|
|
const xmlString = xmlBuffer.toString('utf-8');
|
|
|
|
console.log('\nFatturaPA Structure Analysis:');
|
|
|
|
// Check for mandatory sections
|
|
const mandatorySections = [
|
|
'FatturaElettronicaHeader',
|
|
'CedentePrestatore', // Seller
|
|
'CessionarioCommittente', // Buyer
|
|
'FatturaElettronicaBody',
|
|
'DatiGenerali',
|
|
'DatiBeniServizi'
|
|
];
|
|
|
|
for (const section of mandatorySections) {
|
|
if (xmlString.includes(section)) {
|
|
console.log(`✓ Contains mandatory section: ${section}`);
|
|
}
|
|
}
|
|
|
|
// Check for digital signature block
|
|
if (xmlString.includes('<ds:Signature') || xmlString.includes('<Signature')) {
|
|
console.log('✓ Contains digital signature block');
|
|
}
|
|
}
|
|
|
|
// Check if all failures are due to unimplemented decoder
|
|
const allNotImplemented = failures.every(f => f.error.includes('decoder not yet implemented'));
|
|
|
|
if (allNotImplemented && results.successful === 0) {
|
|
console.log('\n⚠ FatturaPA decoder not yet implemented - test skipped');
|
|
console.log(' This test will validate files once FatturaPA decoder is implemented');
|
|
return; // Skip success criteria
|
|
}
|
|
|
|
// Success criteria: at least 70% should pass (FatturaPA is complex)
|
|
const successRate = results.successful / results.total;
|
|
expect(successRate).toBeGreaterThan(0.7);
|
|
});
|
|
|
|
tap.start(); |