@fin.cx/einvoice
Version:
A TypeScript module for creating, manipulating, and embedding XML data within PDF files specifically tailored for electronic invoice (einvoice) packages.
218 lines • 16.5 kB
JavaScript
import { CIIBaseDecoder } from '../cii.decoder.js';
import { business, finance } from '../../../plugins.js';
import { EN16931Validator } from '../../validation/en16931.validator.js';
/**
* Decoder for ZUGFeRD invoice format
*/
export class ZUGFeRDDecoder extends CIIBaseDecoder {
/**
* Decodes a ZUGFeRD credit note
* @returns Promise resolving to a TCreditNote object
*/
async decodeCreditNote() {
// Get common invoice data
const commonData = await this.extractCommonData();
// Create a credit note with the common data
return {
...commonData,
accountingDocType: 'creditnote'
};
}
/**
* Decodes a ZUGFeRD debit note (invoice)
* @returns Promise resolving to a TDebitNote object
*/
async decodeDebitNote() {
// Get common invoice data
const commonData = await this.extractCommonData();
// Create a debit note with the common data
return {
...commonData,
accountingDocType: 'debitnote'
};
}
/**
* Extracts common invoice data from ZUGFeRD XML
* @returns Common invoice data
*/
async extractCommonData() {
// Extract invoice ID
const invoiceId = this.getText('//rsm:ExchangedDocument/ram:ID');
// Extract issue date
const issueDateStr = this.getText('//ram:IssueDateTime/udt:DateTimeString');
const issueDateFormat = this.getText('//ram:IssueDateTime/udt:DateTimeString/@format');
const issueDate = this.parseCIIDate(issueDateStr, issueDateFormat);
// 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 (not used in this implementation but could be useful)
// const totalAmount = this.getNumber('//ram:GrandTotalAmount');
// Extract notes
const allNotes = this.extractNotes();
// Extract subject and notes separately
// If we have notes, the first one might be the subject
let subject = `Invoice ${invoiceId}`;
let notes = [...allNotes];
// If the first note doesn't look like a payment term or other standard note,
// treat it as the subject
if (allNotes.length > 0 && !allNotes[0].toLowerCase().includes('due in') &&
!allNotes[0].toLowerCase().includes('payment')) {
subject = allNotes[0];
notes = allNotes.slice(1); // Remove subject from notes
}
// Check for reverse charge
const reverseCharge = this.exists('//ram:SpecifiedTradeAllowanceCharge/ram:ReasonCode[text()="62"]');
// Create the common invoice data
const invoiceData = {
type: 'accounting-doc',
accountingDocType: 'invoice',
id: invoiceId,
accountingDocId: invoiceId,
date: issueDate,
accountingDocStatus: 'issued',
versionInfo: {
type: 'final',
version: '1.0.0'
},
language: 'en',
incidenceId: invoiceId,
from: seller,
to: buyer,
subject: subject,
items: items,
dueInDays: dueInDays,
reverseCharge: reverseCharge,
currency: currencyCode,
notes: notes,
deliveryDate: issueDate,
objectActions: []
};
// Validate mandatory EN16931 fields unless validation is skipped
if (!this.skipValidation) {
EN16931Validator.validateMandatoryFields(invoiceData);
}
return invoiceData;
}
/**
* Extracts party information from ZUGFeRD XML
* @param partyXPath XPath to the party node
* @returns Party information as TContact
*/
extractParty(partyXPath) {
// Extract name
const name = this.getText(`${partyXPath}/ram:Name`);
// Extract address
const streetName = this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:LineOne`);
const city = this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:CityName`);
const postalCode = this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:PostcodeCode`);
const country = this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:CountryID`);
// Try to extract house number from street if possible
let houseNumber = '';
const streetParts = streetName.match(/^(.*?)\s+(\d+.*)$/);
if (streetParts) {
// If we can split into street name and house number
houseNumber = streetParts[2];
}
// Create address object
const address = {
streetName: streetName,
houseNumber: houseNumber,
city: city,
postalCode: postalCode,
countryCode: country
};
// Extract VAT ID
const vatId = this.getText(`${partyXPath}/ram:SpecifiedTaxRegistration/ram:ID[="VA"]`) || '';
// Extract registration ID
const registrationId = this.getText(`${partyXPath}/ram:SpecifiedTaxRegistration/ram:ID[="FC"]`) || '';
// Create contact object
return {
type: 'company',
name: name,
description: '',
address: address,
status: 'active',
foundedDate: this.createDefaultDate(),
registrationDetails: {
vatId: vatId,
registrationId: registrationId,
registrationName: ''
}
};
}
/**
* Extracts invoice items from ZUGFeRD XML
* @returns Array of invoice items
*/
extractItems() {
const items = [];
// 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 ZUGFeRD XML
* @returns Array of notes
*/
extractNotes() {
const notes = [];
// 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 for empty date fields
* @returns Default date as timestamp
*/
createDefaultDate() {
// Create a date object that will be compatible with TContact
return {
year: 2000,
month: 1,
day: 1
};
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoienVnZmVyZC5kZWNvZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vdHMvZm9ybWF0cy9jaWkvenVnZmVyZC96dWdmZXJkLmRlY29kZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBRW5ELE9BQU8sRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDeEQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sdUNBQXVDLENBQUM7QUFFekU7O0dBRUc7QUFDSCxNQUFNLE9BQU8sY0FBZSxTQUFRLGNBQWM7SUFDaEQ7OztPQUdHO0lBQ08sS0FBSyxDQUFDLGdCQUFnQjtRQUM5QiwwQkFBMEI7UUFDMUIsTUFBTSxVQUFVLEdBQUcsTUFBTSxJQUFJLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztRQUVsRCw0Q0FBNEM7UUFDNUMsT0FBTztZQUNMLEdBQUcsVUFBVTtZQUNiLGlCQUFpQixFQUFFLFlBQXFCO1NBQ2YsQ0FBQztJQUM5QixDQUFDO0lBRUQ7OztPQUdHO0lBQ08sS0FBSyxDQUFDLGVBQWU7UUFDN0IsMEJBQTBCO1FBQzFCLE1BQU0sVUFBVSxHQUFHLE1BQU0sSUFBSSxDQUFDLGlCQUFpQixFQUFFLENBQUM7UUFFbEQsMkNBQTJDO1FBQzNDLE9BQU87WUFDTCxHQUFHLFVBQVU7WUFDYixpQkFBaUIsRUFBRSxXQUFvQjtTQUNmLENBQUM7SUFDN0IsQ0FBQztJQUVEOzs7T0FHRztJQUNLLEtBQUssQ0FBQyxpQkFBaUI7UUFDN0IscUJBQXFCO1FBQ3JCLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsZ0NBQWdDLENBQUMsQ0FBQztRQUVqRSxxQkFBcUI7UUFDckIsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyx3Q0FBd0MsQ0FBQyxDQUFDO1FBQzVFLE1BQU0sZUFBZSxHQUFHLElBQUksQ0FB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