feat(validation): add validators

This commit is contained in:
2025-03-17 16:49:49 +00:00
parent e929281861
commit 3fe7446a29
10 changed files with 1175 additions and 5 deletions

View File

@@ -11,6 +11,8 @@ import {
import { FacturXEncoder } from './formats/facturx.encoder.js';
import { DecoderFactory } from './formats/decoder.factory.js';
import { BaseDecoder } from './formats/base.decoder.js';
import { ValidatorFactory } from './formats/validator.factory.js';
import { BaseValidator } from './formats/base.validator.js';
export class XInvoice {
private xmlString: string;
@@ -19,6 +21,10 @@ export class XInvoice {
private encoderInstance = new FacturXEncoder();
private decoderInstance: BaseDecoder;
private validatorInstance: BaseValidator;
// Validation errors from last validation
private validationErrors: interfaces.ValidationError[] = [];
constructor() {
// Decoder will be initialized when we have XML data
@@ -28,7 +34,7 @@ export class XInvoice {
this.pdfUint8Array = Uint8Array.from(pdfBuffer);
}
public async addXmlString(xmlString: string): Promise<void> {
public async addXmlString(xmlString: string, validate: boolean = false): Promise<void> {
// Basic XML validation - just check if it starts with <?xml
if (!xmlString || !xmlString.trim().startsWith('<?xml')) {
throw new Error('Invalid XML: Missing XML declaration');
@@ -39,6 +45,58 @@ export class XInvoice {
// Initialize the decoder with the XML string using the factory
this.decoderInstance = DecoderFactory.createDecoder(xmlString);
// Initialize the validator with the XML string using the factory
this.validatorInstance = ValidatorFactory.createValidator(xmlString);
// Validate the XML if requested
if (validate) {
await this.validate();
}
}
/**
* Validates the XML against the appropriate validation rules
* @param level Validation level (syntax, semantic, business)
* @returns Validation result
*/
public async validate(level: interfaces.ValidationLevel = interfaces.ValidationLevel.SYNTAX): Promise<interfaces.ValidationResult> {
if (!this.xmlString) {
throw new Error('No XML to validate. Use addXmlString() first.');
}
if (!this.validatorInstance) {
// Initialize the validator with the XML string if not already done
this.validatorInstance = ValidatorFactory.createValidator(this.xmlString);
}
// Run validation
const result = this.validatorInstance.validate(level);
// Store validation errors
this.validationErrors = result.errors;
return result;
}
/**
* Checks if the document is valid based on the last validation
* @returns True if the document is valid
*/
public isValid(): boolean {
if (!this.validatorInstance) {
return false;
}
return this.validatorInstance.isValid();
}
/**
* Gets validation errors from the last validation
* @returns Array of validation errors
*/
public getValidationErrors(): interfaces.ValidationError[] {
return this.validationErrors;
}
public async addLetterData(letterData: plugins.tsclass.business.ILetter): Promise<void> {
@@ -183,13 +241,28 @@ export class XInvoice {
if (xmlContent.includes('CrossIndustryInvoice') ||
xmlContent.includes('rsm:') ||
xmlContent.includes('ram:')) {
return 'ZUGFeRD/CII';
// Check for specific profiles
if (xmlContent.includes('factur-x') || xmlContent.includes('Factur-X')) {
return 'Factur-X';
}
if (xmlContent.includes('zugferd') || xmlContent.includes('ZUGFeRD')) {
return 'ZUGFeRD';
}
return 'CII';
}
// Check for UBL
if (xmlContent.includes('<Invoice') ||
xmlContent.includes('ubl:Invoice') ||
xmlContent.includes('oasis:names:specification:ubl')) {
// Check for XRechnung
if (xmlContent.includes('xrechnung') || xmlContent.includes('XRechnung')) {
return 'XRechnung';
}
return 'UBL';
}
@@ -202,6 +275,44 @@ export class XInvoice {
// For unknown formats, return generic
return 'Unknown';
}
/**
* Gets the invoice format as an enum value
* @returns InvoiceFormat enum value
*/
public getFormat(): interfaces.InvoiceFormat {
if (!this.xmlString) {
return interfaces.InvoiceFormat.UNKNOWN;
}
const formatString = this.identifyXmlFormat(this.xmlString);
switch (formatString) {
case 'UBL':
return interfaces.InvoiceFormat.UBL;
case 'XRechnung':
return interfaces.InvoiceFormat.XRECHNUNG;
case 'CII':
return interfaces.InvoiceFormat.CII;
case 'ZUGFeRD':
return interfaces.InvoiceFormat.ZUGFERD;
case 'Factur-X':
return interfaces.InvoiceFormat.FACTURX;
case 'FatturaPA':
return interfaces.InvoiceFormat.FATTURAPA;
default:
return interfaces.InvoiceFormat.UNKNOWN;
}
}
/**
* Checks if the invoice is in a specific format
* @param format Format to check
* @returns True if the invoice is in the specified format
*/
public isFormat(format: interfaces.InvoiceFormat): boolean {
return this.getFormat() === format;
}
public async getParsedXmlData(): Promise<interfaces.IXInvoice> {
if (!this.xmlString && !this.pdfUint8Array) {