diff --git a/controllers/admin.controller.js b/controllers/admin.controller.js index 0ae9390..1801c20 100644 --- a/controllers/admin.controller.js +++ b/controllers/admin.controller.js @@ -1,13 +1,23 @@ const db = require("../db"); -const { createUser, getAllUsers} = require("../services/admin.service"); +const { createUser, getAllUsers } = require("../services/admin.service"); const bcrypt = require("bcrypt"); async function listUsers(req, res) { + const { q } = req.query; + try { - const users = await getAllUsers(db); + let users; + + if (q) { + users = await getAllUsers(db, q); + } else { + users = await getAllUsers(db); + } + res.render("admin_users", { users, - currentUser: req.session.user + currentUser: req.session.user, + query: { q }, }); } catch (err) { console.error(err); @@ -18,7 +28,7 @@ async function listUsers(req, res) { function showCreateUser(req, res) { res.render("admin_create_user", { error: null, - user: req.session.user + user: req.session.user, }); } @@ -30,20 +40,20 @@ async function postCreateUser(req, res) { password, role, fachrichtung, - arztnummer + arztnummer, } = req.body; - first_name = first_name?.trim(); - last_name = last_name?.trim(); - username = username?.trim(); + first_name = first_name?.trim(); + last_name = last_name?.trim(); + username = username?.trim(); fachrichtung = fachrichtung?.trim(); - arztnummer = arztnummer?.trim(); + arztnummer = arztnummer?.trim(); // 🔴 Grundvalidierung if (!first_name || !last_name || !username || !password || !role) { return res.render("admin_create_user", { error: "Alle Pflichtfelder müssen ausgefüllt sein", - user: req.session.user + user: req.session.user, }); } @@ -52,7 +62,7 @@ async function postCreateUser(req, res) { if (!fachrichtung || !arztnummer) { return res.render("admin_create_user", { error: "Für Ärzte sind Fachrichtung und Arztnummer Pflicht", - user: req.session.user + user: req.session.user, }); } } else { @@ -75,20 +85,18 @@ async function postCreateUser(req, res) { req.session.flash = { type: "success", - message: "Benutzer erfolgreich angelegt" + message: "Benutzer erfolgreich angelegt", }; res.redirect("/admin/users"); - } catch (error) { res.render("admin_create_user", { error, - user: req.session.user + user: req.session.user, }); } } - async function changeUserRole(req, res) { const userId = req.params.id; const { role } = req.body; @@ -98,19 +106,21 @@ async function changeUserRole(req, res) { return res.redirect("/admin/users"); } - db.query( - "UPDATE users SET role = ? WHERE id = ?", - [role, userId], - err => { - if (err) { - console.error(err); - req.session.flash = { type: "danger", message: "Fehler beim Ändern der Rolle" }; - } else { - req.session.flash = { type: "success", message: "Rolle erfolgreich geändert" }; - } - res.redirect("/admin/users"); + db.query("UPDATE users SET role = ? WHERE id = ?", [role, userId], (err) => { + if (err) { + console.error(err); + req.session.flash = { + type: "danger", + message: "Fehler beim Ändern der Rolle", + }; + } else { + req.session.flash = { + type: "success", + message: "Rolle erfolgreich geändert", + }; } - ); + res.redirect("/admin/users"); + }); } async function resetUserPassword(req, res) { @@ -123,27 +133,74 @@ async function resetUserPassword(req, res) { } const hash = await bcrypt.hash(password, 10); - + db.query( "UPDATE users SET password = ? WHERE id = ?", [hash, userId], - err => { + (err) => { if (err) { console.error(err); - req.session.flash = { type: "danger", message: "Fehler beim Zurücksetzen" }; + req.session.flash = { + type: "danger", + message: "Fehler beim Zurücksetzen", + }; } else { - req.session.flash = { type: "success", message: "Passwort zurückgesetzt" }; + req.session.flash = { + type: "success", + message: "Passwort zurückgesetzt", + }; } res.redirect("/admin/users"); } ); } +function activateUser(req, res) { + const userId = req.params.id; + + db.query("UPDATE users SET active = 1 WHERE id = ?", [userId], (err) => { + if (err) { + console.error(err); + req.session.flash = { + type: "danger", + message: "Benutzer konnte nicht aktiviert werden", + }; + } else { + req.session.flash = { + type: "success", + message: "Benutzer wurde aktiviert", + }; + } + res.redirect("/admin/users"); + }); +} + +function deactivateUser(req, res) { + const userId = req.params.id; + + db.query("UPDATE users SET active = 0 WHERE id = ?", [userId], (err) => { + if (err) { + console.error(err); + req.session.flash = { + type: "danger", + message: "Benutzer konnte nicht deaktiviert werden", + }; + } else { + req.session.flash = { + type: "success", + message: "Benutzer wurde deaktiviert", + }; + } + res.redirect("/admin/users"); + }); +} module.exports = { listUsers, showCreateUser, postCreateUser, changeUserRole, - resetUserPassword + resetUserPassword, + activateUser, + deactivateUser, }; diff --git a/controllers/patient.controller.js b/controllers/patient.controller.js index c871b41..2021378 100644 --- a/controllers/patient.controller.js +++ b/controllers/patient.controller.js @@ -10,7 +10,7 @@ function createPatient(req, res) { db.query( "INSERT INTO patients (firstname, lastname, birthdate, active) VALUES (?, ?, ?, 1)", [firstname, lastname, birthdate], - err => { + (err) => { if (err) { console.error(err); return res.send("Datenbankfehler"); @@ -26,15 +26,28 @@ function listPatients(req, res) { let sql = "SELECT * FROM patients WHERE 1=1"; const params = []; - if (firstname) { sql += " AND firstname LIKE ?"; params.push(`%${firstname}%`); } - if (lastname) { sql += " AND lastname LIKE ?"; params.push(`%${lastname}%`); } - if (birthdate) { sql += " AND birthdate = ?"; params.push(birthdate); } + if (firstname) { + sql += " AND firstname LIKE ?"; + params.push(`%${firstname}%`); + } + if (lastname) { + sql += " AND lastname LIKE ?"; + params.push(`%${lastname}%`); + } + if (birthdate) { + sql += " AND birthdate = ?"; + params.push(birthdate); + } sql += " ORDER BY lastname, firstname"; db.query(sql, params, (err, patients) => { if (err) return res.send("Datenbankfehler"); - res.render("patients", { patients, query: req.query, user: req.session.user}); + res.render("patients", { + patients, + query: req.query, + user: req.session.user, + }); }); } @@ -43,12 +56,13 @@ function showEditPatient(req, res) { "SELECT * FROM patients WHERE id = ?", [req.params.id], (err, results) => { - if (err || results.length === 0) return res.send("Patient nicht gefunden"); + if (err || results.length === 0) + return res.send("Patient nicht gefunden"); res.render("patient_edit", { patient: results[0], error: null, user: req.session.user, - returnTo: req.query.returnTo || null + returnTo: req.query.returnTo || null, }); } ); @@ -71,13 +85,13 @@ function updatePatient(req, res) { postal_code, city, country, - notes + notes, } = req.body; if (!firstname || !lastname || !birthdate) { req.session.flash = { type: "warning", - message: "Vorname, Nachname und Geburtsdatum sind Pflichtfelder" + message: "Vorname, Nachname und Geburtsdatum sind Pflichtfelder", }; return res.redirect("back"); } @@ -112,9 +126,9 @@ function updatePatient(req, res) { city || null, country || null, notes || null, - id + id, ], - err => { + (err) => { if (err) { console.error(err); return res.send("Fehler beim Speichern"); @@ -174,14 +188,15 @@ function showPatientMedications(req, res) { if (err) return res.send("Medikamente konnten nicht geladen werden"); db.query(currentSql, [patientId], (err, currentMeds) => { - if (err) return res.send("Aktuelle Medikation konnte nicht geladen werden"); + if (err) + return res.send("Aktuelle Medikation konnte nicht geladen werden"); res.render("patient_medications", { patient: patients[0], meds, currentMeds, user: req.session.user, - returnTo + returnTo, }); }); }); @@ -199,7 +214,7 @@ function moveToWaitingRoom(req, res) { WHERE id = ? `, [id], - err => { + (err) => { if (err) return res.send("Fehler beim Verschieben ins Wartezimmer"); res.redirect("/patients"); } @@ -214,7 +229,7 @@ function showWaitingRoom(req, res) { res.render("waiting_room", { patients, - user: req.session.user + user: req.session.user, }); } ); @@ -236,15 +251,17 @@ function showPatientOverview(req, res) { ORDER BY created_at DESC `; - // 🔤 Services dynamisch nach Sprache laden - const servicesSql = (nameField) => ` + const medicationVariantsSql = ` SELECT - id, - ${nameField} AS name, - price - FROM services - WHERE active = 1 - ORDER BY ${nameField} + mv.id AS variant_id, + m.name AS medication_name, + mf.name AS form_name, + mv.dosage, + mv.package + FROM medication_variants mv + JOIN medications m ON mv.medication_id = m.id + JOIN medication_forms mf ON mv.form_id = mf.id + ORDER BY m.name, mf.name, mv.dosage `; db.query(patientSql, [patientId], (err, patients) => { @@ -254,12 +271,22 @@ function showPatientOverview(req, res) { const patient = patients[0]; - // 🇪🇸 / 🇩🇪 Sprache bestimmen + // 🇪🇸 / 🇩🇪 Sprache für Leistungen const serviceNameField = patient.country === "ES" ? "COALESCE(NULLIF(name_es, ''), name_de)" : "name_de"; + const servicesSql = ` + SELECT + id, + ${serviceNameField} AS name, + price + FROM services + WHERE active = 1 + ORDER BY ${serviceNameField} + `; + const todayServicesSql = ` SELECT ps.id, @@ -279,18 +306,23 @@ function showPatientOverview(req, res) { db.query(notesSql, [patientId], (err, notes) => { if (err) return res.send("Fehler Notizen"); - db.query(servicesSql(serviceNameField), (err, services) => { + db.query(servicesSql, (err, services) => { if (err) return res.send("Fehler Leistungen"); db.query(todayServicesSql, [patientId], (err, todayServices) => { if (err) return res.send("Fehler heutige Leistungen"); - res.render("patient_overview", { - patient, - notes, - services, - todayServices, - user: req.session.user + db.query(medicationVariantsSql, (err, medicationVariants) => { + if (err) return res.send("Fehler Medikamente"); + + res.render("patient_overview", { + patient, + notes, + services, + todayServices, + medicationVariants, + user: req.session.user, + }); }); }); }); @@ -298,6 +330,47 @@ function showPatientOverview(req, res) { }); } +function assignMedicationToPatient(req, res) { + const patientId = req.params.id; + const { medication_variant_id, dosage_instruction, start_date, end_date } = + req.body; + + if (!medication_variant_id) { + req.session.flash = { + type: "warning", + message: "Bitte ein Medikament auswählen", + }; + return res.redirect(`/patients/${patientId}/overview`); + } + + db.query( + ` + INSERT INTO patient_medications + (patient_id, medication_variant_id, dosage_instruction, start_date, end_date) + VALUES (?, ?, ?, ?, ?) + `, + [ + patientId, + medication_variant_id, + dosage_instruction || null, + start_date || new Date(), + end_date || null, + ], + (err) => { + if (err) { + console.error(err); + return res.send("Fehler beim Verordnen"); + } + + req.session.flash = { + type: "success", + message: "Medikament erfolgreich verordnet", + }; + + res.redirect(`/patients/${patientId}/overview`); + } + ); +} function addPatientNote(req, res) { const patientId = req.params.id; @@ -310,7 +383,7 @@ function addPatientNote(req, res) { db.query( "INSERT INTO patient_notes (patient_id, note) VALUES (?, ?)", [patientId, note], - err => { + (err) => { if (err) return res.send("Fehler beim Speichern der Notiz"); res.redirect(`/patients/${patientId}/overview`); } @@ -323,7 +396,7 @@ function callFromWaitingRoom(req, res) { db.query( "UPDATE patients SET waiting_room = 0 WHERE id = ?", [patientId], - err => { + (err) => { if (err) return res.send("Fehler beim Entfernen aus dem Wartezimmer"); res.redirect(`/patients/${patientId}/overview`); } @@ -336,7 +409,7 @@ function dischargePatient(req, res) { db.query( "UPDATE patients SET discharged = 1 WHERE id = ?", [patientId], - err => { + (err) => { if (err) return res.send("Fehler beim Entlassen des Patienten"); res.redirect("/waiting-room"); } @@ -375,7 +448,7 @@ function showMedicationPlan(req, res) { res.render("patient_plan", { patient: patients[0], - meds + meds, }); }); }); @@ -392,19 +465,19 @@ function movePatientToWaitingRoom(req, res) { WHERE id = ? `, [patientId], - err => { + (err) => { if (err) { console.error(err); req.session.flash = { type: "danger", - message: "Fehler beim Zurücksetzen ins Wartezimmer" + message: "Fehler beim Zurücksetzen ins Wartezimmer", }; return res.redirect(`/patients/${patientId}/overview`); } req.session.flash = { type: "success", - message: "Patient wurde ins Wartezimmer gesetzt" + message: "Patient wurde ins Wartezimmer gesetzt", }; res.redirect("/waiting-room"); @@ -415,55 +488,107 @@ function movePatientToWaitingRoom(req, res) { function deactivatePatient(req, res) { const id = req.params.id; - db.query( - "UPDATE patients SET active = 0 WHERE id = ?", - [id], - err => { - if (err) { - console.error(err); - req.session.flash = { - type: "danger", - message: "Patient konnte nicht gesperrt werden" - }; - return res.redirect("/patients"); - } - + db.query("UPDATE patients SET active = 0 WHERE id = ?", [id], (err) => { + if (err) { + console.error(err); req.session.flash = { - type: "success", - message: "Patient wurde gesperrt" + type: "danger", + message: "Patient konnte nicht gesperrt werden", }; - - res.redirect("/patients"); + return res.redirect("/patients"); } - ); + + req.session.flash = { + type: "success", + message: "Patient wurde gesperrt", + }; + + res.redirect("/patients"); + }); } function activatePatient(req, res) { const id = req.params.id; - db.query( - "UPDATE patients SET active = 1 WHERE id = ?", - [id], - err => { - if (err) { - console.error(err); - req.session.flash = { - type: "danger", - message: "Patient konnte nicht entsperrt werden" - }; - return res.redirect("/patients"); - } - + db.query("UPDATE patients SET active = 1 WHERE id = ?", [id], (err) => { + if (err) { + console.error(err); req.session.flash = { - type: "success", - message: "Patient wurde entsperrt" + type: "danger", + message: "Patient konnte nicht entsperrt werden", }; - - res.redirect("/patients"); + return res.redirect("/patients"); } - ); + + req.session.flash = { + type: "success", + message: "Patient wurde entsperrt", + }; + + res.redirect("/patients"); + }); } +async function showPatientOverviewDashborad(req, res) { + const patientId = req.params.id; + + try { + // 👤 Patient + const [[patient]] = await db + .promise() + .query("SELECT * FROM patients WHERE id = ?", [patientId]); + + if (!patient) { + return res.redirect("/patients"); + } + + // 💊 AKTUELLE MEDIKAMENTE (end_date IS NULL) + const [medications] = await db.promise().query( + ` + SELECT + m.name AS medication_name, + mv.dosage AS variant_dosage, + pm.dosage_instruction, + pm.start_date + FROM patient_medications pm + JOIN medication_variants mv + ON pm.medication_variant_id = mv.id + JOIN medications m + ON mv.medication_id = m.id + WHERE pm.patient_id = ? + AND pm.end_date IS NULL + ORDER BY pm.start_date DESC + `, + [patientId] + ); + + // 🧾 RECHNUNGEN + const [invoices] = await db.promise().query( + ` + SELECT + id, + invoice_date, + total_amount, + file_path, + status + FROM invoices + WHERE patient_id = ? + ORDER BY invoice_date DESC + `, + [patientId] + ); + + res.render("patient_overview_dashboard", { + patient, + medications, + invoices, + user: req.session.user, + }); + } catch (err) { + console.error(err); + res.send("Datenbankfehler"); + } +} module.exports = { listPatients, @@ -481,5 +606,7 @@ module.exports = { showMedicationPlan, movePatientToWaitingRoom, deactivatePatient, - activatePatient + activatePatient, + showPatientOverviewDashborad, + assignMedicationToPatient, }; diff --git a/public/invoices/2026/invoice-2026-0037.pdf b/public/invoices/2026/invoice-2026-0037.pdf new file mode 100644 index 0000000..dcb197f Binary files /dev/null and b/public/invoices/2026/invoice-2026-0037.pdf differ diff --git a/public/invoices/2026/invoice-2026-0038.pdf b/public/invoices/2026/invoice-2026-0038.pdf new file mode 100644 index 0000000..f73d673 Binary files /dev/null and b/public/invoices/2026/invoice-2026-0038.pdf differ diff --git a/routes/admin.routes.js b/routes/admin.routes.js index 74bec4b..a39dad3 100644 --- a/routes/admin.routes.js +++ b/routes/admin.routes.js @@ -6,7 +6,9 @@ const { showCreateUser, postCreateUser, changeUserRole, - resetUserPassword + resetUserPassword, + activateUser, + deactivateUser, } = require("../controllers/admin.controller"); const { requireAdmin } = require("../middleware/auth.middleware"); @@ -17,6 +19,7 @@ router.post("/create-user", requireAdmin, postCreateUser); 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); module.exports = router; - diff --git a/routes/patient.routes.js b/routes/patient.routes.js index 271dc1f..7641a87 100644 --- a/routes/patient.routes.js +++ b/routes/patient.routes.js @@ -1,10 +1,7 @@ const express = require("express"); const router = express.Router(); -const { - requireLogin, - requireAdmin -} = require("../middleware/auth.middleware"); +const { requireLogin, requireAdmin } = require("../middleware/auth.middleware"); const { listPatients, @@ -20,7 +17,9 @@ const { dischargePatient, showMedicationPlan, deactivatePatient, - activatePatient + activatePatient, + showPatientOverviewDashborad, + assignMedicationToPatient, } = require("../controllers/patient.controller"); router.get("/", requireLogin, listPatients); @@ -35,8 +34,9 @@ router.post("/:id/notes", requireLogin, addPatientNote); router.post("/waiting-room/call/:id", requireAdmin, callFromWaitingRoom); router.post("/:id/discharge", requireLogin, dischargePatient); router.get("/:id/plan", requireLogin, showMedicationPlan); -router.post("/deactivate/:id", requireLogin, deactivatePatient); -router.post("/activate/:id", requireLogin, activatePatient); +router.post("/deactivate/:id", requireLogin, deactivatePatient); +router.post("/activate/:id", requireLogin, activatePatient); +router.get("/:id", requireLogin, showPatientOverviewDashborad); +router.post("/:id/medications/assign", requireLogin, assignMedicationToPatient); module.exports = router; - diff --git a/services/admin.service.js b/services/admin.service.js index 9e21dd7..5846c1b 100644 --- a/services/admin.service.js +++ b/services/admin.service.js @@ -17,16 +17,8 @@ async function createUser( `INSERT INTO users (first_name, last_name, username, password, role, fachrichtung, arztnummer, active) VALUES (?, ?, ?, ?, ?, ?, ?, 1)`, - [ - first_name, - last_name, - username, - hash, - role, - fachrichtung, - arztnummer - ], - err => { + [first_name, last_name, username, hash, role, fachrichtung, arztnummer], + (err) => { if (err) { if (err.code === "ER_DUP_ENTRY") { return reject("Benutzername existiert bereits"); @@ -39,28 +31,33 @@ async function createUser( }); } -function getAllUsers(db) { - return new Promise((resolve, reject) => { - db.query( - `SELECT - id, - first_name, - last_name, - username, - role, - active, - lock_until - FROM users - ORDER BY last_name, first_name`, - (err, users) => { - if (err) return reject(err); - resolve(users); - } - ); - }); +async function getAllUsers(db, search = null) { + let sql = ` + SELECT * + FROM users + WHERE 1=1 + `; + const params = []; + + if (search) { + sql += ` + AND ( + first_name LIKE ? + OR last_name LIKE ? + OR username LIKE ? + ) + `; + const q = `%${search}%`; + params.push(q, q, q); + } + + sql += " ORDER BY last_name, first_name"; + + const [rows] = await db.promise().query(sql, params); + return rows; } module.exports = { createUser, - getAllUsers + getAllUsers, }; diff --git a/views/admin_users.ejs b/views/admin_users.ejs index 84faeb6..44f7e03 100644 --- a/views/admin_users.ejs +++ b/views/admin_users.ejs @@ -44,6 +44,30 @@ + Neuen Benutzer anlegen +