feat(core): improve in-memory validation, FatturaPA detection coverage, and published type compatibility

This commit is contained in:
2026-04-16 20:30:56 +00:00
parent 55bee02a2e
commit 3f37f6538c
60 changed files with 5723 additions and 6678 deletions
+6 -4
View File
@@ -185,7 +185,8 @@ export class ConformanceTestHarness {
r.source === 'Schematron'
);
} catch (error) {
console.warn(`Schematron not available for ${sample.format}: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Schematron not available for ${sample.format}: ${errorMessage}`);
}
// Aggregate results
@@ -202,12 +203,13 @@ export class ConformanceTestHarness {
result.passed = result.errors.length === 0 === sample.expectedValid;
} catch (error) {
console.error(`Error testing ${sample.name}: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`Error testing ${sample.name}: ${errorMessage}`);
result.errors.push({
ruleId: 'TEST-ERROR',
source: 'TestHarness',
severity: 'error',
message: `Test execution failed: ${error.message}`
message: `Test execution failed: ${errorMessage}`
});
}
@@ -588,4 +590,4 @@ export async function runConformanceTests(
// Generate HTML report
await harness.generateHTMLReport();
}
}
}
+20 -10
View File
@@ -27,11 +27,11 @@ export class EN16931Validator {
];
/**
* Validates that an invoice object contains all mandatory EN16931 fields
* Collects mandatory EN16931 field errors without throwing.
* @param invoice The invoice object to validate
* @throws Error if mandatory fields are missing
* @returns List of validation error messages
*/
public static validateMandatoryFields(invoice: any): void {
public static collectMandatoryFieldErrors(invoice: any): string[] {
const errors: string[] = [];
// BR-01: Invoice number is mandatory
@@ -49,7 +49,7 @@ export class EN16931Validator {
errors.push('BR-06: Seller name is mandatory');
}
// BR-07: Buyer name is mandatory
// BR-07: Buyer name is mandatory
if (!invoice.to?.name) {
errors.push('BR-07: Buyer name is mandatory');
}
@@ -67,11 +67,10 @@ export class EN16931Validator {
// BR-05: Invoice currency code is mandatory
if (!invoice.currency) {
errors.push('BR-05: Invoice currency code is mandatory');
} else {
// Validate currency format
if (!this.VALID_CURRENCIES.includes(invoice.currency.toUpperCase())) {
errors.push(`Invalid currency code: ${invoice.currency}. Must be a valid ISO 4217 currency code`);
}
} else if (!this.VALID_CURRENCIES.includes(invoice.currency.toUpperCase())) {
errors.push(
`BR-05: Invalid currency code: ${invoice.currency}. Must be a valid ISO 4217 currency code`
);
}
// BR-08: Seller postal address is mandatory
@@ -84,6 +83,17 @@ export class EN16931Validator {
errors.push('BR-10: Buyer postal address (city, postal code, country) is mandatory');
}
return errors;
}
/**
* Validates that an invoice object contains all mandatory EN16931 fields
* @param invoice The invoice object to validate
* @throws Error if mandatory fields are missing
*/
public static validateMandatoryFields(invoice: any): void {
const errors = this.collectMandatoryFieldErrors(invoice);
if (errors.length > 0) {
throw new Error(`EN16931 validation failed:\n${errors.join('\n')}`);
}
@@ -132,4 +142,4 @@ export class EN16931Validator {
throw new Error(`EN16931 XML validation failed:\n${errors.join('\n')}`);
}
}
}
}
@@ -60,7 +60,8 @@ export class MainValidator {
this.schematronEnabled = true;
console.log(`Schematron validation enabled for ${standard} ${format}`);
} catch (error) {
console.warn(`Failed to initialize Schematron: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Failed to initialize Schematron: ${errorMessage}`);
}
}
@@ -121,7 +122,8 @@ export class MainValidator {
);
results.push(...schematronResults);
} catch (error) {
console.warn(`Schematron validation error: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Schematron validation error: ${errorMessage}`);
}
}
@@ -402,4 +404,4 @@ export async function createValidator(
}
// Export for convenience
export type { ValidationReport, ValidationResult, ValidationOptions } from './validation.types.js';
export type { ValidationReport, ValidationResult, ValidationOptions } from './validation.types.js';
+11 -6
View File
@@ -139,7 +139,8 @@ export class SchematronDownloader {
console.log(`Successfully downloaded: ${fileName}`);
return filePath;
} catch (error) {
throw new Error(`Failed to download ${source.name}: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to download ${source.name}: ${errorMessage}`);
}
}
@@ -160,7 +161,8 @@ export class SchematronDownloader {
const path = await this.download(source);
paths.push(path);
} catch (error) {
console.warn(`Failed to download ${source.name}: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Failed to download ${source.name}: ${errorMessage}`);
}
}
@@ -209,7 +211,8 @@ export class SchematronDownloader {
}
}
} catch (error) {
console.warn(`Failed to list cached files: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Failed to list cached files: ${errorMessage}`);
}
return files;
@@ -230,7 +233,8 @@ export class SchematronDownloader {
console.log('Schematron cache cleared');
} catch (error) {
console.warn(`Failed to clear cache: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Failed to clear cache: ${errorMessage}`);
}
}
@@ -296,9 +300,10 @@ export async function downloadISOSkeletons(targetDir: string = 'assets_downloade
console.log(`Downloaded: ${name}`);
} catch (error) {
console.warn(`Failed to download ${name}: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Failed to download ${name}: ${errorMessage}`);
}
}
console.log('ISO Schematron skeleton download complete');
}
}
@@ -146,7 +146,8 @@ export class IntegratedValidator {
});
results.push(...schematronResults);
} catch (error) {
console.warn(`Schematron validation failed: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Schematron validation failed: ${errorMessage}`);
}
}
@@ -224,7 +225,8 @@ export class IntegratedValidator {
try {
await this.loadSchematron('EN16931', format);
} catch (error) {
console.warn(`Could not load Schematron: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Could not load Schematron: ${errorMessage}`);
}
}
@@ -278,8 +280,9 @@ export async function createStandardValidator(
try {
await validator.loadSchematron(standard, format);
} catch (error) {
console.warn(`Schematron not available for ${standard} ${format}: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Schematron not available for ${standard} ${format}: ${errorMessage}`);
}
return validator;
}
}
+12 -11
View File
@@ -1,5 +1,4 @@
import * as plugins from '../../plugins.js';
import * as SaxonJS from 'saxon-js';
import type { ValidationResult } from './validation.types.js';
/**
@@ -30,9 +29,7 @@ export class SchematronValidator {
*/
public async loadSchematron(source: string, isFilePath: boolean = true): Promise<void> {
if (isFilePath) {
// Load from file
const smartfile = await import('@push.rocks/smartfile');
this.schematronRules = await smartfile.SmartFile.fromFilePath(source).then(f => f.contentBuffer.toString());
this.schematronRules = await plugins.fs.readFile(source, 'utf-8');
} else {
// Use provided string
this.schematronRules = source;
@@ -58,14 +55,15 @@ export class SchematronValidator {
const xslt = this.generateXSLTFromSchematron(this.schematronRules);
// Compile the XSLT with Saxon-JS
this.compiledStylesheet = await SaxonJS.compile({
this.compiledStylesheet = await plugins.SaxonJS.compile({
stylesheetText: xslt,
warnings: 'silent'
});
this.isCompiled = true;
} catch (error) {
throw new Error(`Failed to compile Schematron: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to compile Schematron: ${errorMessage}`);
}
}
@@ -87,7 +85,7 @@ export class SchematronValidator {
try {
// Transform the XML with the compiled Schematron XSLT
const transformResult = await SaxonJS.transform({
const transformResult = await plugins.SaxonJS.transform({
stylesheetInternal: this.compiledStylesheet,
sourceText: xmlContent,
destination: 'serialized',
@@ -108,11 +106,12 @@ export class SchematronValidator {
return results;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
results.push({
ruleId: 'SCHEMATRON-ERROR',
source: 'SCHEMATRON',
severity: 'error',
message: `Schematron validation failed: ${error.message}`,
message: `Schematron validation failed: ${errorMessage}`,
btReference: undefined,
bgReference: undefined
});
@@ -323,7 +322,8 @@ export class HybridValidator {
try {
results.push(...validator.validate(xmlContent));
} catch (error) {
console.warn(`TS validator failed: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`TS validator failed: ${errorMessage}`);
}
}
@@ -333,7 +333,8 @@ export class HybridValidator {
const schematronResults = await this.schematronValidator.validate(xmlContent, options);
results.push(...schematronResults);
} catch (error) {
console.warn(`Schematron validation failed: ${error.message}`);
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Schematron validation failed: ${errorMessage}`);
}
}
@@ -345,4 +346,4 @@ export class HybridValidator {
return true;
});
}
}
}