@fin.cx/einvoice
Version:
A TypeScript module for creating, manipulating, and embedding XML data within PDF files specifically tailored for electronic invoice (einvoice) packages.
293 lines • 27.2 kB
JavaScript
import * as plugins from './plugins.js';
/**
* A class to convert a given ILetter with invoice data
* into a Factur-X compliant XML (also compatible with ZUGFeRD and EN16931).
*
* Factur-X is the French implementation of the European e-invoicing standard EN16931,
* which is also implemented in Germany as ZUGFeRD. Both formats are based on
* UN/CEFACT Cross Industry Invoice (CII) XML schemas.
*/
export class FacturXEncoder {
constructor() { }
/**
* Alias for createFacturXXml to maintain backward compatibility
*/
createZugferdXml(letterArg) {
return this.createFacturXXml(letterArg);
}
/**
* Creates a Factur-X compliant XML based on the provided letter data.
* This XML is also compliant with ZUGFeRD and EN16931 standards.
*/
createFacturXXml(letterArg) {
// 1) Get your "SmartXml" or "xmlbuilder2" instance
const smartxmlInstance = new plugins.smartxml.SmartXml();
if (!letterArg?.content?.invoiceData) {
throw new Error('Letter does not contain invoice data.');
}
const invoice = letterArg.content.invoiceData;
const billedBy = invoice.billedBy;
const billedTo = invoice.billedTo;
// 2) Start building the document
const doc = smartxmlInstance
.create({ version: '1.0', encoding: 'UTF-8' })
.ele('rsm:CrossIndustryInvoice', {
'xmlns:rsm': 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100',
'xmlns:udt': 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100',
'xmlns:qdt': 'urn:un:unece:uncefact:data:standard:QualifiedDataType:100',
'xmlns:ram': 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'
});
// 3) Exchanged Document Context
const docContext = doc.ele('rsm:ExchangedDocumentContext');
// Add test indicator
docContext.ele('ram:TestIndicator')
.ele('udt:Indicator')
.txt(this.isDraft(letterArg) ? 'true' : 'false')
.up()
.up();
// Add Factur-X profile information
// EN16931 profile is compliant with both Factur-X and ZUGFeRD
docContext.ele('ram:GuidelineSpecifiedDocumentContextParameter')
.ele('ram:ID')
.txt('urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:en16931')
.up()
.up();
docContext.up(); // </rsm:ExchangedDocumentContext>
// 4) Exchanged Document (Invoice Header Info)
const exchangedDoc = doc.ele('rsm:ExchangedDocument');
// Invoice ID
exchangedDoc.ele('ram:ID').txt(invoice.id).up();
// Document type code
// 380 = commercial invoice, 381 = credit note
const documentTypeCode = invoice.type === 'creditnote' ? '381' : '380';
exchangedDoc.ele('ram:TypeCode').txt(documentTypeCode).up();
// Issue date
exchangedDoc
.ele('ram:IssueDateTime')
.ele('udt:DateTimeString', { format: '102' })
// Format 'YYYYMMDD' as per Factur-X specification
.txt(this.formatDate(letterArg.date))
.up()
.up();
// Document name - Factur-X recommended field
const documentName = invoice.type === 'creditnote' ? 'CREDIT NOTE' : 'INVOICE';
exchangedDoc.ele('ram:Name').txt(documentName).up();
// Optional: Add language indicator (recommended for Factur-X)
// Use document language if specified, default to 'en'
const languageCode = letterArg.language?.toUpperCase() || 'EN';
exchangedDoc
.ele('ram:IncludedNote')
.ele('ram:Content').txt('Invoice created with Factur-X compliant software').up()
.ele('ram:SubjectCode').txt('REG').up() // REG = regulatory information
.up();
exchangedDoc.up(); // </rsm:ExchangedDocument>
// 5) Supply Chain Trade Transaction
const supplyChainEle = doc.ele('rsm:SupplyChainTradeTransaction');
// 5.1) Included Supply Chain Trade Line Items
invoice.items.forEach((item) => {
const lineItemEle = supplyChainEle.ele('ram:IncludedSupplyChainTradeLineItem');
lineItemEle.ele('ram:SpecifiedTradeProduct')
.ele('ram:Name')
.txt(item.name)
.up()
.up(); // </ram:SpecifiedTradeProduct>
lineItemEle.ele('ram:SpecifiedLineTradeAgreement')
.ele('ram:GrossPriceProductTradePrice')
.ele('ram:ChargeAmount')
.txt(item.unitNetPrice.toFixed(2))
.up()
.up()
.up(); // </ram:SpecifiedLineTradeAgreement>
lineItemEle.ele('ram:SpecifiedLineTradeDelivery')
.ele('ram:BilledQuantity')
.txt(item.unitQuantity.toString())
.up()
.up(); // </ram:SpecifiedLineTradeDelivery>
lineItemEle.ele('ram:SpecifiedLineTradeSettlement')
.ele('ram:ApplicableTradeTax')
.ele('ram:RateApplicablePercent')
.txt(item.vatPercentage.toFixed(2))
.up()
.up()
.ele('ram:SpecifiedTradeSettlementLineMonetarySummation')
.ele('ram:LineTotalAmount')
.txt((item.unitQuantity *
item.unitNetPrice *
(1 + item.vatPercentage / 100)).toFixed(2))
.up()
.up()
.up(); // </ram:SpecifiedLineTradeSettlement>
});
// 5.2) Applicable Header Trade Agreement
const headerTradeAgreementEle = supplyChainEle.ele('ram:ApplicableHeaderTradeAgreement');
// Seller
const sellerPartyEle = headerTradeAgreementEle.ele('ram:SellerTradeParty');
sellerPartyEle.ele('ram:Name').txt(billedBy.name).up();
// Example: If it's a company, put company name, etc.
const sellerAddressEle = sellerPartyEle.ele('ram:PostalTradeAddress');
sellerAddressEle.ele('ram:PostcodeCode').txt(billedBy.address.postalCode).up();
sellerAddressEle.ele('ram:LineOne').txt(billedBy.address.streetName).up();
sellerAddressEle.ele('ram:CityName').txt(billedBy.address.city).up();
// Typically you'd include 'ram:CountryID' with ISO2 code, e.g. "DE"
sellerAddressEle.up(); // </ram:PostalTradeAddress>
sellerPartyEle.up(); // </ram:SellerTradeParty>
// Buyer
const buyerPartyEle = headerTradeAgreementEle.ele('ram:BuyerTradeParty');
buyerPartyEle.ele('ram:Name').txt(billedTo.name).up();
const buyerAddressEle = buyerPartyEle.ele('ram:PostalTradeAddress');
buyerAddressEle.ele('ram:PostcodeCode').txt(billedTo.address.postalCode).up();
buyerAddressEle.ele('ram:LineOne').txt(billedTo.address.streetName).up();
buyerAddressEle.ele('ram:CityName').txt(billedTo.address.city).up();
buyerAddressEle.up(); // </ram:PostalTradeAddress>
buyerPartyEle.up(); // </ram:BuyerTradeParty>
headerTradeAgreementEle.up(); // </ram:ApplicableHeaderTradeAgreement>
// 5.3) Applicable Header Trade Delivery
const headerTradeDeliveryEle = supplyChainEle.ele('ram:ApplicableHeaderTradeDelivery');
const actualDeliveryEle = headerTradeDeliveryEle.ele('ram:ActualDeliverySupplyChainEvent');
const occurrenceEle = actualDeliveryEle.ele('ram:OccurrenceDateTime')
.ele('udt:DateTimeString', { format: '102' });
const deliveryDate = invoice.deliveryDate || letterArg.date;
occurrenceEle.txt(this.formatDate(deliveryDate)).up();
actualDeliveryEle.up(); // </ram:ActualDeliverySupplyChainEvent>
headerTradeDeliveryEle.up(); // </ram:ApplicableHeaderTradeDelivery>
// 5.4) Applicable Header Trade Settlement
const headerTradeSettlementEle = supplyChainEle.ele('ram:ApplicableHeaderTradeSettlement');
// Tax currency code, doc currency code, etc.
headerTradeSettlementEle.ele('ram:InvoiceCurrencyCode').txt(invoice.currency).up();
// Example single tax breakdown
const tradeTaxEle = headerTradeSettlementEle.ele('ram:ApplicableTradeTax');
tradeTaxEle.ele('ram:TypeCode').txt('VAT').up();
tradeTaxEle.ele('ram:CalculatedAmount').txt(this.sumAllVat(invoice).toFixed(2)).up();
tradeTaxEle
.ele('ram:RateApplicablePercent')
.txt(this.extractMainVatRate(invoice.items).toFixed(2))
.up();
tradeTaxEle.up(); // </ram:ApplicableTradeTax>
// Payment Terms
const paymentTermsEle = headerTradeSettlementEle.ele('ram:SpecifiedTradePaymentTerms');
// Payment description
paymentTermsEle.ele('ram:Description').txt(`Payment due in ${invoice.dueInDays} days.`).up();
// Due date calculation
const dueDate = new Date(letterArg.date);
dueDate.setDate(dueDate.getDate() + invoice.dueInDays);
// Add due date as per Factur-X spec
paymentTermsEle
.ele('ram:DueDateDateTime')
.ele('udt:DateTimeString', { format: '102' })
.txt(this.formatDate(dueDate.getTime()))
.up()
.up();
// Add payment means if available
if (invoice.billedBy.sepaConnection) {
// Add SEPA information as per Factur-X standard
const paymentMeans = headerTradeSettlementEle.ele('ram:SpecifiedTradeSettlementPaymentMeans');
paymentMeans.ele('ram:TypeCode').txt('58').up(); // 58 = SEPA credit transfer
// Payment reference (for bank statement reconciliation)
paymentMeans.ele('ram:Information').txt(`Reference: ${invoice.id}`).up();
// Payee account (IBAN)
if (invoice.billedBy.sepaConnection.iban) {
const payeeAccount = paymentMeans.ele('ram:PayeePartyCreditorFinancialAccount');
payeeAccount.ele('ram:IBANID').txt(invoice.billedBy.sepaConnection.iban).up();
payeeAccount.up();
}
// Bank BIC
if (invoice.billedBy.sepaConnection.bic) {
const payeeBank = paymentMeans.ele('ram:PayeeSpecifiedCreditorFinancialInstitution');
payeeBank.ele('ram:BICID').txt(invoice.billedBy.sepaConnection.bic).up();
payeeBank.up();
}
paymentMeans.up();
}
paymentTermsEle.up(); // </ram:SpecifiedTradePaymentTerms>
// Monetary Summation
const monetarySummationEle = headerTradeSettlementEle.ele('ram:SpecifiedTradeSettlementHeaderMonetarySummation');
monetarySummationEle
.ele('ram:LineTotalAmount')
.txt(this.calcLineTotalNet(invoice).toFixed(2))
.up();
monetarySummationEle
.ele('ram:TaxTotalAmount')
.txt(this.sumAllVat(invoice).toFixed(2))
.up();
monetarySummationEle
.ele('ram:GrandTotalAmount')
.txt(this.calcGrandTotal(invoice).toFixed(2))
.up();
monetarySummationEle.up(); // </ram:SpecifiedTradeSettlementHeaderMonetarySummation>
headerTradeSettlementEle.up(); // </ram:ApplicableHeaderTradeSettlement>
supplyChainEle.up(); // </rsm:SupplyChainTradeTransaction>
doc.up(); // </rsm:CrossIndustryInvoice>
// 6) Return the final XML string
return doc.end({ prettyPrint: true });
}
/**
* Helper: Determine if the letter is in draft or final.
*/
isDraft(letterArg) {
return letterArg.versionInfo?.type === 'draft';
}
/**
* Helper: Format date to certain patterns (very minimal example).
* e.g. 'yyyyMMdd' => '20231231'
*/
formatDate(timestampMs) {
const date = new Date(timestampMs);
const yyyy = date.getFullYear();
const mm = String(date.getMonth() + 1).padStart(2, '0');
const dd = String(date.getDate()).padStart(2, '0');
return `${yyyy}${mm}${dd}`;
}
/**
* Helper: Map your custom 'unitType' to an ISO code or similar.
*/
mapUnitType(unitType) {
switch (unitType.toLowerCase()) {
case 'hour':
return 'HUR';
case 'piece':
return 'C62';
default:
return 'C62'; // fallback
}
}
/**
* Example: Sum all VAT amounts from items.
*/
sumAllVat(invoice) {
return invoice.items.reduce((acc, item) => {
const net = item.unitNetPrice * item.unitQuantity;
const vat = net * (item.vatPercentage / 100);
return acc + vat;
}, 0);
}
/**
* Example: Extract main (or highest) VAT rate from items as representative.
* In reality, you might list multiple 'ApplicableTradeTax' blocks by group.
*/
extractMainVatRate(items) {
let max = 0;
items.forEach((item) => {
if (item.vatPercentage > max)
max = item.vatPercentage;
});
return max;
}
/**
* Example: Sum net amounts (without VAT).
*/
calcLineTotalNet(invoice) {
return invoice.items.reduce((acc, item) => {
const net = item.unitNetPrice * item.unitQuantity;
return acc + net;
}, 0);
}
/**
* Example: net + VAT = grand total
*/
calcGrandTotal(invoice) {
const net = this.calcLineTotalNet(invoice);
const vat = this.sumAllVat(invoice);
return net + vat;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5lbmNvZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5lbmNvZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLE9BQU8sY0FBYztJQUV6QixnQkFBZSxDQUFDO0lBRWhCOztPQUVHO0lBQ0ksZ0JBQWdCLENBQUMsU0FBMkM7UUFDakUsT0FBTyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDMUMsQ0FBQztJQUVEOzs7T0FHRztJQUNJLGdCQUFnQixDQUFDLFNBQTJDO1FBQ2pFLG1EQUFtRDtRQUNuRCxNQUFNLGdCQUFnQixHQUFHLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUV6RCxJQUFJLENBQUMsU0FBUyxFQUFFLE9BQU8sRUFBRSxXQUFXLEVBQUUsQ0FBQztZQUNyQyxNQUFNLElBQUksS0FBSyxDQUFDLHVDQUF1QyxDQUFDLENBQUM7UUFDM0QsQ0FBQztRQUVELE1BQU0sT0FBTyxHQUFxQyxTQUFTLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQztRQUNoRixNQUFNLFFBQVEsR0FBc0MsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUNyRSxNQUFNLFFBQVEsR0FBc0MsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUVyRSxpQ0FBaUM7UUFDakMsTUFBTSxHQUFHLEdBQUcsZ0JBQWdCO2FBQ3pCLE1BQU0sQ0FBQyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxDQUFDO2FBQzdDLEdBQUcsQ0FBQywwQkFBMEIsRUFBRTtZQUMvQixXQUFXLEVBQUUsOERBQThEO1lBQzNFLFdBQVcsRUFBRSw2REFBNkQ7WUFDMUUsV0FBVyxFQUFFLDJEQUEyRDtZQUN4RSxXQUFXLEVBQUUsb0ZBQW9GO1NBQ2xHLENBQUMsQ0FBQztRQUVM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