import * as plugins from '../plugins.js'; import * as xmldom from 'xmldom'; import { BaseDecoder } from './base.decoder.js'; /** * A decoder for Factur-X/ZUGFeRD XML format (based on UN/CEFACT CII). * Converts XML into structured ILetter with invoice data. */ export class FacturXDecoder extends BaseDecoder { private xmlDoc: Document | null = null; constructor(xmlString: string) { super(xmlString); // Parse XML to DOM for easier element extraction try { const parser = new xmldom.DOMParser(); this.xmlDoc = parser.parseFromString(this.xmlString, 'text/xml'); } catch (error) { console.error('Error parsing Factur-X XML:', error); } } /** * Extracts text from the first element matching the tag name */ private getElementText(tagName: string): string { if (!this.xmlDoc) { return ''; } try { // Basic handling for namespaced tags let namespace = ''; let localName = tagName; if (tagName.includes(':')) { const parts = tagName.split(':'); namespace = parts[0]; localName = parts[1]; } // Find all elements with this name const elements = this.xmlDoc.getElementsByTagName(tagName); if (elements.length > 0) { return elements[0].textContent || ''; } // Try with just the local name if we didn't find it with the namespace if (namespace) { const elements = this.xmlDoc.getElementsByTagName(localName); if (elements.length > 0) { return elements[0].textContent || ''; } } return ''; } catch (error) { console.error(`Error extracting element ${tagName}:`, error); return ''; } } /** * Converts Factur-X/ZUGFeRD XML to a structured letter object */ public async getLetterData(): Promise { try { // Extract invoice ID let invoiceId = this.getElementText('ram:ID'); if (!invoiceId) { // Try alternative locations invoiceId = this.getElementText('rsm:ExchangedDocument ram:ID') || 'Unknown'; } // Extract seller name let sellerName = this.getElementText('ram:Name'); if (!sellerName) { sellerName = this.getElementText('ram:SellerTradeParty ram:Name') || 'Unknown Seller'; } // Extract buyer name let buyerName = ''; // Try to find BuyerTradeParty Name specifically if (this.xmlDoc) { const buyerParties = this.xmlDoc.getElementsByTagName('ram:BuyerTradeParty'); if (buyerParties.length > 0) { const nameElements = buyerParties[0].getElementsByTagName('ram:Name'); if (nameElements.length > 0) { buyerName = nameElements[0].textContent || ''; } } } if (!buyerName) { buyerName = 'Unknown Buyer'; } // Create seller const seller: plugins.tsclass.business.TContact = { name: sellerName, type: 'company', description: sellerName, address: { streetName: this.getElementText('ram:LineOne') || 'Unknown', houseNumber: '0', city: this.getElementText('ram:CityName') || 'Unknown', country: this.getElementText('ram:CountryID') || 'Unknown', postalCode: this.getElementText('ram:PostcodeCode') || 'Unknown', }, registrationDetails: { vatId: this.getElementText('ram:ID') || 'Unknown', registrationId: this.getElementText('ram:ID') || 'Unknown', registrationName: sellerName }, foundedDate: { year: 2000, month: 1, day: 1 }, closedDate: { year: 9999, month: 12, day: 31 }, status: 'active' }; // Create buyer const buyer: plugins.tsclass.business.TContact = { name: buyerName, type: 'company', description: buyerName, address: { streetName: 'Unknown', houseNumber: '0', city: 'Unknown', country: 'Unknown', postalCode: 'Unknown', }, registrationDetails: { vatId: 'Unknown', registrationId: 'Unknown', registrationName: buyerName }, foundedDate: { year: 2000, month: 1, day: 1 }, closedDate: { year: 9999, month: 12, day: 31 }, status: 'active' }; // Extract invoice type let invoiceType = 'debitnote'; const typeCode = this.getElementText('ram:TypeCode'); if (typeCode === '381') { invoiceType = 'creditnote'; } // Create invoice data const invoiceData: plugins.tsclass.finance.IInvoice = { id: invoiceId, status: null, type: invoiceType as 'debitnote' | 'creditnote', billedBy: seller, billedTo: buyer, deliveryDate: Date.now(), dueInDays: 30, periodOfPerformance: null, printResult: null, currency: (this.getElementText('ram:InvoiceCurrencyCode') || 'EUR') as plugins.tsclass.finance.TCurrency, notes: [], items: [ { name: 'Item from Factur-X XML', unitQuantity: 1, unitNetPrice: 0, vatPercentage: 0, position: 0, unitType: 'units', } ], reverseCharge: false, }; // Return a letter return { versionInfo: { type: 'draft', version: '1.0.0', }, type: 'invoice', date: Date.now(), subject: `Invoice: ${invoiceId}`, from: seller, to: buyer, content: { invoiceData: invoiceData, textData: null, timesheetData: null, contractData: null, }, needsCoverSheet: false, objectActions: [], pdf: null, incidenceId: null, language: null, legalContact: null, logoUrl: null, pdfAttachments: null, accentColor: null, }; } catch (error) { console.error('Error converting Factur-X XML to letter data:', error); return this.createDefaultLetter(); } } }