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