258 lines
7.0 KiB
TypeScript
258 lines
7.0 KiB
TypeScript
import { BaseValidator } from '../base/base.validator.js';
|
|
import { InvoiceFormat, ValidationLevel } from '../../interfaces/common.js';
|
|
import type { ValidationResult } from '../../interfaces/common.js';
|
|
import { FormatDetector } from '../utils/format.detector.js';
|
|
|
|
// Import specific validators
|
|
import { UBLBaseValidator } from '../ubl/ubl.validator.js';
|
|
import { FacturXValidator } from '../cii/facturx/facturx.validator.js';
|
|
import { ZUGFeRDValidator } from '../cii/zugferd/zugferd.validator.js';
|
|
|
|
/**
|
|
* UBL validator implementation
|
|
* Provides validation for standard UBL documents
|
|
*/
|
|
class UBLValidator extends UBLBaseValidator {
|
|
protected validateStructure(): boolean {
|
|
// Basic validation to check for required UBL invoice elements
|
|
if (!this.doc) return false;
|
|
|
|
let valid = true;
|
|
|
|
// Check for required UBL elements
|
|
const requiredElements = [
|
|
'cbc:ID',
|
|
'cbc:IssueDate',
|
|
'cac:AccountingSupplierParty',
|
|
'cac:AccountingCustomerParty'
|
|
];
|
|
|
|
for (const element of requiredElements) {
|
|
if (!this.exists(`//${element}`)) {
|
|
this.addError(
|
|
'UBL-STRUCT-1',
|
|
`Required element ${element} is missing`,
|
|
`/${element}`
|
|
);
|
|
valid = false;
|
|
}
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
protected validateBusinessRules(): boolean {
|
|
// Basic business rule validation for UBL
|
|
if (!this.doc) return false;
|
|
|
|
let valid = true;
|
|
|
|
// Check that issue date is present and valid
|
|
const issueDateText = this.getText('//cbc:IssueDate');
|
|
if (!issueDateText) {
|
|
this.addError(
|
|
'UBL-BUS-1',
|
|
'Issue date is required',
|
|
'//cbc:IssueDate'
|
|
);
|
|
valid = false;
|
|
} else {
|
|
const issueDate = new Date(issueDateText);
|
|
if (isNaN(issueDate.getTime())) {
|
|
this.addError(
|
|
'UBL-BUS-2',
|
|
'Issue date is not a valid date',
|
|
'//cbc:IssueDate'
|
|
);
|
|
valid = false;
|
|
}
|
|
}
|
|
|
|
// Check that at least one invoice line exists
|
|
if (!this.exists('//cac:InvoiceLine') && !this.exists('//cac:CreditNoteLine')) {
|
|
this.addError(
|
|
'UBL-BUS-3',
|
|
'At least one invoice line or credit note line is required',
|
|
'/'
|
|
);
|
|
valid = false;
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* XRechnung validator implementation
|
|
* Extends UBL validator with additional XRechnung specific validation rules
|
|
*/
|
|
class XRechnungValidator extends UBLValidator {
|
|
protected validateStructure(): boolean {
|
|
// Call the base UBL validation first
|
|
const baseValid = super.validateStructure();
|
|
let valid = baseValid;
|
|
|
|
// Check for XRechnung-specific elements
|
|
if (!this.exists('//cbc:CustomizationID[contains(text(), "xrechnung")]')) {
|
|
this.addError(
|
|
'XRECH-STRUCT-1',
|
|
'XRechnung customization ID is missing or invalid',
|
|
'//cbc:CustomizationID'
|
|
);
|
|
valid = false;
|
|
}
|
|
|
|
// Check for buyer reference which is mandatory in XRechnung
|
|
if (!this.exists('//cbc:BuyerReference')) {
|
|
this.addError(
|
|
'XRECH-STRUCT-2',
|
|
'BuyerReference is required in XRechnung',
|
|
'//'
|
|
);
|
|
valid = false;
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
protected validateBusinessRules(): boolean {
|
|
// Call the base UBL business rule validation
|
|
const baseValid = super.validateBusinessRules();
|
|
let valid = baseValid;
|
|
|
|
// German-specific validation rules
|
|
// Check for proper VAT ID structure for German VAT IDs
|
|
const supplierVatId = this.getText('//cac:AccountingSupplierParty//cbc:CompanyID[../cac:TaxScheme/cbc:ID="VAT"]');
|
|
if (supplierVatId && supplierVatId.startsWith('DE') && !/^DE[0-9]{9}$/.test(supplierVatId)) {
|
|
this.addError(
|
|
'XRECH-BUS-1',
|
|
'German VAT ID format is invalid (must be DE followed by 9 digits)',
|
|
'//cac:AccountingSupplierParty//cbc:CompanyID'
|
|
);
|
|
valid = false;
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* FatturaPA validator implementation
|
|
* Basic implementation for Italian electronic invoices
|
|
*/
|
|
class FatturaPAValidator extends BaseValidator {
|
|
validate(level: ValidationLevel = ValidationLevel.SYNTAX): ValidationResult {
|
|
// Reset errors
|
|
this.errors = [];
|
|
|
|
let valid = true;
|
|
|
|
if (level === ValidationLevel.SYNTAX) {
|
|
valid = this.validateSchema();
|
|
} else if (level === ValidationLevel.SEMANTIC || level === ValidationLevel.BUSINESS) {
|
|
valid = this.validateSchema() && this.validateBusinessRules();
|
|
}
|
|
|
|
return {
|
|
valid,
|
|
errors: this.errors,
|
|
level
|
|
};
|
|
}
|
|
|
|
protected validateSchema(): boolean {
|
|
// Basic schema validation for FatturaPA
|
|
if (!this.xml.includes('<FatturaElettronica')) {
|
|
this.addError('FATT-SCHEMA-1', 'Root element must be FatturaElettronica', '/');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
protected validateBusinessRules(): boolean {
|
|
// Basic placeholder implementation - would need more detailed rules
|
|
// for a real implementation
|
|
return this.validateSchema();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Factory to create the appropriate validator based on the XML format
|
|
*/
|
|
export class ValidatorFactory {
|
|
/**
|
|
* Creates a validator for the specified XML content
|
|
* @param xml XML content to validate
|
|
* @returns Appropriate validator instance
|
|
*/
|
|
public static createValidator(xml: string): BaseValidator {
|
|
try {
|
|
const format = FormatDetector.detectFormat(xml);
|
|
|
|
switch (format) {
|
|
case InvoiceFormat.UBL:
|
|
return new UBLValidator(xml);
|
|
|
|
case InvoiceFormat.XRECHNUNG:
|
|
return new XRechnungValidator(xml);
|
|
|
|
case InvoiceFormat.CII:
|
|
// For now, use Factur-X validator for generic CII
|
|
return new FacturXValidator(xml);
|
|
|
|
case InvoiceFormat.ZUGFERD:
|
|
return new ZUGFeRDValidator(xml);
|
|
|
|
case InvoiceFormat.FACTURX:
|
|
return new FacturXValidator(xml);
|
|
|
|
case InvoiceFormat.FATTURAPA:
|
|
return new FatturaPAValidator(xml);
|
|
|
|
default:
|
|
// For unknown formats, provide a generic validator that will
|
|
// mark the document as invalid but won't throw an exception
|
|
return new GenericValidator(xml, format);
|
|
}
|
|
} catch (error) {
|
|
// If an error occurs during validator creation, return a generic validator
|
|
// that will provide meaningful error information instead of throwing
|
|
console.error(`Error creating validator: ${error}`);
|
|
return new GenericValidator(xml, 'unknown');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generic validator for unknown or unsupported formats
|
|
* Provides meaningful validation errors instead of throwing exceptions
|
|
*/
|
|
class GenericValidator extends BaseValidator {
|
|
private format: string;
|
|
|
|
constructor(xml: string, format: string) {
|
|
super(xml);
|
|
this.format = format;
|
|
this.addError(
|
|
'GEN-1',
|
|
`Unsupported invoice format: ${format}`,
|
|
'/'
|
|
);
|
|
}
|
|
|
|
validate(level: ValidationLevel = ValidationLevel.SYNTAX): ValidationResult {
|
|
return {
|
|
valid: false,
|
|
errors: this.errors,
|
|
level
|
|
};
|
|
}
|
|
|
|
protected validateSchema(): boolean {
|
|
return false;
|
|
}
|
|
|
|
protected validateBusinessRules(): boolean {
|
|
return false;
|
|
}
|
|
} |