const PDFDocument = require('pdfkit'); const fs = require('fs'); const path = require('path'); const Database = require('better-sqlite3'); const db = new Database('plusfit.db', { readonly: true }); /** * Footer (stabil, ohne Rekursion) */ function drawFooter(doc, text) { const y = doc.page.height - doc.page.margins.bottom + 10; doc.save(); doc .fontSize(9) .fillColor('gray') .text( text, doc.page.margins.left, y, { width: doc.page.width - doc.page.margins.left * 2, align: 'center' } ); doc.restore(); } module.exports = function createContractPdf(data) { return new Promise((resolve, reject) => { try { /* ========================= Firmendaten laden ========================= */ const company = db.prepare('SELECT * FROM company LIMIT 1').get(); /* ========================= Zielordner sicherstellen ========================= */ const outputDir = path.join(__dirname, '..', 'documents', 'contracts'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } const filePath = path.join( outputDir, `vertrag_${data.vertragsnummer}.pdf` ); /* ========================= PDF initialisieren ========================= */ const doc = new PDFDocument({ size: 'A4', margin: 50 }); const stream = fs.createWriteStream(filePath); doc.pipe(stream); const footerText = `Plusfit · Vertrag ${data.vertragsnummer} · automatisch erstellt`; /* ========================= HEADER MIT LOGO ========================= */ const logoPath = path.join( __dirname, '..', 'public', 'images', 'logo.png' ); if (fs.existsSync(logoPath)) { doc.image(logoPath, { fit: [120, 60], align: 'left', valign: 'top' }); } doc .moveDown(1.5) .fontSize(20) .text('Mitgliedsvertrag – Plusfit', { align: 'center' }) .moveDown(2); doc.fontSize(12); /* ========================= VERTRAGSPARTNER (FIRMA) ========================= */ doc.fontSize(11).text('Vertragspartner:', { underline: true }); doc.moveDown(0.5); if (company) { doc.text(company.name || 'Plusfit'); if (company.inhaber) doc.text(`Inhaber: ${company.inhaber}`); const addressLine = [ company.strasse, company.hausnummer ].filter(Boolean).join(' '); if (addressLine) doc.text(addressLine); doc.text(`${company.plz || ''} ${company.ort || ''}`.trim()); if (company.email) doc.text(`E-Mail: ${company.email}`); if (company.telefon) doc.text(`Telefon: ${company.telefon}`); } else { doc.fillColor('red') .text('⚠️ Firmendaten nicht hinterlegt') .fillColor('black'); } doc.moveDown(2); /* ========================= VERTRAGSDATEN ========================= */ doc.fontSize(12); doc.text(`Vertragsnummer: ${data.vertragsnummer}`); doc.text(`Name: ${data.vorname} ${data.nachname}`); doc.text(`Vertragsart: ${data.vertragName}`); doc.text(`Laufzeit: ${data.laufzeit} Monate`); doc.text(`Mitgliedsbeitrag: ${Number(data.betrag).toFixed(2)} € / Monat`); doc.moveDown(); /* ========================= VERTRAGSERKLÄRUNG ========================= */ doc.text('Vertragserklärung', { underline: true }); doc.moveDown(0.5); doc.text( 'Der Kunde hat diesen Vertrag durch Anklicken des Buttons ' + '„Kostenpflichtig verbindlich abschließen“ ausdrücklich angenommen.\n\n' + 'Der Vertrag kommt gemäß §§ 145 ff. BGB durch elektronische Annahme zustande. ' + 'Eine handschriftliche Unterschrift ist nicht erforderlich.' ); doc.moveDown(); /* ========================= ZAHLUNG ========================= */ doc.text('Zahlungsmodalitäten', { underline: true }); doc.moveDown(0.5); doc.text( 'Die Zahlung erfolgt monatlich im Voraus per SEPA-Lastschrift ' + 'von dem vom Kunden angegebenen Bankkonto.' ); doc.moveDown(); /* ========================= WIDERRUFSBELEHRUNG ========================= */ doc.text('Widerrufsbelehrung', { underline: true }); doc.moveDown(0.5); doc.text( 'Sie haben das Recht, diesen Vertrag binnen 14 Tagen ohne Angabe von Gründen zu widerrufen. ' + 'Die Frist beginnt mit dem Tag des Vertragsabschlusses.' ); doc.moveDown(2); /* ========================= ABSCHLUSSINFO ========================= */ const datum = new Date(data.datum).toLocaleDateString('de-DE'); doc.text(`Vertragsabschluss am: ${datum}`); const ipText = data.ip && data.ip !== '::1' ? data.ip : 'nicht gespeichert (Datenschutz)'; doc.text(`IP-Adresse bei Abschluss: ${ipText}`); doc.moveDown(2); doc.text( 'Dieser Vertrag wurde elektronisch abgeschlossen. ' + 'Gemäß § 126a BGB ist keine handschriftliche Unterschrift erforderlich.', { italic: true } ); /* ========================= FOOTER (nur einmal, stabil) ========================= */ drawFooter(doc, footerText); doc.end(); stream.on('finish', () => resolve(filePath)); stream.on('error', reject); } catch (err) { reject(err); } }); };