UNPKG

@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
"use strict"; 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, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&apos;'); } 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