diff --git a/controllers/admin.controller.js b/controllers/admin.controller.js index 1801c20..ad2d5f8 100644 --- a/controllers/admin.controller.js +++ b/controllers/admin.controller.js @@ -195,6 +195,76 @@ function deactivateUser(req, res) { }); } +async function showInvoiceOverview(req, res) { + const search = req.query.q || ""; + const view = req.query.view || "year"; + const currentYear = new Date().getFullYear(); + const fromYear = req.query.fromYear || currentYear; + const toYear = req.query.toYear || currentYear; + + try { + const [yearly] = await db.promise().query(` + SELECT + YEAR(invoice_date) AS year, + SUM(total_amount) AS total + FROM invoices + WHERE status IN ('paid','open') + GROUP BY YEAR(invoice_date) + ORDER BY year DESC + `); + + const [quarterly] = await db.promise().query(` + SELECT + YEAR(invoice_date) AS year, + QUARTER(invoice_date) AS quarter, + SUM(total_amount) AS total + FROM invoices + WHERE status IN ('paid','open') + GROUP BY YEAR(invoice_date), QUARTER(invoice_date) + ORDER BY year DESC, quarter DESC + `); + + const [monthly] = await db.promise().query(` + SELECT + DATE_FORMAT(invoice_date, '%Y-%m') AS month, + SUM(total_amount) AS total + FROM invoices + WHERE status IN ('paid','open') + GROUP BY month + ORDER BY month DESC + `); + + const [patients] = await db.promise().query( + ` + SELECT + CONCAT(p.firstname, ' ', p.lastname) AS patient, + SUM(i.total_amount) AS total + FROM invoices i + JOIN patients p ON p.id = i.patient_id + WHERE i.status IN ('paid','open') + AND CONCAT(p.firstname, ' ', p.lastname) LIKE ? + GROUP BY p.id + ORDER BY total DESC + `, + [`%${search}%`] + ); + + res.render("admin/admin_invoice_overview", { + user: req.session.user, + yearly, + quarterly, + monthly, + patients, + search, + fromYear, + toYear, + }); + } catch (err) { + console.error(err); + res.status(500).send("Fehler beim Laden der Rechnungsübersicht"); + } +} + module.exports = { listUsers, showCreateUser, @@ -203,4 +273,5 @@ module.exports = { resetUserPassword, activateUser, deactivateUser, + showInvoiceOverview, }; diff --git a/public/invoices/2026/invoice-2026-0039.pdf b/public/invoices/2026/invoice-2026-0039.pdf new file mode 100644 index 0000000..4540647 Binary files /dev/null and b/public/invoices/2026/invoice-2026-0039.pdf differ diff --git a/routes/admin.routes.js b/routes/admin.routes.js index a39dad3..e868f73 100644 --- a/routes/admin.routes.js +++ b/routes/admin.routes.js @@ -9,6 +9,7 @@ const { resetUserPassword, activateUser, deactivateUser, + showInvoiceOverview, } = require("../controllers/admin.controller"); const { requireAdmin } = require("../middleware/auth.middleware"); @@ -21,5 +22,6 @@ router.post("/users/change-role/:id", requireAdmin, changeUserRole); router.post("/users/reset-password/:id", requireAdmin, resetUserPassword); router.post("/users/activate/:id", requireAdmin, activateUser); router.post("/users/deactivate/:id", requireAdmin, deactivateUser); +router.get("/invoices", requireAdmin, showInvoiceOverview); module.exports = router; diff --git a/views/admin/admin_invoice_overview.ejs b/views/admin/admin_invoice_overview.ejs new file mode 100644 index 0000000..80ed2c1 --- /dev/null +++ b/views/admin/admin_invoice_overview.ejs @@ -0,0 +1,233 @@ + + + + + Rechnungsübersicht + + + + + + + + + + + +
+
+
+ +
+ +
+ +
+ +
+ +
+
+ + +
+ +
+
+
Jahresumsatz
+
+ + + + + + + + + <% if (yearly.length === 0) { %> + + + + <% } %> <% yearly.forEach(y => { %> + + + + + <% }) %> + +
Jahr
+ Keine Daten +
<%= y.year %> + <%= Number(y.total).toFixed(2) %> +
+
+
+
+ + +
+
+
Quartalsumsatz
+
+ + + + + + + + + + <% if (quarterly.length === 0) { %> + + + + <% } %> <% quarterly.forEach(q => { %> + + + + + + <% }) %> + +
JahrQ
+ Keine Daten +
<%= q.year %>Q<%= q.quarter %> + <%= Number(q.total).toFixed(2) %> +
+
+
+
+ + +
+
+
Monatsumsatz
+
+ + + + + + + + + <% if (monthly.length === 0) { %> + + + + <% } %> <% monthly.forEach(m => { %> + + + + + <% }) %> + +
Monat
+ Keine Daten +
<%= m.month %> + <%= Number(m.total).toFixed(2) %> +
+
+
+
+ + +
+
+
Umsatz pro Patient
+
+ +
+ + + + + + + + + Reset + +
+ + + + + + + + + + <% if (patients.length === 0) { %> + + + + <% } %> <% patients.forEach(p => { %> + + + + + <% }) %> + +
Patient
+ Keine Daten +
<%= p.patient %> + <%= Number(p.total).toFixed(2) %> +
+
+
+
+
+
+ + diff --git a/views/dashboard.ejs b/views/dashboard.ejs index c1d81d8..d8855ed 100644 --- a/views/dashboard.ejs +++ b/views/dashboard.ejs @@ -1,128 +1,110 @@ - - + + Dashboard - - - - - - - - - - +
+ + <%- include("partials/flash") %> - -
- - - <%- include("partials/flash") %> - - -
+

Willkommen, <%= user.username %>

- - 🪑 Wartezimmer - + + 🪑 Wartezimmer + - <% if (user.role === 'arzt') { %> - - 👥 Userverwaltung - - <% } %> + <% if (user.role === 'arzt') { %> + + 👥 Userverwaltung + + <% } %> - - Patientenübersicht - + Patientenübersicht - - Medikamentenübersicht - + + Medikamentenübersicht + - <% if (user.role === 'arzt') { %> - - 🧾 Leistungen - - <% } %> + <% if (user.role === 'arzt') { %> + 🧾 Leistungen + <% } %> - - 🧾 Offene Leistungen - + + 🧾 Offene Leistungen + - - <% if (user.role === 'arzt') { %> - - 📜 Änderungsprotokoll (Services) - - <% } %> - - <% if (user.role === 'arzt') { %> - - 🏢 Firmendaten - - <% } %> + <% if (user.role === 'arzt') { %> + + 📜 Änderungsprotokoll (Services) + + <% } %> <% if (user.role === 'arzt') { %> + + 🏢 Firmendaten + + <% } %> <% if (user.role === 'arzt') { %> + + 💶 Abrechnung + + <% } %>
-
+
- -
- +
🪑 Wartezimmer-Monitor
- <% - const maxSlots = 21; // 3 Reihen × 7 Plätze - for (let i = 0; i < maxSlots; i++) { - const p = waitingPatients && waitingPatients[i]; - %> - -
- <% if (p) { %> -
- <%= p.firstname %> <%= p.lastname %> -
-
- <%= new Date(p.birthdate).toLocaleDateString("de-DE") %> -
- <% } else { %> -
- Freier Platz -
- <% } %> -
+ <% const maxSlots = 21; for (let i = 0; i < maxSlots; i++) { const p = + waitingPatients && waitingPatients[i]; %> +
+ <% if (p) { %> +
<%= p.firstname %> <%= p.lastname %>
+
+ <%= new Date(p.birthdate).toLocaleDateString("de-DE") %> +
+ <% } else { %> +
+ Freier Platz +
<% } %> +
+ + <% } %>
- +
- -
- - +