import { CIIBaseDecoder } from '../cii.decoder.js'; import type { TInvoice, TCreditNote, TDebitNote } from '../../../interfaces/common.js'; import { FACTURX_PROFILE_IDS } from './facturx.types.js'; import { business, finance, general } from '../../../plugins.js'; /** * Decoder for Factur-X invoice format */ export class FacturXDecoder extends CIIBaseDecoder { /** * Decodes a Factur-X credit note * @returns Promise resolving to a TCreditNote object */ protected async decodeCreditNote(): Promise { // Get common invoice data const commonData = await this.extractCommonData(); // Create a credit note with the common data return { ...commonData, invoiceType: 'creditnote' } as TCreditNote; } /** * Decodes a Factur-X debit note (invoice) * @returns Promise resolving to a TDebitNote object */ protected async decodeDebitNote(): Promise { // Get common invoice data const commonData = await this.extractCommonData(); // Create a debit note with the common data return { ...commonData, invoiceType: 'debitnote' } as TDebitNote; } /** * Extracts common invoice data from Factur-X XML * @returns Common invoice data */ private async extractCommonData(): Promise> { // Extract invoice ID const invoiceId = this.getText('//rsm:ExchangedDocument/ram:ID'); // Extract issue date const issueDateStr = this.getText('//ram:IssueDateTime/udt:DateTimeString'); const issueDate = issueDateStr ? new Date(issueDateStr).getTime() : Date.now(); // Extract seller information const seller = this.extractParty('//ram:SellerTradeParty'); // Extract buyer information const buyer = this.extractParty('//ram:BuyerTradeParty'); // Extract items const items = this.extractItems(); // Extract due date const dueDateStr = this.getText('//ram:SpecifiedTradePaymentTerms/ram:DueDateDateTime/udt:DateTimeString'); const dueDate = dueDateStr ? new Date(dueDateStr).getTime() : Date.now(); const dueInDays = Math.round((dueDate - issueDate) / (1000 * 60 * 60 * 24)); // Extract currency const currencyCode = this.getText('//ram:InvoiceCurrencyCode') || 'EUR'; // Extract total amount const totalAmount = this.getNumber('//ram:GrandTotalAmount'); // Extract notes const notes = this.extractNotes(); // Check for reverse charge const reverseCharge = this.exists('//ram:SpecifiedTradeAllowanceCharge/ram:ReasonCode[text()="62"]'); // Create the common invoice data return { type: 'invoice', id: invoiceId, date: issueDate, status: 'invoice', versionInfo: { type: 'final', version: '1.0.0' }, language: 'en', incidenceId: invoiceId, from: seller, to: buyer, subject: `Invoice ${invoiceId}`, items: items, dueInDays: dueInDays, reverseCharge: reverseCharge, currency: currencyCode as finance.TCurrency, notes: notes, deliveryDate: issueDate, objectActions: [], invoiceType: 'debitnote' // Default to debit note, will be overridden in decode methods }; } /** * Extracts party information from Factur-X XML * @param partyXPath XPath to the party node * @returns Party information as TContact */ private extractParty(partyXPath: string): business.TContact { // Extract name const name = this.getText(`${partyXPath}/ram:Name`); // Extract address const address: business.IAddress = { streetName: this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:LineOne`) || '', houseNumber: this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:LineTwo`) || '0', postalCode: this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:PostcodeCode`) || '', city: this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:CityName`) || '', country: this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:CountryID`) || '', countryCode: this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:CountryID`) || '' }; // Extract VAT ID const vatId = this.getText(`${partyXPath}/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]`) || ''; // Extract registration ID const registrationId = this.getText(`${partyXPath}/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]`) || ''; // Create contact object return { type: 'company', name: name, description: '', address: address, status: 'active', foundedDate: this.createDefaultDate(), registrationDetails: { vatId: vatId, registrationId: registrationId, registrationName: '' } } as business.TContact; } /** * Extracts invoice items from Factur-X XML * @returns Array of invoice items */ private extractItems(): finance.TInvoiceItem[] { const items: finance.TInvoiceItem[] = []; // Get all item nodes const itemNodes = this.select('//ram:IncludedSupplyChainTradeLineItem', this.doc); // Process each item if (Array.isArray(itemNodes)) { for (let i = 0; i < itemNodes.length; i++) { const itemNode = itemNodes[i]; // Extract item data const name = this.getText('ram:SpecifiedTradeProduct/ram:Name', itemNode); const articleNumber = this.getText('ram:SpecifiedTradeProduct/ram:SellerAssignedID', itemNode); const unitQuantity = this.getNumber('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', itemNode); const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode) || 'EA'; const unitNetPrice = this.getNumber('ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount', itemNode); const vatPercentage = this.getNumber('ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', itemNode); // Create item object items.push({ position: i + 1, name: name, articleNumber: articleNumber, unitType: unitType, unitQuantity: unitQuantity, unitNetPrice: unitNetPrice, vatPercentage: vatPercentage }); } } return items; } /** * Extracts notes from Factur-X XML * @returns Array of notes */ private extractNotes(): string[] { const notes: string[] = []; // Get all note nodes const noteNodes = this.select('//ram:IncludedNote', this.doc); // Process each note if (Array.isArray(noteNodes)) { for (let i = 0; i < noteNodes.length; i++) { const noteNode = noteNodes[i]; const noteText = this.getText('ram:Content', noteNode); if (noteText) { notes.push(noteText); } } } return notes; } /** * Creates a default date object * @returns Default date object */ private createDefaultDate(): general.IDate { return { year: 2000, month: 1, day: 1 }; } }