UNPKG

@fin.cx/einvoice

Version:

A TypeScript module for creating, manipulating, and embedding XML data within PDF files specifically tailored for electronic invoice (einvoice) packages.

266 lines 20.3 kB
import { BaseValidator } from './base.validator.js'; import { ValidationLevel } from '../interfaces.js'; import * as xpath from 'xpath'; import { DOMParser } from 'xmldom'; /** * Validator for Factur-X/ZUGFeRD invoice format * Implements validation rules according to EN16931 and Factur-X specification */ export class FacturXValidator extends BaseValidator { // XML namespaces for Factur-X/ZUGFeRD static { this.NS_RSMT = 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'; } static { this.NS_RAM = 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'; } static { this.NS_UDT = 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'; } constructor(xml) { super(xml); // XML document for processing this.xmlDoc = null; // Factur-X profile (BASIC, EN16931, EXTENDED, etc.) this.profile = ''; try { // Parse XML document this.xmlDoc = new DOMParser().parseFromString(xml, 'application/xml'); // Determine Factur-X profile this.detectProfile(); } catch (error) { this.addError('FX-PARSE', `Failed to parse XML: ${error}`, '/'); } } /** * Validates the Factur-X invoice against the specified level * @param level Validation level * @returns Validation result */ validate(level = ValidationLevel.SYNTAX) { // Reset errors this.errors = []; // Check if document was parsed successfully if (!this.xmlDoc) { return { valid: false, errors: this.errors, level: level }; } // Perform validation based on level let valid = true; if (level === ValidationLevel.SYNTAX) { valid = this.validateSchema(); } else if (level === ValidationLevel.SEMANTIC) { valid = this.validateSchema() && this.validateStructure(); } else if (level === ValidationLevel.BUSINESS) { valid = this.validateSchema() && this.validateStructure() && this.validateBusinessRules(); } return { valid, errors: this.errors, level }; } /** * Validates XML against schema * @returns True if schema validation passed */ validateSchema() { // Basic schema validation (simplified for now) if (!this.xmlDoc) return false; // Check for root element const root = this.xmlDoc.documentElement; if (!root || root.nodeName !== 'rsm:CrossIndustryInvoice') { this.addError('FX-SCHEMA-1', 'Root element must be rsm:CrossIndustryInvoice', '/'); return false; } // Check for required namespaces if (!root.lookupNamespaceURI('rsm') || !root.lookupNamespaceURI('ram')) { this.addError('FX-SCHEMA-2', 'Required namespaces rsm and ram must be declared', '/'); return false; } return true; } /** * Validates structure of the XML document * @returns True if structure validation passed */ validateStructure() { if (!this.xmlDoc) return false; let valid = true; // Check for required main sections const sections = [ 'rsm:ExchangedDocumentContext', 'rsm:ExchangedDocument', 'rsm:SupplyChainTradeTransaction' ]; for (const section of sections) { if (!this.exists(section)) { this.addError('FX-STRUCT-1', `Required section ${section} is missing`, '/rsm:CrossIndustryInvoice'); valid = false; } } // Check for SupplyChainTradeTransaction sections if (this.exists('rsm:SupplyChainTradeTransaction')) { const tradeSubsections = [ 'ram:ApplicableHeaderTradeAgreement', 'ram:ApplicableHeaderTradeDelivery', 'ram:ApplicableHeaderTradeSettlement' ]; for (const subsection of tradeSubsections) { if (!this.exists(`rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeTransaction/${subsection}`)) { this.addError('FX-STRUCT-2', `Required subsection ${subsection} is missing`, '/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeTransaction'); valid = false; } } } return valid; } /** * Validates business rules * @returns True if business rule validation passed */ validateBusinessRules() { if (!this.xmlDoc) return false; let valid = true; // BR-16: Amount due for payment (BT-115) = Invoice total amount with VAT (BT-112) - Paid amount (BT-113) valid = this.validateAmounts() && valid; // BR-CO-3: Value added tax point date (BT-7) and Value added tax point date code (BT-8) are mutually exclusive valid = this.validateMutuallyExclusiveFields() && valid; // BR-S-1: An Invoice that contains a line (BG-25) where the Invoiced item VAT category code (BT-151) is "Standard rated" // shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32) // and/or the Seller tax representative VAT identifier (BT-63). valid = this.validateSellerVatIdentifier() && valid; return valid; } /** * Detects Factur-X profile from the XML */ detectProfile() { if (!this.xmlDoc) return; // Look for profile identifier const profileNode = xpath.select1('string(//rsm:ExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID)', this.xmlDoc); if (profileNode) { const profileText = profileNode.toString(); if (profileText.includes('BASIC')) { this.profile = 'BASIC'; } else if (profileText.includes('EN16931')) { this.profile = 'EN16931'; } else if (profileText.includes('EXTENDED')) { this.profile = 'EXTENDED'; } else if (profileText.includes('MINIMUM')) { this.profile = 'MINIMUM'; } } } /** * Validates amount calculations in the invoice * @returns True if amount validation passed */ validateAmounts() { if (!this.xmlDoc) return false; try { // Extract amounts const totalAmount = this.getNumberValue('//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:GrandTotalAmount'); const paidAmount = this.getNumberValue('//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TotalPrepaidAmount') || 0; const dueAmount = this.getNumberValue('//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:DuePayableAmount'); // Calculate expected due amount const expectedDueAmount = totalAmount - paidAmount; // Compare with a small tolerance for rounding errors if (Math.abs(dueAmount - expectedDueAmount) > 0.01) { this.addError('BR-16', `Amount due for payment (${dueAmount}) must equal Invoice total amount with VAT (${totalAmount}) - Paid amount (${paidAmount})`, '//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation'); return false; } return true; } catch (error) { this.addError('FX-AMOUNT', `Error validating amounts: ${error}`, '/'); return false; } } /** * Validates mutually exclusive fields * @returns True if validation passed */ validateMutuallyExclusiveFields() { if (!this.xmlDoc) return false; try { // Check for VAT point date and code (BR-CO-3) const vatPointDate = this.exists('//ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax/ram:TaxPointDate'); const vatPointDateCode = this.exists('//ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax/ram:DueDateTypeCode'); if (vatPointDate && vatPointDateCode) { this.addError('BR-CO-3', 'Value added tax point date and Value added tax point date code are mutually exclusive', '//ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax'); return false; } return true; } catch (error) { this.addError('FX-MUTUAL', `Error validating mutually exclusive fields: ${error}`, '/'); return false; } } /** * Validates seller VAT identifier requirements * @returns True if validation passed */ validateSellerVatIdentifier() { if (!this.xmlDoc) return false; try { // Check if there are any standard rated line items const standardRatedItems = this.exists('//ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:CategoryCode[text()="S"]'); if (standardRatedItems) { // Check for seller VAT identifier const sellerVatId = this.exists('//ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]'); const sellerTaxId = this.exists('//ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]'); const sellerTaxRepId = this.exists('//ram:ApplicableHeaderTradeAgreement/ram:SellerTaxRepresentativeTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]'); if (!sellerVatId && !sellerTaxId && !sellerTaxRepId) { this.addError('BR-S-1', 'An Invoice with standard rated items must contain the Seller VAT Identifier, Tax registration identifier or Tax representative VAT identifier', '//ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty'); return false; } } return true; } catch (error) { this.addError('FX-VAT', `Error validating seller VAT identifier: ${error}`, '/'); return false; } } /** * Helper method to check if a node exists * @param xpathExpression XPath to check * @returns True if node exists */ exists(xpathExpression) { if (!this.xmlDoc) return false; const nodes = xpath.select(xpathExpression, this.xmlDoc); // Handle different return types from xpath.select() if (Array.isArray(nodes)) { return nodes.length > 0; } return nodes ? true : false; } /** * Helper method to get a number value from XPath * @param xpathExpression XPath to get number from * @returns Number value or NaN if not found */ getNumberValue(xpathExpression) { if (!this.xmlDoc) return NaN; const node = xpath.select1(`string(${xpathExpression})`, this.xmlDoc); return node ? parseFloat(node.toString()) : NaN; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdHVyeC52YWxpZGF0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9mb3JtYXRzL2ZhY3R1cngudmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFbkQsT0FBTyxLQUFLLEtBQUssTUFBTSxPQUFPLENBQUM7QUFDL0IsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUVuQzs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sZ0JBQWlCLFNBQVEsYUFBYTtJQUNqRCxzQ0FBc0M7YUFDdkIsWUFBTyxHQUFHLDhEQUE4RCxBQUFqRSxDQUFrRTthQUN6RSxXQUFNLEdBQUcsb0ZBQW9GLEFBQXZGLENBQXdGO2FBQzlGLFdBQU0sR0FBRyw2REFBNkQsQUFBaEUsQ0FBaUU7SUFRdEYsWUFBWSxHQUFXO1FBQ3JCLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQVBiLDhCQUE4QjtRQUN0QixXQUFNLEdBQW9CLElBQUksQ0FBQztRQUV2QyxvREFBb0Q7UUFDNUMsWUFBTyxHQUFXLEVBQUUsQ0FBQztRQUszQixJQUFJLENBQUM7WUFDSCxxQkFBcUI7WUFDckIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLFNBQVMsRUFBRSxDQUFDLGVBQWUsQ0FBQyxHQUFHLEVBQUUsaUJBQWlCLENBQUMsQ0FBQztZQUV0RSw2QkFBNkI7WUFDN0IsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBQ3ZCLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLEVBQUUsd0JBQXdCLEtBQUssRUFBRSxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQ2xFLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNJLFFBQVEsQ0FBQyxRQUF5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