@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.3 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeC5lbmNvZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvZm9ybWF0cy9mYWN0b3J4LmVuY29kZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFFekM7Ozs7Ozs7R0FPRztBQUNILE1BQU0sT0FBTyxjQUFjO0lBRXpCLGdCQUFlLENBQUM7SUFFaEI7O09BRUc7SUFDSSxnQkFBZ0IsQ0FBQyxTQUEyQztRQUNqRSxPQUFPLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksZ0JBQWdCLENBQUMsU0FBMkM7UUFDakUsbURBQW1EO1FBQ25ELE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBRXpELElBQUksQ0FBQyxTQUFTLEVBQUUsT0FBTyxFQUFFLFdBQVcsRUFBRSxDQUFDO1lBQ3JDLE1BQU0sSUFBSSxLQUFLLENBQUMsdUNBQXVDLENBQUMsQ0FBQztRQUMzRCxDQUFDO1FBRUQsTUFBTSxPQUFPLEdBQXFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDO1FBQ2hGLE1BQU0sUUFBUSxHQUFzQyxPQUFPLENBQUMsUUFBUSxDQUFDO1FBQ3JFLE1BQU0sUUFBUSxHQUFzQyxPQUFPLENBQUMsUUFBUSxDQUFDO1FBRXJFLGlDQUFpQztRQUNqQyxNQUFNLEdBQUcsR0FBRyxnQkFBZ0I7YUFDekIsTUFBTSxDQUFDLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLENBQUM7YUFDN0MsR0FBRyxDQUFDLDBCQUEwQixFQUFFO1lBQy9CLFdBQVcsRUFBRSw4REFBOEQ7WUFDM0UsV0FBVyxFQUFFLDZEQUE2RDtZQUMxRSxXQUFXLEVBQUUsMkRBQTJEO1lBQ3hFLFdBQVcsRUFBRSxvRkFBb0Y7U0FDbEcsQ0FB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