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('