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.
This commit is contained in:
185
ts/formats/ubl/xrechnung.validator.ts
Normal file
185
ts/formats/ubl/xrechnung.validator.ts
Normal file
@ -0,0 +1,185 @@
|
||||
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user