- 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.
185 lines
6.4 KiB
TypeScript
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;
|
|
}
|
|
} |