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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user