199 lines
5.2 KiB
JavaScript
199 lines
5.2 KiB
JavaScript
const db = require("../db");
|
|
const ejs = require("ejs");
|
|
const path = require("path");
|
|
const htmlToPdf = require("html-pdf-node");
|
|
const fs = require("fs");
|
|
|
|
async function createInvoicePdf(req, res) {
|
|
const patientId = req.params.id;
|
|
const connection = await db.promise().getConnection();
|
|
|
|
try {
|
|
await connection.beginTransaction();
|
|
|
|
const year = new Date().getFullYear();
|
|
|
|
// 🔒 Rechnungszähler sperren
|
|
const [[counterRow]] = await connection.query(
|
|
"SELECT counter FROM invoice_counters WHERE year = ? FOR UPDATE",
|
|
[year]
|
|
);
|
|
|
|
let counter;
|
|
if (!counterRow) {
|
|
counter = 1;
|
|
await connection.query(
|
|
"INSERT INTO invoice_counters (year, counter) VALUES (?, ?)",
|
|
[year, counter]
|
|
);
|
|
} else {
|
|
counter = counterRow.counter + 1;
|
|
await connection.query(
|
|
"UPDATE invoice_counters SET counter = ? WHERE year = ?",
|
|
[counter, year]
|
|
);
|
|
}
|
|
|
|
const invoiceNumber = `${year}-${String(counter).padStart(4, "0")}`;
|
|
|
|
// 🔹 Patient
|
|
const [[patient]] = await connection.query(
|
|
"SELECT * FROM patients WHERE id = ?",
|
|
[patientId]
|
|
);
|
|
if (!patient) throw new Error("Patient nicht gefunden");
|
|
|
|
// 🔹 Leistungen
|
|
const [rows] = await connection.query(
|
|
`
|
|
SELECT
|
|
ps.quantity,
|
|
COALESCE(ps.price_override, s.price) AS price,
|
|
s.name_de AS name
|
|
FROM patient_services ps
|
|
JOIN services s ON ps.service_id = s.id
|
|
WHERE ps.patient_id = ?
|
|
AND ps.invoice_id IS NULL
|
|
`,
|
|
[patientId]
|
|
);
|
|
|
|
if (!rows.length) throw new Error("Keine Leistungen vorhanden");
|
|
|
|
const services = rows.map((s) => ({
|
|
quantity: Number(s.quantity),
|
|
name: s.name,
|
|
price: Number(s.price),
|
|
total: Number(s.price) * Number(s.quantity),
|
|
}));
|
|
|
|
const total = services.reduce((sum, s) => sum + s.total, 0);
|
|
|
|
// 🔹 Arzt
|
|
const [[doctor]] = await connection.query(
|
|
`
|
|
SELECT first_name, last_name, fachrichtung, arztnummer
|
|
FROM users
|
|
WHERE id = (
|
|
SELECT created_by
|
|
FROM patient_services
|
|
WHERE patient_id = ?
|
|
ORDER BY service_date DESC
|
|
LIMIT 1
|
|
)
|
|
`,
|
|
[patientId]
|
|
);
|
|
|
|
// 🔹 Firma
|
|
const [[company]] = await connection.query(
|
|
"SELECT * FROM company_settings LIMIT 1"
|
|
);
|
|
|
|
// 🖼 Logo als Base64
|
|
let logoBase64 = null;
|
|
if (company && company.invoice_logo_path) {
|
|
const logoPath = path.join(
|
|
__dirname,
|
|
"..",
|
|
"public",
|
|
company.invoice_logo_path
|
|
);
|
|
|
|
if (fs.existsSync(logoPath)) {
|
|
const buffer = fs.readFileSync(logoPath);
|
|
const ext = path.extname(logoPath).toLowerCase();
|
|
const mime =
|
|
ext === ".jpg" || ext === ".jpeg" ? "image/jpeg" : "image/png";
|
|
|
|
logoBase64 = `data:${mime};base64,${buffer.toString("base64")}`;
|
|
}
|
|
}
|
|
|
|
// 📁 PDF-Pfad vorbereiten
|
|
const invoiceDir = path.join(
|
|
__dirname,
|
|
"..",
|
|
"public",
|
|
"invoices",
|
|
String(year)
|
|
);
|
|
|
|
if (!fs.existsSync(invoiceDir)) {
|
|
fs.mkdirSync(invoiceDir, { recursive: true });
|
|
}
|
|
|
|
const fileName = `invoice-${invoiceNumber}.pdf`;
|
|
const absoluteFilePath = path.join(invoiceDir, fileName);
|
|
const dbFilePath = `/invoices/${year}/${fileName}`;
|
|
|
|
// 🔹 Rechnung speichern
|
|
const [result] = await connection.query(
|
|
`
|
|
INSERT INTO invoices
|
|
(patient_id, invoice_date, file_path, total_amount, created_by, status)
|
|
VALUES (?, CURDATE(), ?, ?, ?, 'open')
|
|
`,
|
|
[patientId, dbFilePath, total, req.session.user.id]
|
|
);
|
|
|
|
const invoiceId = result.insertId;
|
|
|
|
const invoice = {
|
|
number: invoiceNumber,
|
|
date: new Date().toLocaleDateString("de-DE"),
|
|
};
|
|
|
|
// 🔹 HTML rendern
|
|
const html = await ejs.renderFile(
|
|
path.join(__dirname, "../views/invoices/invoice.ejs"),
|
|
{
|
|
patient,
|
|
services,
|
|
total,
|
|
invoice,
|
|
doctor,
|
|
company,
|
|
logoBase64,
|
|
}
|
|
);
|
|
|
|
// 🔹 PDF erzeugen
|
|
const pdfBuffer = await htmlToPdf.generatePdf(
|
|
{ content: html },
|
|
{ format: "A4", printBackground: true }
|
|
);
|
|
|
|
// 💾 PDF speichern
|
|
fs.writeFileSync(absoluteFilePath, pdfBuffer);
|
|
|
|
// 🔗 Leistungen mit Rechnung verknüpfen
|
|
const [updateResult] = await connection.query(
|
|
`
|
|
UPDATE patient_services
|
|
SET invoice_id = ?
|
|
WHERE patient_id = ?
|
|
AND invoice_id IS NULL
|
|
`,
|
|
[invoiceId, patientId]
|
|
);
|
|
const [[cid]] = await connection.query("SELECT CONNECTION_ID() AS cid");
|
|
console.log("🔌 INVOICE CID:", cid.cid);
|
|
await connection.commit();
|
|
|
|
console.log("🔌 INVOICE CID:", cid.cid);
|
|
// 📤 PDF anzeigen
|
|
res.render("invoice_preview", {
|
|
pdfUrl: dbFilePath,
|
|
});
|
|
} catch (err) {
|
|
await connection.rollback();
|
|
console.error("❌ INVOICE ERROR:", err);
|
|
res.status(500).send(err.message || "Fehler beim Erstellen der Rechnung");
|
|
} finally {
|
|
connection.release();
|
|
}
|
|
}
|
|
|
|
module.exports = { createInvoicePdf };
|