feat(encoder): Rename encoder class from ZugferdXmlEncoder to FacturXEncoder to better reflect Factur-X compliance. All related imports, exports, and tests have been updated while maintaining backward compatibility.

This commit is contained in:
2025-03-17 15:18:33 +00:00
parent f07f81c585
commit cdf4179613
10 changed files with 687 additions and 36 deletions

View File

@@ -2,13 +2,28 @@ import * as plugins from './plugins.js';
/**
* A class to convert a given ILetter with invoice data
* into a minimal Factur-X / ZUGFeRD / EN16931-style XML.
* 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 ZugferdXmlEncoder {
export class FacturXEncoder {
constructor() {}
/**
* Alias for createFacturXXml to maintain backward compatibility
*/
public createZugferdXml(letterArg: plugins.tsclass.business.ILetter): string {
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.
*/
public createFacturXXml(letterArg: plugins.tsclass.business.ILetter): string {
// 1) Get your "SmartXml" or "xmlbuilder2" instance
const smartxmlInstance = new plugins.smartxml.SmartXml();
@@ -31,29 +46,58 @@ export class ZugferdXmlEncoder {
});
// 3) Exchanged Document Context
doc.ele('rsm:ExchangedDocumentContext')
.ele('ram:TestIndicator')
.ele('udt:Indicator')
.txt(this.isDraft(letterArg) ? 'true' : 'false')
.up()
const docContext = doc.ele('rsm:ExchangedDocumentContext');
// Add test indicator
docContext.ele('ram:TestIndicator')
.ele('udt:Indicator')
.txt(this.isDraft(letterArg) ? 'true' : 'false')
.up()
.up(); // </rsm:ExchangedDocumentContext>
.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();
exchangedDoc
.ele('ram:TypeCode')
// Usually: '380' = commercial invoice, '381' = credit note
.txt(invoice.type === 'creditnote' ? '381' : '380')
.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' or 'YYYY-MM-DD'? Depending on standard
// 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
@@ -78,9 +122,7 @@ export class ZugferdXmlEncoder {
.up(); // </ram:SpecifiedLineTradeAgreement>
lineItemEle.ele('ram:SpecifiedLineTradeDelivery')
.ele('ram:BilledQuantity', {
'@unitCode': this.mapUnitType(item.unitType)
})
.ele('ram:BilledQuantity')
.txt(item.unitQuantity.toString())
.up()
.up(); // </ram:SpecifiedLineTradeDelivery>
@@ -158,7 +200,48 @@ export class ZugferdXmlEncoder {
// 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