Abrechnungsübersicht erstellt admin_invoice_overview

This commit is contained in:
Cay 2026-01-09 18:59:50 +00:00
parent d41b0d22d1
commit 8754c22dc4
5 changed files with 385 additions and 97 deletions

View File

@ -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,
};

Binary file not shown.

View File

@ -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;

View File

@ -0,0 +1,233 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<title>Rechnungsübersicht</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<link rel="stylesheet" href="/bootstrap-icons/bootstrap-icons.min.css" />
</head>
<body class="bg-light">
<!-- =========================
NAVBAR
========================== -->
<nav class="navbar navbar-dark bg-dark position-relative px-3">
<div
class="position-absolute top-50 start-50 translate-middle d-flex align-items-center gap-2 text-white"
>
<i class="bi bi-calculator fs-4"></i>
<span class="fw-semibold fs-5">Rechnungsübersicht</span>
</div>
<!-- 🔵 RECHTS: DASHBOARD -->
<div class="ms-auto">
<a href="/dashboard" class="btn btn-outline-primary btn-sm">
⬅️ Dashboard
</a>
</div>
</nav>
<!-- =========================
FILTER: JAHR VON / BIS
========================== -->
<div class="container-fluid mt-4">
<form method="get" class="row g-2 mb-4">
<div class="col-auto">
<input
type="number"
name="fromYear"
class="form-control"
placeholder="Von Jahr"
value="<%= fromYear %>"
/>
</div>
<div class="col-auto">
<input
type="number"
name="toYear"
class="form-control"
placeholder="Bis Jahr"
value="<%= toYear %>"
/>
</div>
<div class="col-auto">
<button class="btn btn-outline-secondary">Filtern</button>
</div>
</form>
<!-- =========================
GRID 4 SPALTEN
========================== -->
<div class="row g-3">
<!-- =========================
JAHRESUMSATZ
========================== -->
<div class="col-xl-3 col-lg-6">
<div class="card h-100">
<div class="card-header fw-semibold">Jahresumsatz</div>
<div class="card-body p-0">
<table class="table table-sm table-striped mb-0">
<thead>
<tr>
<th>Jahr</th>
<th class="text-end">€</th>
</tr>
</thead>
<tbody>
<% if (yearly.length === 0) { %>
<tr>
<td colspan="2" class="text-center text-muted">
Keine Daten
</td>
</tr>
<% } %> <% yearly.forEach(y => { %>
<tr>
<td><%= y.year %></td>
<td class="text-end fw-semibold">
<%= Number(y.total).toFixed(2) %>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</div>
</div>
<!-- =========================
QUARTALSUMSATZ
========================== -->
<div class="col-xl-3 col-lg-6">
<div class="card h-100">
<div class="card-header fw-semibold">Quartalsumsatz</div>
<div class="card-body p-0">
<table class="table table-sm table-striped mb-0">
<thead>
<tr>
<th>Jahr</th>
<th>Q</th>
<th class="text-end">€</th>
</tr>
</thead>
<tbody>
<% if (quarterly.length === 0) { %>
<tr>
<td colspan="3" class="text-center text-muted">
Keine Daten
</td>
</tr>
<% } %> <% quarterly.forEach(q => { %>
<tr>
<td><%= q.year %></td>
<td>Q<%= q.quarter %></td>
<td class="text-end fw-semibold">
<%= Number(q.total).toFixed(2) %>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</div>
</div>
<!-- =========================
MONATSUMSATZ
========================== -->
<div class="col-xl-3 col-lg-6">
<div class="card h-100">
<div class="card-header fw-semibold">Monatsumsatz</div>
<div class="card-body p-0">
<table class="table table-sm table-striped mb-0">
<thead>
<tr>
<th>Monat</th>
<th class="text-end">€</th>
</tr>
</thead>
<tbody>
<% if (monthly.length === 0) { %>
<tr>
<td colspan="2" class="text-center text-muted">
Keine Daten
</td>
</tr>
<% } %> <% monthly.forEach(m => { %>
<tr>
<td><%= m.month %></td>
<td class="text-end fw-semibold">
<%= Number(m.total).toFixed(2) %>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</div>
</div>
<!-- =========================
UMSATZ PRO PATIENT
========================== -->
<div class="col-xl-3 col-lg-6">
<div class="card h-100">
<div class="card-header fw-semibold">Umsatz pro Patient</div>
<div class="card-body p-2">
<!-- 🔍 Suche -->
<form method="get" class="mb-2 d-flex gap-2">
<input type="hidden" name="fromYear" value="<%= fromYear %>" />
<input type="hidden" name="toYear" value="<%= toYear %>" />
<input
type="text"
name="q"
value="<%= search %>"
class="form-control form-control-sm"
placeholder="Patient suchen..."
/>
<button class="btn btn-sm btn-outline-primary">Suchen</button>
<a
href="/admin/invoices?fromYear=<%= fromYear %>&toYear=<%= toYear %>"
class="btn btn-sm btn-outline-secondary"
>
Reset
</a>
</form>
<table class="table table-sm table-striped mb-0">
<thead>
<tr>
<th>Patient</th>
<th class="text-end">€</th>
</tr>
</thead>
<tbody>
<% if (patients.length === 0) { %>
<tr>
<td colspan="2" class="text-center text-muted">
Keine Daten
</td>
</tr>
<% } %> <% patients.forEach(p => { %>
<tr>
<td><%= p.patient %></td>
<td class="text-end fw-semibold">
<%= Number(p.total).toFixed(2) %>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,37 +1,30 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta charset="UTF-8" />
<title>Dashboard</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/bootstrap-icons/bootstrap-icons.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="/bootstrap-icons/bootstrap-icons.min.css" />
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark position-relative px-3">
<!-- 🟢 ZENTRIERTER TITEL -->
<div class="position-absolute top-50 start-50 translate-middle
d-flex align-items-center gap-2 text-white">
<div
class="position-absolute top-50 start-50 translate-middle d-flex align-items-center gap-2 text-white"
>
<i class="bi bi-speedometer2 fs-4"></i>
<span class="fw-semibold fs-5">Dashboard</span>
</div>
<!-- 🔴 RECHTS: LOGOUT -->
<div class="ms-auto">
<a href="/logout" class="btn btn-outline-light btn-sm">
Logout
</a>
<a href="/logout" class="btn btn-outline-light btn-sm"> Logout </a>
</div>
</nav>
<div class="container-fluid mt-4">
<!-- Flash Messages -->
<%- include("partials/flash") %>
@ -52,36 +45,32 @@
</a>
<% } %>
<a href="/patients" class="btn btn-primary">
Patientenübersicht
</a>
<a href="/patients" class="btn btn-primary"> Patientenübersicht </a>
<a href="/medications" class="btn btn-secondary">
Medikamentenübersicht
</a>
<% if (user.role === 'arzt') { %>
<a href="/services" class="btn btn-secondary">
🧾 Leistungen
</a>
<a href="/services" class="btn btn-secondary"> 🧾 Leistungen </a>
<% } %>
<a href="/services/open"
class="btn btn-warning">
<a href="/services/open" class="btn btn-warning">
🧾 Offene Leistungen
</a>
<% if (user.role === 'arzt') { %>
<a href="/services/logs" class="btn btn-outline-secondary">
📜 Änderungsprotokoll (Services)
</a>
<% } %>
<% if (user.role === 'arzt') { %>
<% } %> <% if (user.role === 'arzt') { %>
<a href="/admin/company-settings" class="btn btn-outline-dark">
🏢 Firmendaten
</a>
<% } %> <% if (user.role === 'arzt') { %>
<a href="/admin/invoices" class="btn btn-outline-success">
💶 Abrechnung
</a>
<% } %>
</div>
</div>
@ -90,39 +79,32 @@
UNTERE HÄLFTE MONITOR
========================== -->
<div class="waiting-monitor">
<h5 class="mb-3">🪑 Wartezimmer-Monitor</h5>
<div class="waiting-grid">
<%
const maxSlots = 21; // 3 Reihen × 7 Plätze
for (let i = 0; i < maxSlots; i++) {
const p = waitingPatients && waitingPatients[i];
%>
<% const maxSlots = 21; for (let i = 0; i < maxSlots; i++) { const p =
waitingPatients && waitingPatients[i]; %>
<div class="waiting-slot <%= p ? 'occupied' : 'empty' %>">
<% if (p) { %>
<div class="name">
<%= p.firstname %> <%= p.lastname %>
</div>
<div class="name"><%= p.firstname %> <%= p.lastname %></div>
<div class="birthdate">
<%= new Date(p.birthdate).toLocaleDateString("de-DE") %>
</div>
<% } else { %>
<div class="placeholder">
<img src="/images/stuhl.jpg"
<img
src="/images/stuhl.jpg"
alt="Freier Platz"
class="chair-icon">
class="chair-icon"
/>
</div>
<% } %>
</div>
<% } %>
</div>
</div>
</div>
</body>
</html>