205 lines
6.6 KiB
JavaScript
205 lines
6.6 KiB
JavaScript
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);
|
||
}
|
||
});
|
||
};
|