Awnderung der HTML von Invoice
This commit is contained in:
parent
7ab67a839b
commit
491bd2db7d
@ -18,18 +18,16 @@ async function createInvoicePdf(req, res) {
|
|||||||
return res.status(404).send("Patient nicht gefunden");
|
return res.status(404).send("Patient nicht gefunden");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2️⃣ Leistungen laden (noch nicht abgerechnet)
|
// 2️⃣ Leistungen laden
|
||||||
const [rows] = await db.promise().query(`
|
const [rows] = await db.promise().query(`
|
||||||
SELECT
|
SELECT
|
||||||
ps.quantity,
|
ps.quantity,
|
||||||
COALESCE(ps.price_override, s.price) AS price,
|
COALESCE(ps.price_override, s.price) AS price,
|
||||||
|
|
||||||
CASE
|
CASE
|
||||||
WHEN UPPER(TRIM(?)) = 'ES'
|
WHEN UPPER(TRIM(?)) = 'ES'
|
||||||
THEN COALESCE(NULLIF(s.name_es, ''), s.name_de)
|
THEN COALESCE(NULLIF(s.name_es, ''), s.name_de)
|
||||||
ELSE s.name_de
|
ELSE s.name_de
|
||||||
END AS name
|
END AS name
|
||||||
|
|
||||||
FROM patient_services ps
|
FROM patient_services ps
|
||||||
JOIN services s ON ps.service_id = s.id
|
JOIN services s ON ps.service_id = s.id
|
||||||
WHERE ps.patient_id = ?
|
WHERE ps.patient_id = ?
|
||||||
@ -49,10 +47,15 @@ async function createInvoicePdf(req, res) {
|
|||||||
|
|
||||||
const total = services.reduce((sum, s) => sum + s.total, 0);
|
const total = services.reduce((sum, s) => sum + s.total, 0);
|
||||||
|
|
||||||
// 3️⃣ HTML aus EJS erzeugen
|
// 3️⃣ HTML rendern (NOCH OHNE invoiceId)
|
||||||
|
const invoice = {
|
||||||
|
number: "—",
|
||||||
|
date: new Date().toLocaleDateString("de-DE")
|
||||||
|
};
|
||||||
|
|
||||||
const html = await ejs.renderFile(
|
const html = await ejs.renderFile(
|
||||||
path.join(__dirname, "../views/invoices/invoice.ejs"),
|
path.join(__dirname, "../views/invoices/invoice.ejs"),
|
||||||
{ patient, services, total }
|
{ patient, services, total, invoice }
|
||||||
);
|
);
|
||||||
|
|
||||||
// 4️⃣ PDF erzeugen
|
// 4️⃣ PDF erzeugen
|
||||||
@ -61,18 +64,17 @@ async function createInvoicePdf(req, res) {
|
|||||||
{ format: "A4" }
|
{ format: "A4" }
|
||||||
);
|
);
|
||||||
|
|
||||||
// 5️⃣ Dateiname + Pfad
|
// 5️⃣ Datei speichern
|
||||||
const date = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
|
const dateStr = new Date().toISOString().split("T")[0];
|
||||||
const fileName = `invoice_${patientId}_${date}.pdf`;
|
const fileName = `invoice_${patientId}_${dateStr}.pdf`;
|
||||||
const outputPath = path.join(__dirname, "..", "documents", fileName);
|
const outputPath = path.join(__dirname, "..", "documents", fileName);
|
||||||
|
|
||||||
// 6️⃣ PDF speichern
|
|
||||||
fs.writeFileSync(outputPath, pdfBuffer);
|
fs.writeFileSync(outputPath, pdfBuffer);
|
||||||
|
|
||||||
// 7️⃣ OPTIONAL: Rechnung in DB speichern (empfohlen)
|
// 6️⃣ Rechnung EINMAL in DB speichern
|
||||||
const [invoiceResult] = await db.promise().query(`
|
const [invoiceResult] = await db.promise().query(`
|
||||||
INSERT INTO invoices
|
INSERT INTO invoices
|
||||||
(patient_id, invoice_date, total_amount, file_path, created_by, status)
|
(patient_id, invoice_date, total_amount, file_path, created_by, status)
|
||||||
VALUES (?, CURDATE(), ?, ?, ?, 'open')
|
VALUES (?, CURDATE(), ?, ?, ?, 'open')
|
||||||
`, [
|
`, [
|
||||||
patientId,
|
patientId,
|
||||||
@ -83,7 +85,7 @@ async function createInvoicePdf(req, res) {
|
|||||||
|
|
||||||
const invoiceId = invoiceResult.insertId;
|
const invoiceId = invoiceResult.insertId;
|
||||||
|
|
||||||
// 8️⃣ Leistungen verknüpfen
|
// 7️⃣ Leistungen verknüpfen
|
||||||
await db.promise().query(`
|
await db.promise().query(`
|
||||||
UPDATE patient_services
|
UPDATE patient_services
|
||||||
SET invoice_id = ?
|
SET invoice_id = ?
|
||||||
@ -91,7 +93,7 @@ async function createInvoicePdf(req, res) {
|
|||||||
AND invoice_id IS NULL
|
AND invoice_id IS NULL
|
||||||
`, [invoiceId, patientId]);
|
`, [invoiceId, patientId]);
|
||||||
|
|
||||||
// 9️⃣ PDF anzeigen
|
// 8️⃣ PDF anzeigen
|
||||||
res.setHeader("Content-Type", "application/pdf");
|
res.setHeader("Content-Type", "application/pdf");
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
"Content-Disposition",
|
"Content-Disposition",
|
||||||
|
|||||||
109
controllers/invoicePdf.controller.js_original
Normal file
109
controllers/invoicePdf.controller.js_original
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
const db = require("../db");
|
||||||
|
const ejs = require("ejs");
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
const pdf = require("html-pdf-node");
|
||||||
|
|
||||||
|
async function createInvoicePdf(req, res) {
|
||||||
|
const patientId = req.params.id;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1️⃣ Patient laden
|
||||||
|
const [[patient]] = await db.promise().query(
|
||||||
|
"SELECT * FROM patients WHERE id = ?",
|
||||||
|
[patientId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!patient) {
|
||||||
|
return res.status(404).send("Patient nicht gefunden");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Leistungen laden (noch nicht abgerechnet)
|
||||||
|
const [rows] = await db.promise().query(`
|
||||||
|
SELECT
|
||||||
|
ps.quantity,
|
||||||
|
COALESCE(ps.price_override, s.price) AS price,
|
||||||
|
|
||||||
|
CASE
|
||||||
|
WHEN UPPER(TRIM(?)) = 'ES'
|
||||||
|
THEN COALESCE(NULLIF(s.name_es, ''), s.name_de)
|
||||||
|
ELSE s.name_de
|
||||||
|
END 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
|
||||||
|
`, [patient.country, patientId]);
|
||||||
|
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return res.send("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);
|
||||||
|
|
||||||
|
// 3️⃣ HTML aus EJS erzeugen
|
||||||
|
const html = await ejs.renderFile(
|
||||||
|
path.join(__dirname, "../views/invoices/invoice.ejs"),
|
||||||
|
{ patient, services, total }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4️⃣ PDF erzeugen
|
||||||
|
const pdfBuffer = await pdf.generatePdf(
|
||||||
|
{ content: html },
|
||||||
|
{ format: "A4" }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5️⃣ Dateiname + Pfad
|
||||||
|
const date = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
|
||||||
|
const fileName = `invoice_${patientId}_${date}.pdf`;
|
||||||
|
const outputPath = path.join(__dirname, "..", "documents", fileName);
|
||||||
|
|
||||||
|
// 6️⃣ PDF speichern
|
||||||
|
fs.writeFileSync(outputPath, pdfBuffer);
|
||||||
|
|
||||||
|
// 7️⃣ OPTIONAL: Rechnung in DB speichern (empfohlen)
|
||||||
|
const [invoiceResult] = await db.promise().query(`
|
||||||
|
INSERT INTO invoices
|
||||||
|
(patient_id, invoice_date, total_amount, file_path, created_by, status)
|
||||||
|
VALUES (?, CURDATE(), ?, ?, ?, 'open')
|
||||||
|
`, [
|
||||||
|
patientId,
|
||||||
|
total,
|
||||||
|
`documents/${fileName}`,
|
||||||
|
req.session.user.id
|
||||||
|
]);
|
||||||
|
|
||||||
|
const invoiceId = invoiceResult.insertId;
|
||||||
|
|
||||||
|
// 8️⃣ Leistungen verknüpfen
|
||||||
|
await db.promise().query(`
|
||||||
|
UPDATE patient_services
|
||||||
|
SET invoice_id = ?
|
||||||
|
WHERE patient_id = ?
|
||||||
|
AND invoice_id IS NULL
|
||||||
|
`, [invoiceId, patientId]);
|
||||||
|
|
||||||
|
// 9️⃣ PDF anzeigen
|
||||||
|
res.setHeader("Content-Type", "application/pdf");
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`inline; filename="${fileName}"`
|
||||||
|
);
|
||||||
|
|
||||||
|
res.send(pdfBuffer);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ PDF ERROR:", err);
|
||||||
|
res.status(500).send("Fehler beim Erstellen der Rechnung");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { createInvoicePdf };
|
||||||
@ -1,16 +1,28 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<style>
|
|
||||||
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3 {
|
.header {
|
||||||
margin-bottom: 10px;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin: 30px 0 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
@ -22,55 +34,146 @@
|
|||||||
th, td {
|
th, td {
|
||||||
border: 1px solid #333;
|
border: 1px solid #333;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
text-align: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-border td {
|
||||||
|
border: none;
|
||||||
|
padding: 4px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.total {
|
.total {
|
||||||
margin-top: 20px;
|
margin-top: 15px;
|
||||||
font-weight: bold;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 30px;
|
||||||
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h2>Rechnung</h2>
|
<!-- HEADER -->
|
||||||
|
<div class="header">
|
||||||
|
|
||||||
<p>
|
<!-- LOGO -->
|
||||||
<strong>Patient:</strong> <%= patient.firstname %> <%= patient.lastname %><br>
|
<div>
|
||||||
<strong>Adresse:</strong><br>
|
<!-- HIER LOGO EINBINDEN -->
|
||||||
<%= patient.street %> <%= patient.house_number %><br>
|
<!-- <img src="file:///ABSOLUTER/PFAD/logo.png" class="logo"> -->
|
||||||
<%= patient.postal_code %> <%= patient.city %>
|
</div>
|
||||||
</p>
|
|
||||||
|
|
||||||
<table>
|
<!-- ADRESSE -->
|
||||||
<thead>
|
<div>
|
||||||
<tr>
|
<strong>MedCenter Tenerife S.L.</strong><br>
|
||||||
<th>Menge</th>
|
C.I.F. B76766302<br><br>
|
||||||
<th>Leistung</th>
|
|
||||||
<th>Preis</th>
|
Praxis El Médano<br>
|
||||||
<th>Summe</th>
|
Calle Teobaldo Power 5<br>
|
||||||
</tr>
|
38612 El Médano<br>
|
||||||
</thead>
|
Fon: 922 157 527 / 657 497 996<br><br>
|
||||||
<tbody>
|
|
||||||
<% services.forEach(s => { %>
|
Praxis Los Cristianos<br>
|
||||||
<tr>
|
Avenida de Suecia 10<br>
|
||||||
<td><%= s.quantity %></td>
|
38650 Los Cristianos<br>
|
||||||
<td><%= s.name %></td>
|
Fon: 922 157 527 / 654 520 717
|
||||||
<td><%= s.price.toFixed(2) %> €</td>
|
</div>
|
||||||
<td><%= s.total.toFixed(2) %> €</td>
|
|
||||||
</tr>
|
</div>
|
||||||
<% }) %>
|
|
||||||
</tbody>
|
<h1>RECHNUNG / FACTURA</h1>
|
||||||
|
|
||||||
|
<!-- RECHNUNGSDATEN -->
|
||||||
|
<table class="no-border">
|
||||||
|
<tr>
|
||||||
|
<td><strong>Factura número</strong></td>
|
||||||
|
<td><%= invoice.number %></td>
|
||||||
|
<td><strong>Fecha</strong></td>
|
||||||
|
<td><%= invoice.date %></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Rechnungsnummer</strong></td>
|
||||||
|
<td><%= invoice.number %></td>
|
||||||
|
<td><strong>Datum</strong></td>
|
||||||
|
<td><%= invoice.date %></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>N.I.E. / DNI</strong></td>
|
||||||
|
<td><%= patient.dni || "" %></td>
|
||||||
|
<td><strong>Geburtsdatum</strong></td>
|
||||||
|
<td><%= patient.birthdate || "" %></td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<h3>Gesamt: <%= total.toFixed(2) %> €</h3>
|
<br>
|
||||||
|
|
||||||
|
<!-- PATIENT -->
|
||||||
|
<strong>Patient:</strong><br>
|
||||||
|
<%= patient.firstname %> <%= patient.lastname %><br>
|
||||||
|
<%= patient.street %> <%= patient.house_number %><br>
|
||||||
|
<%= patient.postal_code %> <%= patient.city %>
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<!-- DIAGNOSE -->
|
||||||
|
<strong>Diagnosis / Diagnose:</strong><br>
|
||||||
|
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<!-- LEISTUNGEN -->
|
||||||
|
<p>Für unsere Leistungen erlauben wir uns Ihnen folgendes in Rechnung zu stellen:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Menge</th>
|
||||||
|
<th>Terapia / Behandlung</th>
|
||||||
|
<th>Preis (€)</th>
|
||||||
|
<th>Summe (€)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<% services.forEach(s => { %>
|
||||||
|
<tr>
|
||||||
|
<td><%= s.quantity %></td>
|
||||||
|
<td><%= s.name %></td>
|
||||||
|
<td><%= s.price.toFixed(2) %></td>
|
||||||
|
<td><%= s.total.toFixed(2) %></td>
|
||||||
|
</tr>
|
||||||
|
<% }) %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="total">
|
||||||
|
T O T A L: <%= total.toFixed(2) %> €
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<!-- ZAHLUNGSART -->
|
||||||
|
<strong>Forma de pago / Zahlungsform:</strong><br>
|
||||||
|
Efectivo □ Tarjeta □<br>
|
||||||
|
Barzahlung EC/Kreditkarte
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<!-- BANK -->
|
||||||
|
<strong>Santander</strong><br>
|
||||||
|
IBAN: ES37 0049 4507 8925 1002 3301<br>
|
||||||
|
BIC: BSCHESMMXXX
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
Privatärztliche Rechnung – gemäß spanischem und deutschem Recht
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
76
views/invoices/invoice.ejs_original
Normal file
76
views/invoices/invoice.ejs_original
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #333;
|
||||||
|
padding: 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h2>Rechnung</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Patient:</strong> <%= patient.firstname %> <%= patient.lastname %><br>
|
||||||
|
<strong>Adresse:</strong><br>
|
||||||
|
<%= patient.street %> <%= patient.house_number %><br>
|
||||||
|
<%= patient.postal_code %> <%= patient.city %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Menge</th>
|
||||||
|
<th>Leistung</th>
|
||||||
|
<th>Preis</th>
|
||||||
|
<th>Summe</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% services.forEach(s => { %>
|
||||||
|
<tr>
|
||||||
|
<td><%= s.quantity %></td>
|
||||||
|
<td><%= s.name %></td>
|
||||||
|
<td><%= s.price.toFixed(2) %> €</td>
|
||||||
|
<td><%= s.total.toFixed(2) %> €</td>
|
||||||
|
</tr>
|
||||||
|
<% }) %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Gesamt: <%= total.toFixed(2) %> €</h3>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user