Compare commits
No commits in common. "860b41ab287310ddee794b4c539bb153b93c85b8" and "7ab67a839b96fc208293efd29ceaeec9f647f285" have entirely different histories.
860b41ab28
...
7ab67a839b
BIN
2024091001 Preisliste PRAXIS.xlsx
Normal file
BIN
2024091001 Preisliste PRAXIS.xlsx
Normal file
Binary file not shown.
BIN
MEDIKAMENTE 228.02.2024 docx.docx
Normal file
BIN
MEDIKAMENTE 228.02.2024 docx.docx
Normal file
Binary file not shown.
277
app.js
277
app.js
@ -1,252 +1,109 @@
|
||||
require("dotenv").config();
|
||||
|
||||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const helmet = require("helmet");
|
||||
const mysql = require("mysql2/promise");
|
||||
const bcrypt = require("bcrypt");
|
||||
const db = require("./db");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// ✅ Verschlüsselte Config
|
||||
const { configExists, saveConfig } = require("./config-manager");
|
||||
|
||||
// ✅ Reset-Funktionen (Soft-Restart)
|
||||
const db = require("./db");
|
||||
const { getSessionStore, resetSessionStore } = require("./config/session");
|
||||
|
||||
// ✅ Deine Routes (unverändert)
|
||||
const { requireLogin, requireAdmin} = require("./middleware/auth.middleware");
|
||||
const adminRoutes = require("./routes/admin.routes");
|
||||
const dashboardRoutes = require("./routes/dashboard.routes");
|
||||
const helmet = require("helmet");
|
||||
const sessionStore = require("./config/session");
|
||||
const patientRoutes = require("./routes/patient.routes");
|
||||
const medicationRoutes = require("./routes/medications.routes");
|
||||
const medicationRoutes = require("./routes/medication.routes");
|
||||
const patientMedicationRoutes = require("./routes/patientMedication.routes");
|
||||
const waitingRoomRoutes = require("./routes/waitingRoom.routes");
|
||||
const serviceRoutes = require("./routes/service.routes");
|
||||
const patientServiceRoutes = require("./routes/patientService.routes");
|
||||
const invoiceRoutes = require("./routes/invoice.routes");
|
||||
const patientFileRoutes = require("./routes/patientFile.routes");
|
||||
const companySettingsRoutes = require("./routes/companySettings.routes");
|
||||
const authRoutes = require("./routes/auth.routes");
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
const app = express();
|
||||
|
||||
/* ===============================
|
||||
SETUP HTML
|
||||
================================ */
|
||||
function setupHtml(error = "") {
|
||||
return `
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Praxissoftware Setup</title>
|
||||
<style>
|
||||
body{font-family:Arial;background:#f4f4f4;padding:30px}
|
||||
.card{max-width:520px;margin:auto;background:#fff;padding:22px;border-radius:14px}
|
||||
input{width:100%;padding:10px;margin:6px 0;border:1px solid #ccc;border-radius:8px}
|
||||
button{padding:10px 14px;border:0;border-radius:8px;background:#111;color:#fff;cursor:pointer}
|
||||
.err{color:#b00020;margin:10px 0}
|
||||
.hint{color:#666;font-size:13px;margin-top:12px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h2>🔧 Datenbank Einrichtung</h2>
|
||||
${error ? `<div class="err">❌ ${error}</div>` : ""}
|
||||
|
||||
<form method="POST" action="/setup">
|
||||
<label>DB Host</label>
|
||||
<input name="host" placeholder="85.215.63.122" required />
|
||||
|
||||
<label>DB Benutzer</label>
|
||||
<input name="user" placeholder="praxisuser" required />
|
||||
|
||||
<label>DB Passwort</label>
|
||||
<input name="password" type="password" required />
|
||||
|
||||
<label>DB Name</label>
|
||||
<input name="name" placeholder="praxissoftware" required />
|
||||
|
||||
<button type="submit">✅ Speichern</button>
|
||||
</form>
|
||||
|
||||
<div class="hint">
|
||||
Die Daten werden verschlüsselt gespeichert (<b>config.enc</b>).<br/>
|
||||
Danach wirst du automatisch auf die Loginseite weitergeleitet.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
/* ===============================
|
||||
MIDDLEWARE
|
||||
================================ */
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(express.json());
|
||||
app.use(helmet());
|
||||
|
||||
// ✅ SessionStore dynamisch (Setup: MemoryStore, normal: MySQLStore)
|
||||
app.use(
|
||||
session({
|
||||
name: "praxis.sid",
|
||||
secret: process.env.SESSION_SECRET,
|
||||
store: getSessionStore(),
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
}),
|
||||
);
|
||||
|
||||
// ✅ i18n Middleware
|
||||
app.use((req, res, next) => {
|
||||
const lang = req.session.lang || "de"; // Standard DE
|
||||
|
||||
const filePath = path.join(__dirname, "locales", `${lang}.json`);
|
||||
const raw = fs.readFileSync(filePath, "utf-8");
|
||||
|
||||
res.locals.t = JSON.parse(raw); // t = translations
|
||||
res.locals.lang = lang;
|
||||
|
||||
next();
|
||||
});
|
||||
app.use(session({
|
||||
name: "praxis.sid",
|
||||
secret: process.env.SESSION_SECRET,
|
||||
store: sessionStore,
|
||||
resave: false,
|
||||
saveUninitialized: false
|
||||
}));
|
||||
|
||||
const flashMiddleware = require("./middleware/flash.middleware");
|
||||
app.use(flashMiddleware);
|
||||
|
||||
app.use(express.static("public"));
|
||||
app.use("/uploads", express.static("uploads"));
|
||||
|
||||
app.set("view engine", "ejs");
|
||||
app.use((req, res, next) => {
|
||||
res.locals.user = req.session.user || null;
|
||||
next();
|
||||
});
|
||||
|
||||
app.use("/patients", require("./routes/patient.routes"));
|
||||
|
||||
app.use("/uploads", express.static("uploads"));
|
||||
|
||||
|
||||
/* ===============================
|
||||
SETUP ROUTES
|
||||
LOGIN
|
||||
================================ */
|
||||
const authRoutes = require("./routes/auth.routes");
|
||||
|
||||
// Setup-Seite
|
||||
app.get("/setup", (req, res) => {
|
||||
if (configExists()) return res.redirect("/");
|
||||
return res.status(200).send(setupHtml());
|
||||
});
|
||||
|
||||
// Setup speichern + DB testen + Soft-Restart + Login redirect
|
||||
app.post("/setup", async (req, res) => {
|
||||
try {
|
||||
const { host, user, password, name } = req.body;
|
||||
|
||||
if (!host || !user || !password || !name) {
|
||||
return res.status(400).send(setupHtml("Bitte alle Felder ausfüllen."));
|
||||
}
|
||||
|
||||
// ✅ DB Verbindung testen
|
||||
const conn = await mysql.createConnection({
|
||||
host,
|
||||
user,
|
||||
password,
|
||||
database: name,
|
||||
});
|
||||
|
||||
await conn.query("SELECT 1");
|
||||
await conn.end();
|
||||
|
||||
// ✅ verschlüsselt speichern
|
||||
saveConfig({
|
||||
db: { host, user, password, name },
|
||||
});
|
||||
|
||||
// ✅ Soft-Restart (DB Pool + SessionStore neu laden)
|
||||
if (typeof db.resetPool === "function") {
|
||||
db.resetPool();
|
||||
}
|
||||
resetSessionStore();
|
||||
|
||||
// ✅ automatisch zurück zur Loginseite
|
||||
return res.redirect("/");
|
||||
} catch (err) {
|
||||
return res
|
||||
.status(500)
|
||||
.send(setupHtml("DB Verbindung fehlgeschlagen: " + err.message));
|
||||
}
|
||||
});
|
||||
|
||||
// Wenn keine config.enc → alles außer /setup auf Setup umleiten
|
||||
app.use((req, res, next) => {
|
||||
if (!configExists() && req.path !== "/setup") {
|
||||
return res.redirect("/setup");
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
//Sprachen Route
|
||||
// ✅ i18n Middleware (Sprache pro Benutzer über Session)
|
||||
app.use((req, res, next) => {
|
||||
const lang = req.session.lang || "de"; // Standard: Deutsch
|
||||
|
||||
let translations = {};
|
||||
try {
|
||||
const filePath = path.join(__dirname, "locales", `${lang}.json`);
|
||||
translations = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
||||
} catch (err) {
|
||||
console.error("❌ i18n Fehler:", err.message);
|
||||
}
|
||||
|
||||
// ✅ In EJS verfügbar machen
|
||||
res.locals.t = translations;
|
||||
res.locals.lang = lang;
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
app.get("/lang/:lang", (req, res) => {
|
||||
const newLang = req.params.lang;
|
||||
|
||||
if (!["de", "es"].includes(newLang)) {
|
||||
return res.redirect(req.get("Referrer") || "/dashboard");
|
||||
}
|
||||
|
||||
req.session.lang = newLang;
|
||||
|
||||
// ✅ WICHTIG: Session speichern bevor redirect
|
||||
req.session.save((err) => {
|
||||
if (err) console.error("❌ Session save error:", err);
|
||||
|
||||
return res.redirect(req.get("Referrer") || "/dashboard");
|
||||
});
|
||||
});
|
||||
|
||||
/* ===============================
|
||||
DEINE LOGIK (unverändert)
|
||||
================================ */
|
||||
|
||||
app.use(companySettingsRoutes);
|
||||
app.use("/", authRoutes);
|
||||
|
||||
/* ===============================
|
||||
DASHBOARD
|
||||
================================ */
|
||||
app.use("/dashboard", dashboardRoutes);
|
||||
|
||||
/* ===============================
|
||||
Mitarbeiter
|
||||
================================ */
|
||||
app.use("/admin", adminRoutes);
|
||||
|
||||
/* ===============================
|
||||
PATIENTEN
|
||||
================================ */
|
||||
app.use("/patients", patientRoutes);
|
||||
app.use("/patients", patientMedicationRoutes);
|
||||
app.use("/patients", patientServiceRoutes);
|
||||
|
||||
app.use("/medications", medicationRoutes);
|
||||
console.log("🧪 /medications Router mounted");
|
||||
|
||||
app.use("/services", serviceRoutes);
|
||||
|
||||
app.use("/", patientFileRoutes);
|
||||
app.use("/", waitingRoomRoutes);
|
||||
app.use("/", invoiceRoutes);
|
||||
|
||||
app.get("/logout", (req, res) => {
|
||||
req.session.destroy(() => res.redirect("/"));
|
||||
});
|
||||
|
||||
/* ===============================
|
||||
ERROR HANDLING
|
||||
MEDIKAMENTENÜBERSICHT
|
||||
================================ */
|
||||
app.use("/medications", medicationRoutes);
|
||||
app.use("/patients", patientMedicationRoutes);
|
||||
|
||||
|
||||
// ===============================
|
||||
// PATIENT INS WARTEZIMMER
|
||||
// ===============================
|
||||
app.use("/", waitingRoomRoutes);
|
||||
|
||||
// ===============================
|
||||
// Leistungen
|
||||
// ===============================
|
||||
app.use("/services", serviceRoutes);
|
||||
app.use("/patients", patientServiceRoutes);
|
||||
|
||||
// ===============================
|
||||
// RECHNUNGEN
|
||||
// ===============================
|
||||
app.use("/", invoiceRoutes);
|
||||
|
||||
/* ===============================
|
||||
LOGOUT
|
||||
================================ */
|
||||
app.get("/logout", (req, res) => {
|
||||
req.session.destroy(() => res.redirect("/"));
|
||||
});
|
||||
|
||||
// ===============================
|
||||
// ERROR HANDLING (IMMER ZUM SCHLUSS)
|
||||
// ===============================
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err);
|
||||
res.status(500).send("Interner Serverfehler");
|
||||
@ -255,8 +112,8 @@ app.use((err, req, res, next) => {
|
||||
/* ===============================
|
||||
SERVER
|
||||
================================ */
|
||||
const PORT = process.env.PORT || 51777;
|
||||
const HOST = "127.0.0.1";
|
||||
const PORT = 51777; // garantiert frei
|
||||
const HOST = "127.0.0.1"; // kein HTTP.sys
|
||||
|
||||
app.listen(PORT, HOST, () => {
|
||||
console.log(`Server läuft auf http://${HOST}:${PORT}`);
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,71 +0,0 @@
|
||||
const fs = require("fs");
|
||||
const crypto = require("crypto");
|
||||
const path = require("path");
|
||||
|
||||
const CONFIG_FILE = path.join(__dirname, "config.enc");
|
||||
|
||||
function getKey() {
|
||||
const key = process.env.CONFIG_KEY;
|
||||
if (!key) throw new Error("CONFIG_KEY fehlt in .env");
|
||||
|
||||
// stabil auf 32 bytes
|
||||
return crypto.createHash("sha256").update(key).digest();
|
||||
}
|
||||
|
||||
function encryptConfig(obj) {
|
||||
const key = getKey();
|
||||
const iv = crypto.randomBytes(12);
|
||||
|
||||
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
||||
const json = JSON.stringify(obj);
|
||||
|
||||
const encrypted = Buffer.concat([
|
||||
cipher.update(json, "utf8"),
|
||||
cipher.final(),
|
||||
]);
|
||||
const tag = cipher.getAuthTag();
|
||||
|
||||
return Buffer.concat([iv, tag, encrypted]).toString("base64");
|
||||
}
|
||||
|
||||
function decryptConfig(str) {
|
||||
const raw = Buffer.from(str, "base64");
|
||||
|
||||
const iv = raw.subarray(0, 12);
|
||||
const tag = raw.subarray(12, 28);
|
||||
const encrypted = raw.subarray(28);
|
||||
|
||||
const key = getKey();
|
||||
|
||||
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
||||
decipher.setAuthTag(tag);
|
||||
|
||||
const decrypted = Buffer.concat([
|
||||
decipher.update(encrypted),
|
||||
decipher.final(),
|
||||
]);
|
||||
return JSON.parse(decrypted.toString("utf8"));
|
||||
}
|
||||
|
||||
function configExists() {
|
||||
return fs.existsSync(CONFIG_FILE);
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
if (!configExists()) return null;
|
||||
const enc = fs.readFileSync(CONFIG_FILE, "utf8").trim();
|
||||
if (!enc) return null;
|
||||
return decryptConfig(enc);
|
||||
}
|
||||
|
||||
function saveConfig(obj) {
|
||||
const enc = encryptConfig(obj);
|
||||
fs.writeFileSync(CONFIG_FILE, enc, "utf8");
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
configExists,
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
G/kDLEJ/LddnnNnginIGYSM4Ax0g5pJaF0lrdOXke51cz3jSTrZxP7rjTXRlqLcoUJhPaVLvjb/DcyNYB/C339a+PFWyIdWYjSb6G4aPkD8J21yFWDDLpc08bXvoAx2PeE+Fc9v5mJUGDVv2wQoDvkHqIpN8ewrfRZ6+JF3OfQ==
|
||||
@ -1,31 +1,6 @@
|
||||
const session = require("express-session");
|
||||
const { configExists } = require("../config-manager");
|
||||
const MySQLStore = require("express-mysql-session")(require("express-session"));
|
||||
const db = require("../db");
|
||||
|
||||
let store = null;
|
||||
const sessionStore = new MySQLStore({}, db);
|
||||
|
||||
function getSessionStore() {
|
||||
if (store) return store;
|
||||
|
||||
// ✅ Setup-Modus (keine DB)
|
||||
if (!configExists()) {
|
||||
console.log("⚠️ Setup-Modus aktiv → SessionStore = MemoryStore");
|
||||
store = new session.MemoryStore();
|
||||
return store;
|
||||
}
|
||||
|
||||
// ✅ Normalbetrieb (mit DB)
|
||||
const MySQLStore = require("express-mysql-session")(session);
|
||||
const db = require("../db");
|
||||
|
||||
store = new MySQLStore({}, db);
|
||||
return store;
|
||||
}
|
||||
|
||||
function resetSessionStore() {
|
||||
store = null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getSessionStore,
|
||||
resetSessionStore,
|
||||
};
|
||||
module.exports = sessionStore;
|
||||
|
||||
@ -1,27 +1,13 @@
|
||||
const db = require("../db");
|
||||
const { createUser, getAllUsers} = require("../services/admin.service");
|
||||
const bcrypt = require("bcrypt");
|
||||
const {
|
||||
createUser,
|
||||
getAllUsers,
|
||||
updateUserById,
|
||||
} = require("../services/admin.service");
|
||||
|
||||
async function listUsers(req, res) {
|
||||
const { q } = req.query;
|
||||
|
||||
try {
|
||||
let users;
|
||||
|
||||
if (q) {
|
||||
users = await getAllUsers(db, q);
|
||||
} else {
|
||||
users = await getAllUsers(db);
|
||||
}
|
||||
|
||||
const users = await getAllUsers(db);
|
||||
res.render("admin_users", {
|
||||
users,
|
||||
currentUser: req.session.user,
|
||||
query: { q },
|
||||
currentUser: req.session.user
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@ -32,75 +18,34 @@ async function listUsers(req, res) {
|
||||
function showCreateUser(req, res) {
|
||||
res.render("admin_create_user", {
|
||||
error: null,
|
||||
user: req.session.user,
|
||||
user: req.session.user
|
||||
});
|
||||
}
|
||||
|
||||
async function postCreateUser(req, res) {
|
||||
let {
|
||||
title,
|
||||
first_name,
|
||||
last_name,
|
||||
username,
|
||||
password,
|
||||
role,
|
||||
fachrichtung,
|
||||
arztnummer,
|
||||
} = req.body;
|
||||
let { username, password, role } = req.body;
|
||||
username = username.trim();
|
||||
|
||||
title = title?.trim();
|
||||
first_name = first_name?.trim();
|
||||
last_name = last_name?.trim();
|
||||
username = username?.trim();
|
||||
fachrichtung = fachrichtung?.trim();
|
||||
arztnummer = arztnummer?.trim();
|
||||
|
||||
// 🔴 Grundvalidierung
|
||||
if (!first_name || !last_name || !username || !password || !role) {
|
||||
if (!username || !password || !role) {
|
||||
return res.render("admin_create_user", {
|
||||
error: "Alle Pflichtfelder müssen ausgefüllt sein",
|
||||
user: req.session.user,
|
||||
error: "Alle Felder sind Pflichtfelder",
|
||||
user: req.session.user
|
||||
});
|
||||
}
|
||||
|
||||
// 🔴 Arzt-spezifische Validierung
|
||||
if (role === "arzt") {
|
||||
if (!fachrichtung || !arztnummer) {
|
||||
return res.render("admin_create_user", {
|
||||
error: "Für Ärzte sind Fachrichtung und Arztnummer Pflicht",
|
||||
user: req.session.user,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Sicherheit: Mitarbeiter dürfen keine Arzt-Daten haben
|
||||
fachrichtung = null;
|
||||
arztnummer = null;
|
||||
title = null;
|
||||
}
|
||||
|
||||
try {
|
||||
await createUser(
|
||||
db,
|
||||
title,
|
||||
first_name,
|
||||
last_name,
|
||||
username,
|
||||
password,
|
||||
role,
|
||||
fachrichtung,
|
||||
arztnummer,
|
||||
);
|
||||
await createUser(db, username, password, role);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -114,21 +59,19 @@ 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",
|
||||
};
|
||||
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");
|
||||
}
|
||||
res.redirect("/admin/users");
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
async function resetUserPassword(req, res) {
|
||||
@ -141,191 +84,27 @@ 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");
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
view, // ✅ WICHTIG: damit EJS weiß welche Tabelle angezeigt wird
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).send("Fehler beim Laden der Rechnungsübersicht");
|
||||
}
|
||||
}
|
||||
|
||||
async function updateUser(req, res) {
|
||||
const userId = req.params.id;
|
||||
|
||||
let { title, first_name, last_name, username, role } = req.body;
|
||||
|
||||
title = title?.trim() || null;
|
||||
first_name = first_name?.trim();
|
||||
last_name = last_name?.trim();
|
||||
username = username?.trim();
|
||||
role = role?.trim();
|
||||
|
||||
try {
|
||||
// ✅ Fehlende Felder aus DB holen (weil disabled inputs nicht gesendet werden)
|
||||
const [rows] = await db
|
||||
.promise()
|
||||
.query("SELECT * FROM users WHERE id = ?", [userId]);
|
||||
|
||||
if (!rows.length) {
|
||||
req.session.flash = { type: "danger", message: "User nicht gefunden" };
|
||||
return res.redirect("/admin/users");
|
||||
}
|
||||
|
||||
const current = rows[0];
|
||||
|
||||
// ✅ Fallback: wenn Felder nicht gesendet wurden -> alte Werte behalten
|
||||
const updatedData = {
|
||||
title: title ?? current.title,
|
||||
first_name: first_name ?? current.first_name,
|
||||
last_name: last_name ?? current.last_name,
|
||||
username: username ?? current.username,
|
||||
role: role ?? current.role,
|
||||
};
|
||||
|
||||
await updateUserById(db, userId, updatedData);
|
||||
|
||||
req.session.flash = { type: "success", message: "User aktualisiert ✅" };
|
||||
return res.redirect("/admin/users");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
req.session.flash = { type: "danger", message: "Fehler beim Speichern" };
|
||||
return res.redirect("/admin/users");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
listUsers,
|
||||
showCreateUser,
|
||||
postCreateUser,
|
||||
changeUserRole,
|
||||
resetUserPassword,
|
||||
activateUser,
|
||||
deactivateUser,
|
||||
showInvoiceOverview,
|
||||
updateUser,
|
||||
resetUserPassword
|
||||
};
|
||||
|
||||
@ -1,162 +0,0 @@
|
||||
const db = require("../db");
|
||||
|
||||
/**
|
||||
* Helper: leere Strings → NULL
|
||||
*/
|
||||
const safe = (v) => {
|
||||
if (typeof v !== "string") return null;
|
||||
const t = v.trim();
|
||||
return t.length > 0 ? t : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* GET: Firmendaten anzeigen
|
||||
*/
|
||||
async function getCompanySettings(req, res) {
|
||||
const [[company]] = await db.promise().query(
|
||||
"SELECT * FROM company_settings LIMIT 1"
|
||||
);
|
||||
|
||||
res.render("admin/company-settings", {
|
||||
user: req.user,
|
||||
company: company || {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* POST: Firmendaten speichern (INSERT oder UPDATE)
|
||||
*/
|
||||
async function saveCompanySettings(req, res) {
|
||||
try {
|
||||
const data = req.body;
|
||||
|
||||
// 🔒 Pflichtfeld
|
||||
if (!data.company_name || data.company_name.trim() === "") {
|
||||
return res.status(400).send("Firmenname darf nicht leer sein");
|
||||
}
|
||||
|
||||
// 🖼 Logo (optional)
|
||||
let logoPath = null;
|
||||
if (req.file) {
|
||||
logoPath = "/images/" + req.file.filename;
|
||||
}
|
||||
|
||||
// 🔍 Existierenden Datensatz laden
|
||||
const [[existing]] = await db.promise().query(
|
||||
"SELECT * FROM company_settings LIMIT 1"
|
||||
);
|
||||
|
||||
const oldData = existing ? { ...existing } : null;
|
||||
|
||||
if (existing) {
|
||||
// 🔁 UPDATE
|
||||
await db.promise().query(
|
||||
`
|
||||
UPDATE company_settings SET
|
||||
company_name = ?,
|
||||
company_legal_form = ?,
|
||||
company_owner = ?,
|
||||
street = ?,
|
||||
house_number = ?,
|
||||
postal_code = ?,
|
||||
city = ?,
|
||||
country = ?,
|
||||
phone = ?,
|
||||
email = ?,
|
||||
vat_id = ?,
|
||||
bank_name = ?,
|
||||
iban = ?,
|
||||
bic = ?,
|
||||
invoice_footer_text = ?,
|
||||
invoice_logo_path = ?
|
||||
WHERE id = ?
|
||||
`,
|
||||
[
|
||||
data.company_name.trim(), // NOT NULL
|
||||
safe(data.company_legal_form),
|
||||
safe(data.company_owner),
|
||||
safe(data.street),
|
||||
safe(data.house_number),
|
||||
safe(data.postal_code),
|
||||
safe(data.city),
|
||||
safe(data.country),
|
||||
safe(data.phone),
|
||||
safe(data.email),
|
||||
safe(data.vat_id),
|
||||
safe(data.bank_name),
|
||||
safe(data.iban),
|
||||
safe(data.bic),
|
||||
safe(data.invoice_footer_text),
|
||||
logoPath || existing.invoice_logo_path,
|
||||
existing.id
|
||||
]
|
||||
);
|
||||
} else {
|
||||
// ➕ INSERT
|
||||
await db.promise().query(
|
||||
`
|
||||
INSERT INTO company_settings (
|
||||
company_name,
|
||||
company_legal_form,
|
||||
company_owner,
|
||||
street,
|
||||
house_number,
|
||||
postal_code,
|
||||
city,
|
||||
country,
|
||||
phone,
|
||||
email,
|
||||
vat_id,
|
||||
bank_name,
|
||||
iban,
|
||||
bic,
|
||||
invoice_footer_text,
|
||||
invoice_logo_path
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
`,
|
||||
[
|
||||
data.company_name.trim(), // NOT NULL
|
||||
safe(data.company_legal_form),
|
||||
safe(data.company_owner),
|
||||
safe(data.street),
|
||||
safe(data.house_number),
|
||||
safe(data.postal_code),
|
||||
safe(data.city),
|
||||
safe(data.country),
|
||||
safe(data.phone),
|
||||
safe(data.email),
|
||||
safe(data.vat_id),
|
||||
safe(data.bank_name),
|
||||
safe(data.iban),
|
||||
safe(data.bic),
|
||||
safe(data.invoice_footer_text),
|
||||
logoPath
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// 📝 Audit-Log
|
||||
await db.promise().query(
|
||||
`
|
||||
INSERT INTO company_settings_logs (changed_by, old_data, new_data)
|
||||
VALUES (?, ?, ?)
|
||||
`,
|
||||
[
|
||||
req.user.id,
|
||||
JSON.stringify(oldData || {}),
|
||||
JSON.stringify(data)
|
||||
]
|
||||
);
|
||||
|
||||
res.redirect("/admin/company-settings");
|
||||
|
||||
} catch (err) {
|
||||
console.error("❌ COMPANY SETTINGS ERROR:", err);
|
||||
res.status(500).send("Fehler beim Speichern der Firmendaten");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getCompanySettings,
|
||||
saveCompanySettings
|
||||
};
|
||||
@ -1,197 +1,108 @@
|
||||
const db = require("../db");
|
||||
const ejs = require("ejs");
|
||||
const path = require("path");
|
||||
const htmlToPdf = require("html-pdf-node");
|
||||
const fs = require("fs");
|
||||
const pdf = require("html-pdf-node");
|
||||
|
||||
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(
|
||||
// 1️⃣ Patient laden
|
||||
const [[patient]] = await db.promise().query(
|
||||
"SELECT * FROM patients WHERE id = ?",
|
||||
[patientId]
|
||||
);
|
||||
if (!patient) throw new Error("Patient nicht gefunden");
|
||||
|
||||
// 🔹 Leistungen
|
||||
const [rows] = await connection.query(
|
||||
`
|
||||
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,
|
||||
s.name_de AS name
|
||||
|
||||
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
|
||||
`,
|
||||
[patientId]
|
||||
);
|
||||
`, [patient.country, patientId]);
|
||||
|
||||
if (!rows.length) throw new Error("Keine Leistungen vorhanden");
|
||||
if (rows.length === 0) {
|
||||
return res.send("Keine Leistungen vorhanden");
|
||||
}
|
||||
|
||||
const services = rows.map((s) => ({
|
||||
const services = rows.map(s => ({
|
||||
quantity: Number(s.quantity),
|
||||
name: s.name,
|
||||
price: Number(s.price),
|
||||
total: Number(s.price) * Number(s.quantity),
|
||||
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
|
||||
// 3️⃣ HTML aus EJS erzeugen
|
||||
const html = await ejs.renderFile(
|
||||
path.join(__dirname, "../views/invoices/invoice.ejs"),
|
||||
{
|
||||
patient,
|
||||
services,
|
||||
total,
|
||||
invoice,
|
||||
doctor,
|
||||
company,
|
||||
logoBase64,
|
||||
}
|
||||
{ patient, services, total }
|
||||
);
|
||||
|
||||
// 🔹 PDF erzeugen
|
||||
const pdfBuffer = await htmlToPdf.generatePdf(
|
||||
// 4️⃣ PDF erzeugen
|
||||
const pdfBuffer = await pdf.generatePdf(
|
||||
{ content: html },
|
||||
{ format: "A4", printBackground: true }
|
||||
{ format: "A4" }
|
||||
);
|
||||
|
||||
// 💾 PDF speichern
|
||||
fs.writeFileSync(absoluteFilePath, pdfBuffer);
|
||||
// 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);
|
||||
|
||||
// 🔗 Leistungen mit Rechnung verknüpfen
|
||||
const [updateResult] = await connection.query(
|
||||
`
|
||||
// 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]
|
||||
);
|
||||
const [[cid]] = await connection.query("SELECT CONNECTION_ID() AS cid");
|
||||
console.log("🔌 INVOICE CID:", cid.cid);
|
||||
await connection.commit();
|
||||
`, [invoiceId, patientId]);
|
||||
|
||||
// 9️⃣ PDF anzeigen
|
||||
res.setHeader("Content-Type", "application/pdf");
|
||||
res.setHeader(
|
||||
"Content-Disposition",
|
||||
`inline; filename="${fileName}"`
|
||||
);
|
||||
|
||||
res.send(pdfBuffer);
|
||||
|
||||
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();
|
||||
console.error("❌ PDF ERROR:", err);
|
||||
res.status(500).send("Fehler beim Erstellen der Rechnung");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,109 +0,0 @@
|
||||
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 };
|
||||
@ -2,50 +2,25 @@ const db = require("../db");
|
||||
|
||||
// 📋 LISTE
|
||||
function listMedications(req, res, next) {
|
||||
const { q, onlyActive } = req.query;
|
||||
|
||||
let sql = `
|
||||
const sql = `
|
||||
SELECT
|
||||
v.id,
|
||||
m.id AS medication_id,
|
||||
m.name AS medication,
|
||||
m.active,
|
||||
f.name AS form,
|
||||
v.dosage,
|
||||
v.package
|
||||
FROM medication_variants v
|
||||
JOIN medications m ON v.medication_id = m.id
|
||||
JOIN medication_forms f ON v.form_id = f.id
|
||||
WHERE 1=1
|
||||
ORDER BY m.name, v.dosage
|
||||
`;
|
||||
|
||||
const params = [];
|
||||
|
||||
if (q) {
|
||||
sql += `
|
||||
AND (
|
||||
m.name LIKE ?
|
||||
OR f.name LIKE ?
|
||||
OR v.dosage LIKE ?
|
||||
OR v.package LIKE ?
|
||||
)
|
||||
`;
|
||||
params.push(`%${q}%`, `%${q}%`, `%${q}%`, `%${q}%`);
|
||||
}
|
||||
|
||||
if (onlyActive === "1") {
|
||||
sql += " AND m.active = 1";
|
||||
}
|
||||
|
||||
sql += " ORDER BY m.name, v.dosage";
|
||||
|
||||
db.query(sql, params, (err, rows) => {
|
||||
db.query(sql, (err, rows) => {
|
||||
if (err) return next(err);
|
||||
|
||||
res.render("medications", {
|
||||
rows,
|
||||
query: { q, onlyActive },
|
||||
user: req.session.user,
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -63,75 +38,16 @@ function updateMedication(req, res, next) {
|
||||
WHERE id = ?
|
||||
`;
|
||||
|
||||
db.query(sql, [dosage, pkg, id], (err) => {
|
||||
db.query(sql, [dosage, pkg, id], err => {
|
||||
if (err) return next(err);
|
||||
|
||||
req.session.flash = { type: "success", message: "Medikament gespeichert" };
|
||||
req.session.flash = { type: "success", message: "Medikament gespeichert"};
|
||||
res.redirect("/medications");
|
||||
});
|
||||
}
|
||||
|
||||
function toggleMedication(req, res, next) {
|
||||
const id = req.params.id;
|
||||
|
||||
db.query(
|
||||
"UPDATE medications SET active = NOT active WHERE id = ?",
|
||||
[id],
|
||||
(err) => {
|
||||
if (err) return next(err);
|
||||
res.redirect("/medications");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function showCreateMedication(req, res) {
|
||||
const sql = "SELECT id, name FROM medication_forms ORDER BY name";
|
||||
|
||||
db.query(sql, (err, forms) => {
|
||||
if (err) return res.send("DB Fehler");
|
||||
|
||||
res.render("medication_create", {
|
||||
forms,
|
||||
user: req.session.user,
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createMedication(req, res) {
|
||||
const { name, form_id, dosage, package: pkg } = req.body;
|
||||
|
||||
if (!name || !form_id || !dosage) {
|
||||
return res.send("Pflichtfelder fehlen");
|
||||
}
|
||||
|
||||
db.query(
|
||||
"INSERT INTO medications (name, active) VALUES (?, 1)",
|
||||
[name],
|
||||
(err, result) => {
|
||||
if (err) return res.send("Fehler Medikament");
|
||||
|
||||
const medicationId = result.insertId;
|
||||
|
||||
db.query(
|
||||
`INSERT INTO medication_variants
|
||||
(medication_id, form_id, dosage, package)
|
||||
VALUES (?, ?, ?, ?)`,
|
||||
[medicationId, form_id, dosage, pkg || null],
|
||||
(err) => {
|
||||
if (err) return res.send("Fehler Variante");
|
||||
|
||||
res.redirect("/medications");
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
listMedications,
|
||||
updateMedication,
|
||||
toggleMedication,
|
||||
showCreateMedication,
|
||||
createMedication,
|
||||
updateMedication
|
||||
};
|
||||
|
||||
|
||||
@ -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,28 +26,15 @@ 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});
|
||||
});
|
||||
}
|
||||
|
||||
@ -56,13 +43,12 @@ 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
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -85,13 +71,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");
|
||||
}
|
||||
@ -126,9 +112,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");
|
||||
@ -188,15 +174,14 @@ 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
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -210,14 +195,13 @@ function moveToWaitingRoom(req, res) {
|
||||
`
|
||||
UPDATE patients
|
||||
SET waiting_room = 1,
|
||||
discharged = 0,
|
||||
active = 1
|
||||
discharged = 0
|
||||
WHERE id = ?
|
||||
`,
|
||||
[id],
|
||||
(err) => {
|
||||
err => {
|
||||
if (err) return res.send("Fehler beim Verschieben ins Wartezimmer");
|
||||
return res.redirect("/dashboard"); // optional: direkt Dashboard
|
||||
res.redirect("/patients");
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -230,7 +214,7 @@ function showWaitingRoom(req, res) {
|
||||
|
||||
res.render("waiting_room", {
|
||||
patients,
|
||||
user: req.session.user,
|
||||
user: req.session.user
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -246,28 +230,21 @@ function showPatientOverview(req, res) {
|
||||
`;
|
||||
|
||||
const notesSql = `
|
||||
SELECT
|
||||
pn.*,
|
||||
u.title,
|
||||
u.first_name,
|
||||
u.last_name
|
||||
FROM patient_notes pn
|
||||
LEFT JOIN users u ON pn.created_by = u.id
|
||||
WHERE pn.patient_id = ?
|
||||
ORDER BY pn.created_at DESC
|
||||
`;
|
||||
SELECT *
|
||||
FROM patient_notes
|
||||
WHERE patient_id = ?
|
||||
ORDER BY created_at DESC
|
||||
`;
|
||||
|
||||
const medicationVariantsSql = `
|
||||
// 🔤 Services dynamisch nach Sprache laden
|
||||
const servicesSql = (nameField) => `
|
||||
SELECT
|
||||
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
|
||||
id,
|
||||
${nameField} AS name,
|
||||
price
|
||||
FROM services
|
||||
WHERE active = 1
|
||||
ORDER BY ${nameField}
|
||||
`;
|
||||
|
||||
db.query(patientSql, [patientId], (err, patients) => {
|
||||
@ -277,22 +254,12 @@ function showPatientOverview(req, res) {
|
||||
|
||||
const patient = patients[0];
|
||||
|
||||
// 🇪🇸 / 🇩🇪 Sprache für Leistungen
|
||||
// 🇪🇸 / 🇩🇪 Sprache bestimmen
|
||||
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,
|
||||
@ -312,23 +279,18 @@ function showPatientOverview(req, res) {
|
||||
db.query(notesSql, [patientId], (err, notes) => {
|
||||
if (err) return res.send("Fehler Notizen");
|
||||
|
||||
db.query(servicesSql, (err, services) => {
|
||||
db.query(servicesSql(serviceNameField), (err, services) => {
|
||||
if (err) return res.send("Fehler Leistungen");
|
||||
|
||||
db.query(todayServicesSql, [patientId], (err, todayServices) => {
|
||||
if (err) return res.send("Fehler heutige Leistungen");
|
||||
|
||||
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,
|
||||
});
|
||||
res.render("patient_overview", {
|
||||
patient,
|
||||
notes,
|
||||
services,
|
||||
todayServices,
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -336,47 +298,6 @@ 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;
|
||||
@ -387,10 +308,9 @@ function addPatientNote(req, res) {
|
||||
}
|
||||
|
||||
db.query(
|
||||
"INSERT INTO patient_notes (patient_id, created_by, note) VALUES (?, ?, ?)",
|
||||
[patientId, req.session.user.id, note],
|
||||
|
||||
(err) => {
|
||||
"INSERT INTO patient_notes (patient_id, note) VALUES (?, ?)",
|
||||
[patientId, note],
|
||||
err => {
|
||||
if (err) return res.send("Fehler beim Speichern der Notiz");
|
||||
res.redirect(`/patients/${patientId}/overview`);
|
||||
}
|
||||
@ -403,7 +323,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`);
|
||||
}
|
||||
@ -414,21 +334,11 @@ function dischargePatient(req, res) {
|
||||
const patientId = req.params.id;
|
||||
|
||||
db.query(
|
||||
`
|
||||
UPDATE patients
|
||||
SET discharged = 1,
|
||||
waiting_room = 0,
|
||||
active = 0
|
||||
WHERE id = ?
|
||||
`,
|
||||
"UPDATE patients SET discharged = 1 WHERE id = ?",
|
||||
[patientId],
|
||||
(err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.send("Fehler beim Entlassen des Patienten");
|
||||
}
|
||||
|
||||
return res.redirect("/dashboard");
|
||||
err => {
|
||||
if (err) return res.send("Fehler beim Entlassen des Patienten");
|
||||
res.redirect("/waiting-room");
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -465,7 +375,7 @@ function showMedicationPlan(req, res) {
|
||||
|
||||
res.render("patient_plan", {
|
||||
patient: patients[0],
|
||||
meds,
|
||||
meds
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -478,28 +388,26 @@ function movePatientToWaitingRoom(req, res) {
|
||||
`
|
||||
UPDATE patients
|
||||
SET waiting_room = 1,
|
||||
discharged = 0,
|
||||
status = 'waiting',
|
||||
active = 1
|
||||
discharged = 0
|
||||
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"
|
||||
};
|
||||
|
||||
return res.redirect("/dashboard");
|
||||
res.redirect("/waiting-room");
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -507,107 +415,55 @@ 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);
|
||||
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");
|
||||
}
|
||||
|
||||
req.session.flash = {
|
||||
type: "danger",
|
||||
message: "Patient konnte nicht gesperrt werden",
|
||||
type: "success",
|
||||
message: "Patient wurde gesperrt"
|
||||
};
|
||||
return res.redirect("/patients");
|
||||
|
||||
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);
|
||||
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");
|
||||
}
|
||||
|
||||
req.session.flash = {
|
||||
type: "danger",
|
||||
message: "Patient konnte nicht entsperrt werden",
|
||||
type: "success",
|
||||
message: "Patient wurde entsperrt"
|
||||
};
|
||||
return res.redirect("/patients");
|
||||
|
||||
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,
|
||||
@ -625,7 +481,5 @@ module.exports = {
|
||||
showMedicationPlan,
|
||||
movePatientToWaitingRoom,
|
||||
deactivatePatient,
|
||||
activatePatient,
|
||||
showPatientOverviewDashborad,
|
||||
assignMedicationToPatient,
|
||||
activatePatient
|
||||
};
|
||||
|
||||
@ -27,15 +27,17 @@ function addPatientService(req, res) {
|
||||
const price = results[0].price;
|
||||
|
||||
db.query(
|
||||
`INSERT INTO patient_services
|
||||
`
|
||||
INSERT INTO patient_services
|
||||
(patient_id, service_id, quantity, price, service_date, created_by)
|
||||
VALUES (?, ?, ?, ?, CURDATE(), ?) `,
|
||||
VALUES (?, ?, ?, ?, CURDATE(), ?)
|
||||
`,
|
||||
[
|
||||
patientId,
|
||||
service_id,
|
||||
quantity || 1,
|
||||
price,
|
||||
req.session.user.id // behandelnder Arzt
|
||||
patientId,
|
||||
service_id,
|
||||
quantity || 1,
|
||||
price,
|
||||
req.session.user.id
|
||||
],
|
||||
err => {
|
||||
if (err) {
|
||||
@ -79,24 +81,8 @@ function updatePatientServicePrice(req, res) {
|
||||
);
|
||||
}
|
||||
|
||||
function updatePatientServiceQuantity(req, res) {
|
||||
const id = req.params.id;
|
||||
const { quantity } = req.body;
|
||||
|
||||
if (!quantity || quantity < 1) {
|
||||
return res.redirect("/services/open");
|
||||
}
|
||||
|
||||
db.query(
|
||||
"UPDATE patient_services SET quantity = ? WHERE id = ?",
|
||||
[quantity, id],
|
||||
() => res.redirect("/services/open")
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addPatientService,
|
||||
deletePatientService,
|
||||
updatePatientServicePrice,
|
||||
updatePatientServiceQuantity
|
||||
updatePatientServicePrice
|
||||
};
|
||||
|
||||
@ -222,11 +222,7 @@ function toggleService(req, res) {
|
||||
);
|
||||
}
|
||||
|
||||
async function listOpenServices(req, res, next) {
|
||||
res.set("Cache-Control", "no-store, no-cache, must-revalidate, private");
|
||||
res.set("Pragma", "no-cache");
|
||||
res.set("Expires", "0");
|
||||
|
||||
function listOpenServices(req, res, next) {
|
||||
const sql = `
|
||||
SELECT
|
||||
p.id AS patient_id,
|
||||
@ -236,48 +232,32 @@ async function listOpenServices(req, res, next) {
|
||||
ps.id AS patient_service_id,
|
||||
ps.quantity,
|
||||
COALESCE(ps.price_override, s.price) AS price,
|
||||
|
||||
-- 🌍 Sprachabhängiger Servicename
|
||||
CASE
|
||||
WHEN UPPER(TRIM(p.country)) = 'ES'
|
||||
THEN COALESCE(NULLIF(s.name_es, ''), s.name_de)
|
||||
ELSE s.name_de
|
||||
WHEN UPPER(TRIM(p.country)) = 'ES'
|
||||
THEN COALESCE(NULLIF(s.name_es, ''), s.name_de)
|
||||
ELSE s.name_de
|
||||
END AS name
|
||||
|
||||
FROM patient_services ps
|
||||
JOIN patients p ON ps.patient_id = p.id
|
||||
JOIN services s ON ps.service_id = s.id
|
||||
WHERE ps.invoice_id IS NULL
|
||||
ORDER BY p.lastname, p.firstname, name
|
||||
ORDER BY
|
||||
p.lastname,
|
||||
p.firstname,
|
||||
name
|
||||
`;
|
||||
|
||||
let connection;
|
||||
|
||||
try {
|
||||
// 🔌 EXAKT EINE Connection holen
|
||||
connection = await db.promise().getConnection();
|
||||
|
||||
// 🔒 Isolation Level für DIESE Connection
|
||||
await connection.query(
|
||||
"SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"
|
||||
);
|
||||
|
||||
const [[cid]] = await connection.query(
|
||||
"SELECT CONNECTION_ID() AS cid"
|
||||
);
|
||||
console.log("🔌 OPEN SERVICES CID:", cid.cid);
|
||||
|
||||
const [rows] = await connection.query(sql);
|
||||
|
||||
console.log("🧾 OPEN SERVICES ROWS:", rows.length);
|
||||
db.query(sql, (err, rows) => {
|
||||
if (err) return next(err);
|
||||
|
||||
res.render("open_services", {
|
||||
rows,
|
||||
user: req.session.user
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
next(err);
|
||||
} finally {
|
||||
if (connection) connection.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
69
db.js
69
db.js
@ -1,62 +1,15 @@
|
||||
const mysql = require("mysql2");
|
||||
const { loadConfig } = require("./config-manager");
|
||||
|
||||
let pool = null;
|
||||
const db = mysql.createConnection({
|
||||
host: "85.215.63.122",
|
||||
user: "praxisuser",
|
||||
password: "praxisuser",
|
||||
database: "praxissoftware"
|
||||
});
|
||||
|
||||
function initPool() {
|
||||
const config = loadConfig();
|
||||
db.connect(err => {
|
||||
if (err) throw err;
|
||||
console.log("MySQL verbunden");
|
||||
});
|
||||
|
||||
// ✅ Setup-Modus: noch keine config.enc → kein Pool
|
||||
if (!config || !config.db) return null;
|
||||
|
||||
return mysql.createPool({
|
||||
host: config.db.host,
|
||||
user: config.db.user,
|
||||
password: config.db.password,
|
||||
database: config.db.name,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
});
|
||||
}
|
||||
|
||||
function getPool() {
|
||||
if (!pool) pool = initPool();
|
||||
return pool;
|
||||
}
|
||||
|
||||
function resetPool() {
|
||||
pool = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ Proxy damit alter Code weitergeht:
|
||||
* const db = require("../db");
|
||||
* await db.query(...)
|
||||
*/
|
||||
const dbProxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(target, prop) {
|
||||
const p = getPool();
|
||||
|
||||
if (!p) {
|
||||
throw new Error(
|
||||
"❌ DB ist noch nicht konfiguriert (config.enc fehlt). Bitte zuerst Setup ausführen: http://127.0.0.1:51777/setup",
|
||||
);
|
||||
}
|
||||
|
||||
const value = p[prop];
|
||||
|
||||
if (typeof value === "function") {
|
||||
return value.bind(p);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = dbProxy;
|
||||
module.exports.getPool = getPool;
|
||||
module.exports.resetPool = resetPool;
|
||||
module.exports = db;
|
||||
|
||||
@ -1,208 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin: 30px 0 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
page-break-inside: auto;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #333;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.no-border td {
|
||||
border: none;
|
||||
padding: 4px 2px;
|
||||
}
|
||||
|
||||
.total {
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 30px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
tr {
|
||||
page-break-inside: avoid;
|
||||
page-break-after: auto;
|
||||
}
|
||||
|
||||
.page-break {
|
||||
page-break-before: always;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
|
||||
<!-- LOGO -->
|
||||
<div>
|
||||
<!-- HIER LOGO EINBINDEN -->
|
||||
<!-- <img src="file:///ABSOLUTER/PFAD/logo.png" class="logo"> -->
|
||||
</div>
|
||||
|
||||
<!-- ADRESSE -->
|
||||
<div>
|
||||
<strong>MedCenter Tenerife S.L.</strong><br>
|
||||
C.I.F. B76766302<br><br>
|
||||
|
||||
Praxis El Médano<br>
|
||||
Calle Teobaldo Power 5<br>
|
||||
38612 El Médano<br>
|
||||
Fon: 922 157 527 / 657 497 996<br><br>
|
||||
|
||||
Praxis Los Cristianos<br>
|
||||
Avenida de Suecia 10<br>
|
||||
38650 Los Cristianos<br>
|
||||
Fon: 922 157 527 / 654 520 717
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<h1>RECHNUNG / FACTURA</h1>
|
||||
|
||||
<!-- RECHNUNGSDATEN -->
|
||||
<table class="no-border">
|
||||
<tr>
|
||||
<td><strong>Factura número</strong></td>
|
||||
<td>—</td>
|
||||
<td><strong>Fecha</strong></td>
|
||||
<td>7.1.2026</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Rechnungsnummer</strong></td>
|
||||
<td>—</td>
|
||||
<td><strong>Datum</strong></td>
|
||||
<td>7.1.2026</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>N.I.E. / DNI</strong></td>
|
||||
<td></td>
|
||||
<td><strong>Geburtsdatum</strong></td>
|
||||
<td>
|
||||
9.11.1968
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
|
||||
<!-- PATIENT -->
|
||||
<strong>Patient:</strong><br>
|
||||
Cay Joksch<br>
|
||||
Calle la Fuente 24<br>
|
||||
38628 San Miguel de Abina
|
||||
|
||||
<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>
|
||||
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>1 x Ampulle Benerva 100 mg (Vitamin B1)</td>
|
||||
<td>3.00</td>
|
||||
<td>3.00</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="total">
|
||||
T O T A L: 3.00 €
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="page-break"></div>
|
||||
<!-- ARZT -->
|
||||
<strong>Behandelnder Arzt / Médico tratante:</strong><br>
|
||||
Cay Joksch<br>
|
||||
|
||||
|
||||
<strong>Fachrichtung / Especialidad:</strong>
|
||||
Homoopath<br>
|
||||
|
||||
|
||||
|
||||
<strong>Arztnummer / Nº colegiado:</strong>
|
||||
6514.651.651.<br>
|
||||
|
||||
|
||||
<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>
|
||||
</html>
|
||||
BIN
libmysql.dll
BIN
libmysql.dll
Binary file not shown.
@ -1,40 +0,0 @@
|
||||
{
|
||||
"global": {
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"search": "Suchen",
|
||||
"reset": "Reset",
|
||||
"dashboard": "Dashboard",
|
||||
"year": "Jahr",
|
||||
"month": "Monat"
|
||||
},
|
||||
"sidebar": {
|
||||
"patients": "Patienten",
|
||||
"medications": "Medikamente",
|
||||
"servicesOpen": "Offene Leistungen",
|
||||
"billing": "Abrechnung",
|
||||
"admin": "Verwaltung",
|
||||
"logout": "Logout"
|
||||
},
|
||||
"dashboard": {
|
||||
"welcome": "Willkommen",
|
||||
"waitingRoom": "Wartezimmer-Monitor",
|
||||
"noWaitingPatients": "Keine Patienten im Wartezimmer."
|
||||
},
|
||||
"adminSidebar": {
|
||||
"users": "Userverwaltung",
|
||||
"database": "Datenbankverwaltung"
|
||||
},
|
||||
"adminInvoice": {
|
||||
"annualSales": "Jahresumsatz",
|
||||
"quarterlySales": "Quartalsumsatz.",
|
||||
"monthSales": "Monatsumsatz",
|
||||
"patientsSales": "Umsatz pro Patient",
|
||||
"doctorSales": "Umsatz pro Arzt",
|
||||
"filter": "Filtern",
|
||||
"invoiceOverview": "Rechnungsübersicht",
|
||||
"search": "Suchen",
|
||||
"patient": "Patient",
|
||||
"searchPatient": "Patienten suchen"
|
||||
}
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
{
|
||||
"global": {
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"search": "Buscar",
|
||||
"reset": "Resetear",
|
||||
"dashboard": "Panel",
|
||||
"year": "Ano",
|
||||
"month": "mes"
|
||||
},
|
||||
"sidebar": {
|
||||
"patients": "Pacientes",
|
||||
"medications": "Medicamentos",
|
||||
"servicesOpen": "Servicios abiertos",
|
||||
"billing": "Facturación",
|
||||
"admin": "Administración",
|
||||
"logout": "Cerrar sesión"
|
||||
},
|
||||
"dashboard": {
|
||||
"welcome": "Bienvenido",
|
||||
"waitingRoom": "Monitor sala de espera",
|
||||
"noWaitingPatients": "No hay pacientes en la sala de espera."
|
||||
},
|
||||
|
||||
"adminSidebar": {
|
||||
"users": "Administración de usuarios",
|
||||
"database": "Administración de base de datos"
|
||||
},
|
||||
"adminInvoice": {
|
||||
"annualSales": "facturación anual",
|
||||
"quarterlySales": "ingresos trimestrales.",
|
||||
"monthSales": "facturación mensual",
|
||||
"patientsSales": "Ingresos por paciente",
|
||||
"doctorSales": "Facturación por médico",
|
||||
"filter": "filtro",
|
||||
"invoiceOverview": "Resumen de facturas",
|
||||
"search": "buscar",
|
||||
"patient": "paciente",
|
||||
"searchPatient": "Buscar pacientes"
|
||||
}
|
||||
}
|
||||
@ -2,53 +2,25 @@ function requireLogin(req, res, next) {
|
||||
if (!req.session.user) {
|
||||
return res.redirect("/");
|
||||
}
|
||||
|
||||
req.user = req.session.user;
|
||||
next();
|
||||
}
|
||||
|
||||
// ✅ NEU: Arzt-only (das war früher dein requireAdmin)
|
||||
function requireArzt(req, res, next) {
|
||||
console.log("ARZT CHECK:", req.session.user);
|
||||
|
||||
if (!req.session.user) {
|
||||
return res.redirect("/");
|
||||
}
|
||||
|
||||
if (req.session.user.role !== "arzt") {
|
||||
return res
|
||||
.status(403)
|
||||
.send(
|
||||
"⛔ Kein Zugriff (Arzt erforderlich). Rolle: " + req.session.user.role,
|
||||
);
|
||||
}
|
||||
|
||||
req.user = req.session.user;
|
||||
next();
|
||||
}
|
||||
|
||||
// ✅ NEU: Admin-only
|
||||
function requireAdmin(req, res, next) {
|
||||
console.log("ADMIN CHECK:", req.session.user);
|
||||
|
||||
if (!req.session.user) {
|
||||
return res.redirect("/");
|
||||
return res.send("NICHT EINGELOGGT");
|
||||
}
|
||||
|
||||
if (req.session.user.role !== "admin") {
|
||||
return res
|
||||
.status(403)
|
||||
.send(
|
||||
"⛔ Kein Zugriff (Admin erforderlich). Rolle: " + req.session.user.role,
|
||||
);
|
||||
if (req.session.user.role !== "arzt") {
|
||||
return res.send("KEIN ARZT: " + req.session.user.role);
|
||||
}
|
||||
|
||||
req.user = req.session.user;
|
||||
next();
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
requireLogin,
|
||||
requireArzt,
|
||||
requireAdmin,
|
||||
requireAdmin
|
||||
};
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
const multer = require("multer");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
// 🔑 Zielordner: public/images
|
||||
const uploadDir = path.join(__dirname, "../public/images");
|
||||
|
||||
// Ordner sicherstellen
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, uploadDir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
// immer gleicher Name
|
||||
cb(null, "logo" + path.extname(file.originalname));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = multer({ storage });
|
||||
|
||||
BIN
mysqldump.exe
BIN
mysqldump.exe
Binary file not shown.
274
package-lock.json
generated
274
package-lock.json
generated
@ -1559,6 +1559,18 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
||||
@ -2665,6 +2677,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/devtools-protocol": {
|
||||
"version": "0.0.901419",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.901419.tgz",
|
||||
"integrity": "sha512-4INMPwNm9XRpBukhNbF7OB6fNTTCaI8pzy/fXg0xQzAy5h3zL1P8xT3QazgKqBrb/hAYwIBizqDBZ7GtJE74QQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
@ -3724,143 +3742,6 @@
|
||||
"puppeteer": "^10.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-pdf-node/node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-pdf-node/node_modules/debug": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
|
||||
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/html-pdf-node/node_modules/devtools-protocol": {
|
||||
"version": "0.0.901419",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.901419.tgz",
|
||||
"integrity": "sha512-4INMPwNm9XRpBukhNbF7OB6fNTTCaI8pzy/fXg0xQzAy5h3zL1P8xT3QazgKqBrb/hAYwIBizqDBZ7GtJE74QQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/html-pdf-node/node_modules/https-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/html-pdf-node/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/html-pdf-node/node_modules/progress": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz",
|
||||
"integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-pdf-node/node_modules/puppeteer": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-10.4.0.tgz",
|
||||
"integrity": "sha512-2cP8mBoqnu5gzAVpbZ0fRaobBWZM8GEUF4I1F6WbgHrKV/rz7SX8PG2wMymZgD0wo0UBlg2FBPNxlF/xlqW6+w==",
|
||||
"deprecated": "< 24.15.0 is no longer supported",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"debug": "4.3.1",
|
||||
"devtools-protocol": "0.0.901419",
|
||||
"extract-zip": "2.0.1",
|
||||
"https-proxy-agent": "5.0.0",
|
||||
"node-fetch": "2.6.1",
|
||||
"pkg-dir": "4.2.0",
|
||||
"progress": "2.0.1",
|
||||
"proxy-from-env": "1.1.0",
|
||||
"rimraf": "3.0.2",
|
||||
"tar-fs": "2.0.0",
|
||||
"unbzip2-stream": "1.3.3",
|
||||
"ws": "7.4.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/html-pdf-node/node_modules/tar-fs": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz",
|
||||
"integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-pdf-node/node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/html-pdf-node/node_modules/ws": {
|
||||
"version": "7.4.6",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
||||
@ -3895,6 +3776,19 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||
@ -5755,6 +5649,15 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/progress": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz",
|
||||
"integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
@ -5791,6 +5694,54 @@
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-10.4.0.tgz",
|
||||
"integrity": "sha512-2cP8mBoqnu5gzAVpbZ0fRaobBWZM8GEUF4I1F6WbgHrKV/rz7SX8PG2wMymZgD0wo0UBlg2FBPNxlF/xlqW6+w==",
|
||||
"deprecated": "< 24.15.0 is no longer supported",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"debug": "4.3.1",
|
||||
"devtools-protocol": "0.0.901419",
|
||||
"extract-zip": "2.0.1",
|
||||
"https-proxy-agent": "5.0.0",
|
||||
"node-fetch": "2.6.1",
|
||||
"pkg-dir": "4.2.0",
|
||||
"progress": "2.0.1",
|
||||
"proxy-from-env": "1.1.0",
|
||||
"rimraf": "3.0.2",
|
||||
"tar-fs": "2.0.0",
|
||||
"unbzip2-stream": "1.3.3",
|
||||
"ws": "7.4.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/debug": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
|
||||
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pure-rand": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
|
||||
@ -6606,6 +6557,34 @@
|
||||
"url": "https://opencollective.com/synckit"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz",
|
||||
"integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
|
||||
@ -7099,6 +7078,27 @@
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.4.6",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xlsx": {
|
||||
"version": "0.18.5",
|
||||
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user