feat(validation): add validators
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user