@casoon/invoice-generator
Version:
Generate PDF invoices, XRechnung XML, and ZUGFeRD PDF/A-3 from JSON data with TypeScript support
283 lines • 13.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZUGFeRDPDFA3Generator = void 0;
const fs_1 = __importDefault(require("fs"));
class ZUGFeRDPDFA3Generator {
constructor(invoice, options = {}) {
this.invoice = invoice;
this.options = {
profile: 'BASIC',
version: '2.1.1',
conformanceLevel: 'A',
outputFormat: 'PDF/A-3',
...options
};
}
/**
* Generiert eine vollständige ZUGFeRD-konforme PDF/A-3 Datei
* Hinweis: Für echte PDF/A-3 Implementierung wird eine spezielle Library benötigt
*/
async generateZUGFeRDPDFA3(outputPath) {
try {
// Generiere XML-Inhalt
const xmlContent = this.generateZUGFeRDXML();
// Speichere XML separat (für Testzwecke)
const xmlPath = outputPath.replace('.pdf', '.xml');
fs_1.default.writeFileSync(xmlPath, xmlContent, 'utf8');
// Generiere PDF mit eingebettetem XML
await this.generatePDFWithEmbeddedXML(outputPath, xmlContent);
console.log(`✅ ZUGFeRD PDF/A-3 erfolgreich erstellt: ${outputPath}`);
console.log(`📄 XML-Inhalt gespeichert: ${xmlPath}`);
}
catch (error) {
console.error('❌ Fehler beim Erstellen der ZUGFeRD PDF/A-3:', error);
throw error;
}
}
/**
* Generiert das ZUGFeRD-konforme XML
*/
generateZUGFeRDXML() {
const profile = this.getProfileIdentifier();
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice xmlns:rsm="urn:ferd:CrossIndustryDocument:invoice:2p0" xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
<rsm:ExchangedDocumentContext>
<ram:TestIndicator>
<udt:Indicator>false</udt:Indicator>
</ram:TestIndicator>
<ram:BusinessProcessSpecifiedDocumentContextParameter>
<ram:ID>${profile}</ram:ID>
</ram:BusinessProcessSpecifiedDocumentContextParameter>
<ram:GuidelineSpecifiedDocumentContextParameter>
<ram:ID>urn:cen.eu:en16931:2017</ram:ID>
</ram:GuidelineSpecifiedDocumentContextParameter>
</rsm:ExchangedDocumentContext>
<rsm:ExchangedDocument>
<ram:ID>${this.escapeXml(this.invoice.details.invoiceNumber)}</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
<ram:IssueDateTime>
<udt:DateTimeString format="102">${this.formatDate(this.invoice.details.invoiceDate)}</udt:DateTimeString>
</ram:IssueDateTime>
</rsm:ExchangedDocument>
<rsm:SupplyChainTradeTransaction>
<ram:IncludedSupplyChainTradeLineItem>
${this.generateLineItems()}
</ram:IncludedSupplyChainTradeLineItem>
<ram:ApplicableHeaderTradeAgreement>
<ram:SellerTradeParty>
<ram:Name>${this.escapeXml(this.invoice.sender.name)}</ram:Name>
<ram:PostalTradeAddress>
<ram:LineOne>${this.escapeXml(this.invoice.sender.address.street)}</ram:LineOne>
<ram:CityName>${this.escapeXml(this.invoice.sender.address.city)}</ram:CityName>
<ram:PostcodeCode>${this.escapeXml(this.invoice.sender.address.postalCode)}</ram:PostcodeCode>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
<ram:SpecifiedTaxRegistration>
<ram:ID schemeID="FC">${this.escapeXml(this.invoice.sender.vatId || '')}</ram:ID>
</ram:SpecifiedTaxRegistration>
<ram:DefinedTradeContact>
<ram:PersonName>${this.escapeXml(this.invoice.sender.businessOwner || '')}</ram:PersonName>
<ram:TelephoneUniversalCommunication>
<ram:CompleteNumber>${this.escapeXml(this.invoice.sender.contactInfo.phone)}</ram:CompleteNumber>
</ram:TelephoneUniversalCommunication>
<ram:EmailURIUniversalCommunication>
<ram:URIID>${this.escapeXml(this.invoice.sender.contactInfo.email)}</ram:URIID>
</ram:EmailURIUniversalCommunication>
</ram:DefinedTradeContact>
</ram:SellerTradeParty>
<ram:BuyerTradeParty>
<ram:Name>${this.escapeXml(this.invoice.recipient.name)}</ram:Name>
<ram:PostalTradeAddress>
<ram:LineOne>${this.escapeXml(this.invoice.recipient.address.street)}</ram:LineOne>
<ram:CityName>${this.escapeXml(this.invoice.recipient.address.city)}</ram:CityName>
<ram:PostcodeCode>${this.escapeXml(this.invoice.recipient.address.postalCode)}</ram:PostcodeCode>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
</ram:BuyerTradeParty>
</ram:ApplicableHeaderTradeAgreement>
<ram:ApplicableHeaderTradeSettlement>
<ram:InvoiceCurrencyCode>${this.invoice.totals.currency}</ram:InvoiceCurrencyCode>
<ram:SpecifiedTradeSettlementPaymentMeans>
<ram:TypeCode>30</ram:TypeCode>
<ram:Information>${this.escapeXml(this.invoice.paymentInfo.bank)}</ram:Information>
<ram:PayeePartyCreditorFinancialAccount>
<ram:IBANID>${this.escapeXml(this.invoice.paymentInfo.iban)}</ram:IBANID>
</ram:PayeePartyCreditorFinancialAccount>
<ram:PayeeSpecifiedCreditorFinancialInstitution>
<ram:BICID>${this.escapeXml(this.invoice.paymentInfo.bic)}</ram:BICID>
</ram:PayeeSpecifiedCreditorFinancialInstitution>
</ram:SpecifiedTradeSettlementPaymentMeans>
<ram:ApplicableTradeTax>
<ram:CalculatedAmount>${this.invoice.totals.vatAmount.toFixed(2)}</ram:CalculatedAmount>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:BasisAmount>${this.invoice.totals.netAmount.toFixed(2)}</ram:BasisAmount>
<ram:ApplicablePercent>${this.invoice.totals.vatRate.toFixed(2)}</ram:ApplicablePercent>
</ram:ApplicableTradeTax>
<ram:SpecifiedTradePaymentTerms>
<ram:Description>${this.escapeXml(this.invoice.paymentInfo.paymentTerms)}</ram:Description>
</ram:SpecifiedTradePaymentTerms>
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
<ram:LineTotalAmount>${this.invoice.totals.netAmount.toFixed(2)}</ram:LineTotalAmount>
<ram:TaxBasisTotalAmount>${this.invoice.totals.netAmount.toFixed(2)}</ram:TaxBasisTotalAmount>
<ram:TaxTotalAmount>${this.invoice.totals.vatAmount.toFixed(2)}</ram:TaxTotalAmount>
<ram:GrandTotalAmount>${this.invoice.totals.grossAmount.toFixed(2)}</ram:GrandTotalAmount>
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
</ram:ApplicableHeaderTradeSettlement>
</rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>`;
return xml;
}
/**
* Generiert die einzelnen Rechnungspositionen
*/
generateLineItems() {
return this.invoice.items.map((item, index) => `
<ram:IncludedSupplyChainTradeLineItem>
<ram:AssociatedDocumentLineDocument>
<ram:LineID>${index + 1}</ram:LineID>
</ram:AssociatedDocumentLineDocument>
<ram:SpecifiedTradeProduct>
<ram:Name>${this.escapeXml(item.description)}</ram:Name>
</ram:SpecifiedTradeProduct>
<ram:SpecifiedLineTradeAgreement>
<ram:NetPriceProductTradePrice>
<ram:ChargeAmount>${item.unitPrice.toFixed(2)}</ram:ChargeAmount>
</ram:NetPriceProductTradePrice>
</ram:SpecifiedLineTradeAgreement>
<ram:SpecifiedLineTradeDelivery>
<ram:BilledQuantity>${item.quantity}</ram:BilledQuantity>
</ram:SpecifiedLineTradeDelivery>
<ram:SpecifiedLineTradeSettlement>
<ram:ApplicableTradeTax>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:ApplicablePercent>${item.vatRate.toFixed(2)}</ram:ApplicablePercent>
</ram:ApplicableTradeTax>
<ram:SpecifiedTradeSettlementLineMonetarySummation>
<ram:LineTotalAmount>${item.total.toFixed(2)}</ram:LineTotalAmount>
</ram:SpecifiedTradeSettlementLineMonetarySummation>
</ram:SpecifiedLineTradeSettlement>
</ram:IncludedSupplyChainTradeLineItem>`).join('');
}
/**
* Ermittelt den korrekten Profil-Identifier basierend auf ZUGFeRD-Version und Profil
*/
getProfileIdentifier() {
const { profile, version } = this.options;
const profileMap = {
'2.1.1': {
'BASIC': 'urn:ferd:CrossIndustryDocument:invoice:2p0:basic',
'COMFORT': 'urn:ferd:CrossIndustryDocument:invoice:2p0:comfort',
'EXTENDED': 'urn:ferd:CrossIndustryDocument:invoice:2p0:extended'
},
'2.2.0': {
'BASIC': 'urn:ferd:CrossIndustryDocument:invoice:2p0:basic',
'COMFORT': 'urn:ferd:CrossIndustryDocument:invoice:2p0:comfort',
'EXTENDED': 'urn:ferd:CrossIndustryDocument:invoice:2p0:extended'
}
};
return profileMap[version]?.[profile] || profileMap['2.1.1']['BASIC'];
}
/**
* Generiert PDF mit eingebettetem XML
* Hinweis: Echte PDF/A-3 Implementierung erfordert spezielle Libraries
*/
async generatePDFWithEmbeddedXML(outputPath, xmlContent) {
// Für diese Demo-Implementierung erstellen wir eine normale PDF
// In einer echten Implementierung würde hier eine PDF/A-3 Library verwendet werden
const { generateInvoicePDFToFile } = await Promise.resolve().then(() => __importStar(require('../pdf-generator')));
// Erstelle normale PDF (ohne PDF/A-3 Features)
await generateInvoicePDFToFile(this.invoice, outputPath, {
template: 'default',
language: 'de',
includeLogo: false
});
console.log('⚠️ Hinweis: Echte PDF/A-3 Implementierung erfordert spezielle Libraries wie pdf-lib oder ähnliche');
console.log('📄 XML-Inhalt wurde separat gespeichert und kann manuell in PDF/A-3 eingebettet werden');
}
/**
* Validiert die ZUGFeRD-Konformität
*/
validateZUGFeRDConformance() {
const errors = [];
// Grundlegende Validierungen
if (!this.invoice.details.invoiceNumber) {
errors.push('Rechnungsnummer ist erforderlich');
}
if (!this.invoice.sender.vatId) {
errors.push('USt-IdNr. des Absenders ist erforderlich');
}
if (!this.invoice.paymentInfo.iban) {
errors.push('IBAN ist erforderlich');
}
if (this.invoice.items.length === 0) {
errors.push('Mindestens eine Rechnungsposition ist erforderlich');
}
// Profil-spezifische Validierungen
if (this.options.profile === 'COMFORT' || this.options.profile === 'EXTENDED') {
if (!this.invoice.sender.businessOwner) {
errors.push('Geschäftsinhaber ist für COMFORT/EXTENDED Profile erforderlich');
}
}
return {
isValid: errors.length === 0,
errors
};
}
escapeXml(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
formatDate(dateString) {
// Konvertiere DD.MM.YY zu YYYYMMDD
const parts = dateString.split('.');
if (parts.length === 3) {
const day = parts[0].padStart(2, '0');
const month = parts[1].padStart(2, '0');
const year = '20' + parts[2]; // Annahme: 20xx
return `${year}${month}${day}`;
}
return dateString;
}
}
exports.ZUGFeRDPDFA3Generator = ZUGFeRDPDFA3Generator;
//# sourceMappingURL=zugferd-pdfa3-generator.js.map