import { EN16931UBLValidator } from './en16931.ubl.validator.js'; /** * XRechnung-specific validator that extends EN16931 validation * Implements additional German CIUS (Core Invoice Usage Specification) rules */ export class XRechnungValidator extends EN16931UBLValidator { /** * Validates XRechnung-specific structure requirements */ protected validateStructure(): boolean { // First validate EN16931 structure let valid = super.validateStructure(); // XRechnung-specific: Check for proper customization ID const customizationID = this.getText('//cbc:CustomizationID'); if (!customizationID || !customizationID.includes('xrechnung')) { this.addError( 'XRECH-STRUCT-1', 'XRechnung customization ID is missing or invalid', '//cbc:CustomizationID' ); valid = false; } return valid; } /** * Validates XRechnung-specific business rules */ protected validateBusinessRules(): boolean { // First validate EN16931 business rules let valid = super.validateBusinessRules(); // BR-DE-1: Payment terms (BT-20) or Payment due date (BT-9) shall be provided. if (!this.exists('//cbc:PaymentDueDate') && !this.exists('//cac:PaymentTerms/cbc:Note')) { this.addError( 'BR-DE-1', 'Payment terms or Payment due date shall be provided', '//cac:PaymentTerms' ); valid = false; } // BR-DE-2: The element "Buyer reference" (BT-10) shall be provided. if (!this.exists('//cbc:BuyerReference')) { this.addError( 'BR-DE-2', 'Buyer reference is required in XRechnung', '//cbc:BuyerReference' ); valid = false; } // BR-DE-5: In Germany, the element "Seller VAT identifier" (BT-31) shall be provided. const sellerCountry = this.getText('//cac:AccountingSupplierParty//cac:PostalAddress//cac:Country/cbc:IdentificationCode'); if (sellerCountry === 'DE' && !this.exists('//cac:AccountingSupplierParty//cac:PartyTaxScheme[cac:TaxScheme/cbc:ID="VAT"]/cbc:CompanyID')) { this.addError( 'BR-DE-5', 'Seller VAT identifier is required for German sellers', '//cac:AccountingSupplierParty//cac:PartyTaxScheme' ); valid = false; } // BR-DE-6: In Germany, the element "Buyer VAT identifier" (BT-48) shall be provided. const buyerCountry = this.getText('//cac:AccountingCustomerParty//cac:PostalAddress//cac:Country/cbc:IdentificationCode'); if (buyerCountry === 'DE' && !this.exists('//cac:AccountingCustomerParty//cac:PartyTaxScheme[cac:TaxScheme/cbc:ID="VAT"]/cbc:CompanyID')) { this.addError( 'BR-DE-6', 'Buyer VAT identifier is required for German buyers', '//cac:AccountingCustomerParty//cac:PartyTaxScheme' ); valid = false; } // BR-DE-7: The element "Seller city" (BT-37) shall be provided. if (!this.exists('//cac:AccountingSupplierParty//cac:PostalAddress/cbc:CityName')) { this.addError( 'BR-DE-7', 'Seller city is required', '//cac:AccountingSupplierParty//cac:PostalAddress' ); valid = false; } // BR-DE-8: The element "Seller post code" (BT-38) shall be provided. if (!this.exists('//cac:AccountingSupplierParty//cac:PostalAddress/cbc:PostalZone')) { this.addError( 'BR-DE-8', 'Seller post code is required', '//cac:AccountingSupplierParty//cac:PostalAddress' ); valid = false; } // BR-DE-9: The element "Buyer city" (BT-52) shall be provided. if (!this.exists('//cac:AccountingCustomerParty//cac:PostalAddress/cbc:CityName')) { this.addError( 'BR-DE-9', 'Buyer city is required', '//cac:AccountingCustomerParty//cac:PostalAddress' ); valid = false; } // BR-DE-10: The element "Buyer post code" (BT-53) shall be provided. if (!this.exists('//cac:AccountingCustomerParty//cac:PostalAddress/cbc:PostalZone')) { this.addError( 'BR-DE-10', 'Buyer post code is required', '//cac:AccountingCustomerParty//cac:PostalAddress' ); valid = false; } // BR-DE-11: The element "Seller contact telephone number" (BT-42) shall be provided. if (!this.exists('//cac:AccountingSupplierParty//cac:Contact/cbc:Telephone')) { this.addError( 'BR-DE-11', 'Seller contact telephone number is required', '//cac:AccountingSupplierParty//cac:Contact' ); valid = false; } // BR-DE-12: The element "Seller contact email address" (BT-43) shall be provided. if (!this.exists('//cac:AccountingSupplierParty//cac:Contact/cbc:ElectronicMail')) { this.addError( 'BR-DE-12', 'Seller contact email address is required', '//cac:AccountingSupplierParty//cac:Contact' ); valid = false; } // BR-DE-13: The element "Buyer electronic address" (BT-49) shall be provided. if (!this.exists('//cac:AccountingCustomerParty//cac:Party/cbc:EndpointID')) { this.addError( 'BR-DE-13', 'Buyer electronic address (EndpointID) is required', '//cac:AccountingCustomerParty//cac:Party' ); valid = false; } // BR-DE-14: The element "Payment means type code" (BT-81) shall be provided. if (!this.exists('//cac:PaymentMeans/cbc:PaymentMeansCode')) { this.addError( 'BR-DE-14', 'Payment means type code is required', '//cac:PaymentMeans' ); valid = false; } // BR-DE-15: The element "Invoice line identifier" (BT-126) shall be provided. const invoiceLines = this.select('//cac:InvoiceLine | //cac:CreditNoteLine', this.doc) as Node[]; for (let i = 0; i < invoiceLines.length; i++) { const line = invoiceLines[i]; if (!this.exists('./cbc:ID', line)) { this.addError( 'BR-DE-15', `Invoice line ${i + 1} is missing identifier`, `//cac:InvoiceLine[${i + 1}]` ); valid = false; } } // German VAT ID format validation 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( 'BR-DE-VAT', 'German VAT ID format is invalid (must be DE followed by 9 digits)', '//cac:AccountingSupplierParty//cbc:CompanyID' ); valid = false; } return valid; } }