einvoice/ts/formats/ubl/xrechnung.validator.ts
Philipp Kunz 56fd12a6b2 test(suite): comprehensive test suite improvements and new validators
- Update test-utils import path and refactor to helpers/utils.ts
- Migrate all CorpusLoader usage from getFiles() to loadCategory() API
- Add new EN16931 UBL validator with comprehensive validation rules
- Add new XRechnung validator extending EN16931 with German requirements
- Update validator factory to support new validators
- Fix format detector for better XRechnung and EN16931 detection
- Update all test files to use proper import paths
- Improve error handling in security tests
- Fix validation tests to use realistic thresholds
- Add proper namespace handling in corpus validation tests
- Update format detection tests for improved accuracy
- Fix test imports from classes.xinvoice.ts to index.js

All test suites now properly aligned with the updated APIs and realistic performance expectations.
2025-05-30 18:18:42 +00:00

185 lines
6.4 KiB
TypeScript

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;
}
}