Compare commits
10 Commits
7ab67a839b
...
860b41ab28
| Author | SHA1 | Date | |
|---|---|---|---|
| 860b41ab28 | |||
| 10e83f53da | |||
| 81473882e5 | |||
| 1aef5a5749 | |||
| 9b516a48c3 | |||
| 961c024252 | |||
| 8754c22dc4 | |||
| d41b0d22d1 | |||
| 798ba92b8a | |||
| 491bd2db7d |
Binary file not shown.
Binary file not shown.
261
app.js
261
app.js
@ -1,109 +1,252 @@
|
|||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
const session = require("express-session");
|
const session = require("express-session");
|
||||||
const bcrypt = require("bcrypt");
|
const helmet = require("helmet");
|
||||||
const db = require("./db");
|
const mysql = require("mysql2/promise");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { requireLogin, requireAdmin} = require("./middleware/auth.middleware");
|
|
||||||
|
// ✅ 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 adminRoutes = require("./routes/admin.routes");
|
const adminRoutes = require("./routes/admin.routes");
|
||||||
const dashboardRoutes = require("./routes/dashboard.routes");
|
const dashboardRoutes = require("./routes/dashboard.routes");
|
||||||
const helmet = require("helmet");
|
|
||||||
const sessionStore = require("./config/session");
|
|
||||||
const patientRoutes = require("./routes/patient.routes");
|
const patientRoutes = require("./routes/patient.routes");
|
||||||
const medicationRoutes = require("./routes/medication.routes");
|
const medicationRoutes = require("./routes/medications.routes");
|
||||||
const patientMedicationRoutes = require("./routes/patientMedication.routes");
|
const patientMedicationRoutes = require("./routes/patientMedication.routes");
|
||||||
const waitingRoomRoutes = require("./routes/waitingRoom.routes");
|
const waitingRoomRoutes = require("./routes/waitingRoom.routes");
|
||||||
const serviceRoutes = require("./routes/service.routes");
|
const serviceRoutes = require("./routes/service.routes");
|
||||||
const patientServiceRoutes = require("./routes/patientService.routes");
|
const patientServiceRoutes = require("./routes/patientService.routes");
|
||||||
const invoiceRoutes = require("./routes/invoice.routes");
|
const invoiceRoutes = require("./routes/invoice.routes");
|
||||||
const patientFileRoutes = require("./routes/patientFile.routes");
|
const patientFileRoutes = require("./routes/patientFile.routes");
|
||||||
|
const companySettingsRoutes = require("./routes/companySettings.routes");
|
||||||
require("dotenv").config();
|
const authRoutes = require("./routes/auth.routes");
|
||||||
|
|
||||||
const app = express();
|
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
|
MIDDLEWARE
|
||||||
================================ */
|
================================ */
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
app.use(express.json());
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
|
|
||||||
app.use(session({
|
// ✅ SessionStore dynamisch (Setup: MemoryStore, normal: MySQLStore)
|
||||||
|
app.use(
|
||||||
|
session({
|
||||||
name: "praxis.sid",
|
name: "praxis.sid",
|
||||||
secret: process.env.SESSION_SECRET,
|
secret: process.env.SESSION_SECRET,
|
||||||
store: sessionStore,
|
store: getSessionStore(),
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: 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();
|
||||||
|
});
|
||||||
|
|
||||||
const flashMiddleware = require("./middleware/flash.middleware");
|
const flashMiddleware = require("./middleware/flash.middleware");
|
||||||
app.use(flashMiddleware);
|
app.use(flashMiddleware);
|
||||||
|
|
||||||
app.use(express.static("public"));
|
app.use(express.static("public"));
|
||||||
|
|
||||||
app.set("view engine", "ejs");
|
|
||||||
|
|
||||||
app.use("/patients", require("./routes/patient.routes"));
|
|
||||||
|
|
||||||
app.use("/uploads", express.static("uploads"));
|
app.use("/uploads", express.static("uploads"));
|
||||||
|
app.set("view engine", "ejs");
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
res.locals.user = req.session.user || null;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
/* ===============================
|
/* ===============================
|
||||||
LOGIN
|
SETUP ROUTES
|
||||||
================================ */
|
================================ */
|
||||||
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);
|
app.use("/", authRoutes);
|
||||||
|
|
||||||
/* ===============================
|
|
||||||
DASHBOARD
|
|
||||||
================================ */
|
|
||||||
app.use("/dashboard", dashboardRoutes);
|
app.use("/dashboard", dashboardRoutes);
|
||||||
|
|
||||||
/* ===============================
|
|
||||||
Mitarbeiter
|
|
||||||
================================ */
|
|
||||||
app.use("/admin", adminRoutes);
|
app.use("/admin", adminRoutes);
|
||||||
|
|
||||||
/* ===============================
|
|
||||||
PATIENTEN
|
|
||||||
================================ */
|
|
||||||
app.use("/patients", patientRoutes);
|
app.use("/patients", patientRoutes);
|
||||||
app.use("/", patientFileRoutes);
|
|
||||||
|
|
||||||
/* ===============================
|
|
||||||
MEDIKAMENTENÜBERSICHT
|
|
||||||
================================ */
|
|
||||||
app.use("/medications", medicationRoutes);
|
|
||||||
app.use("/patients", patientMedicationRoutes);
|
app.use("/patients", patientMedicationRoutes);
|
||||||
|
|
||||||
|
|
||||||
// ===============================
|
|
||||||
// PATIENT INS WARTEZIMMER
|
|
||||||
// ===============================
|
|
||||||
app.use("/", waitingRoomRoutes);
|
|
||||||
|
|
||||||
// ===============================
|
|
||||||
// Leistungen
|
|
||||||
// ===============================
|
|
||||||
app.use("/services", serviceRoutes);
|
|
||||||
app.use("/patients", patientServiceRoutes);
|
app.use("/patients", patientServiceRoutes);
|
||||||
|
|
||||||
// ===============================
|
app.use("/medications", medicationRoutes);
|
||||||
// RECHNUNGEN
|
console.log("🧪 /medications Router mounted");
|
||||||
// ===============================
|
|
||||||
|
app.use("/services", serviceRoutes);
|
||||||
|
|
||||||
|
app.use("/", patientFileRoutes);
|
||||||
|
app.use("/", waitingRoomRoutes);
|
||||||
app.use("/", invoiceRoutes);
|
app.use("/", invoiceRoutes);
|
||||||
|
|
||||||
/* ===============================
|
|
||||||
LOGOUT
|
|
||||||
================================ */
|
|
||||||
app.get("/logout", (req, res) => {
|
app.get("/logout", (req, res) => {
|
||||||
req.session.destroy(() => res.redirect("/"));
|
req.session.destroy(() => res.redirect("/"));
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===============================
|
/* ===============================
|
||||||
// ERROR HANDLING (IMMER ZUM SCHLUSS)
|
ERROR HANDLING
|
||||||
// ===============================
|
================================ */
|
||||||
app.use((err, req, res, next) => {
|
app.use((err, req, res, next) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
res.status(500).send("Interner Serverfehler");
|
res.status(500).send("Interner Serverfehler");
|
||||||
@ -112,8 +255,8 @@ app.use((err, req, res, next) => {
|
|||||||
/* ===============================
|
/* ===============================
|
||||||
SERVER
|
SERVER
|
||||||
================================ */
|
================================ */
|
||||||
const PORT = 51777; // garantiert frei
|
const PORT = process.env.PORT || 51777;
|
||||||
const HOST = "127.0.0.1"; // kein HTTP.sys
|
const HOST = "127.0.0.1";
|
||||||
|
|
||||||
app.listen(PORT, HOST, () => {
|
app.listen(PORT, HOST, () => {
|
||||||
console.log(`Server läuft auf http://${HOST}:${PORT}`);
|
console.log(`Server läuft auf http://${HOST}:${PORT}`);
|
||||||
|
|||||||
0
backups/praxissoftware_2026-01-18_13-36-29.sql
Normal file
0
backups/praxissoftware_2026-01-18_13-36-29.sql
Normal file
0
backups/praxissoftware_2026-01-18_13-40-33.sql
Normal file
0
backups/praxissoftware_2026-01-18_13-40-33.sql
Normal file
0
backups/praxissoftware_2026-01-18_13-45-35.sql
Normal file
0
backups/praxissoftware_2026-01-18_13-45-35.sql
Normal file
610
backups/praxissoftware_2026-01-18_13-53-09.sql
Normal file
610
backups/praxissoftware_2026-01-18_13-53-09.sql
Normal file
File diff suppressed because one or more lines are too long
610
backups/praxissoftware_2026-01-18_13-58-30.sql
Normal file
610
backups/praxissoftware_2026-01-18_13-58-30.sql
Normal file
File diff suppressed because one or more lines are too long
610
backups/praxissoftware_2026-01-18_13-59-02.sql
Normal file
610
backups/praxissoftware_2026-01-18_13-59-02.sql
Normal file
File diff suppressed because one or more lines are too long
610
backups/praxissoftware_2026-01-18_14-06-52.sql
Normal file
610
backups/praxissoftware_2026-01-18_14-06-52.sql
Normal file
File diff suppressed because one or more lines are too long
71
config-manager.js
Normal file
71
config-manager.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
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
config.enc
Normal file
1
config.enc
Normal file
@ -0,0 +1 @@
|
|||||||
|
G/kDLEJ/LddnnNnginIGYSM4Ax0g5pJaF0lrdOXke51cz3jSTrZxP7rjTXRlqLcoUJhPaVLvjb/DcyNYB/C339a+PFWyIdWYjSb6G4aPkD8J21yFWDDLpc08bXvoAx2PeE+Fc9v5mJUGDVv2wQoDvkHqIpN8ewrfRZ6+JF3OfQ==
|
||||||
@ -1,6 +1,31 @@
|
|||||||
const MySQLStore = require("express-mysql-session")(require("express-session"));
|
const session = require("express-session");
|
||||||
const db = require("../db");
|
const { configExists } = require("../config-manager");
|
||||||
|
|
||||||
const sessionStore = new MySQLStore({}, db);
|
let store = null;
|
||||||
|
|
||||||
module.exports = sessionStore;
|
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,
|
||||||
|
};
|
||||||
|
|||||||
@ -1,13 +1,27 @@
|
|||||||
const db = require("../db");
|
const db = require("../db");
|
||||||
const { createUser, getAllUsers} = require("../services/admin.service");
|
|
||||||
const bcrypt = require("bcrypt");
|
const bcrypt = require("bcrypt");
|
||||||
|
const {
|
||||||
|
createUser,
|
||||||
|
getAllUsers,
|
||||||
|
updateUserById,
|
||||||
|
} = require("../services/admin.service");
|
||||||
|
|
||||||
async function listUsers(req, res) {
|
async function listUsers(req, res) {
|
||||||
|
const { q } = req.query;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const users = await getAllUsers(db);
|
let users;
|
||||||
|
|
||||||
|
if (q) {
|
||||||
|
users = await getAllUsers(db, q);
|
||||||
|
} else {
|
||||||
|
users = await getAllUsers(db);
|
||||||
|
}
|
||||||
|
|
||||||
res.render("admin_users", {
|
res.render("admin_users", {
|
||||||
users,
|
users,
|
||||||
currentUser: req.session.user
|
currentUser: req.session.user,
|
||||||
|
query: { q },
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -18,34 +32,75 @@ async function listUsers(req, res) {
|
|||||||
function showCreateUser(req, res) {
|
function showCreateUser(req, res) {
|
||||||
res.render("admin_create_user", {
|
res.render("admin_create_user", {
|
||||||
error: null,
|
error: null,
|
||||||
user: req.session.user
|
user: req.session.user,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function postCreateUser(req, res) {
|
async function postCreateUser(req, res) {
|
||||||
let { username, password, role } = req.body;
|
let {
|
||||||
username = username.trim();
|
title,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
role,
|
||||||
|
fachrichtung,
|
||||||
|
arztnummer,
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
if (!username || !password || !role) {
|
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) {
|
||||||
return res.render("admin_create_user", {
|
return res.render("admin_create_user", {
|
||||||
error: "Alle Felder sind Pflichtfelder",
|
error: "Alle Pflichtfelder müssen ausgefüllt sein",
|
||||||
user: req.session.user
|
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 {
|
try {
|
||||||
await createUser(db, username, password, role);
|
await createUser(
|
||||||
|
db,
|
||||||
|
title,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
role,
|
||||||
|
fachrichtung,
|
||||||
|
arztnummer,
|
||||||
|
);
|
||||||
|
|
||||||
req.session.flash = {
|
req.session.flash = {
|
||||||
type: "success",
|
type: "success",
|
||||||
message: "Benutzer erfolgreich angelegt"
|
message: "Benutzer erfolgreich angelegt",
|
||||||
};
|
};
|
||||||
|
|
||||||
res.redirect("/admin/users");
|
res.redirect("/admin/users");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.render("admin_create_user", {
|
res.render("admin_create_user", {
|
||||||
error,
|
error,
|
||||||
user: req.session.user
|
user: req.session.user,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,19 +114,21 @@ async function changeUserRole(req, res) {
|
|||||||
return res.redirect("/admin/users");
|
return res.redirect("/admin/users");
|
||||||
}
|
}
|
||||||
|
|
||||||
db.query(
|
db.query("UPDATE users SET role = ? WHERE id = ?", [role, userId], (err) => {
|
||||||
"UPDATE users SET role = ? WHERE id = ?",
|
|
||||||
[role, userId],
|
|
||||||
err => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
req.session.flash = { type: "danger", message: "Fehler beim Ändern der Rolle" };
|
req.session.flash = {
|
||||||
|
type: "danger",
|
||||||
|
message: "Fehler beim Ändern der Rolle",
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
req.session.flash = { type: "success", message: "Rolle erfolgreich geändert" };
|
req.session.flash = {
|
||||||
|
type: "success",
|
||||||
|
message: "Rolle erfolgreich geändert",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
res.redirect("/admin/users");
|
res.redirect("/admin/users");
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetUserPassword(req, res) {
|
async function resetUserPassword(req, res) {
|
||||||
@ -88,23 +145,187 @@ async function resetUserPassword(req, res) {
|
|||||||
db.query(
|
db.query(
|
||||||
"UPDATE users SET password = ? WHERE id = ?",
|
"UPDATE users SET password = ? WHERE id = ?",
|
||||||
[hash, userId],
|
[hash, userId],
|
||||||
err => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(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 {
|
} else {
|
||||||
req.session.flash = { type: "success", message: "Passwort zurückgesetzt" };
|
req.session.flash = {
|
||||||
|
type: "success",
|
||||||
|
message: "Passwort zurückgesetzt",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
res.redirect("/admin/users");
|
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 = {
|
module.exports = {
|
||||||
listUsers,
|
listUsers,
|
||||||
showCreateUser,
|
showCreateUser,
|
||||||
postCreateUser,
|
postCreateUser,
|
||||||
changeUserRole,
|
changeUserRole,
|
||||||
resetUserPassword
|
resetUserPassword,
|
||||||
|
activateUser,
|
||||||
|
deactivateUser,
|
||||||
|
showInvoiceOverview,
|
||||||
|
updateUser,
|
||||||
};
|
};
|
||||||
|
|||||||
162
controllers/companySettings.controller.js
Normal file
162
controllers/companySettings.controller.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
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,108 +1,197 @@
|
|||||||
const db = require("../db");
|
const db = require("../db");
|
||||||
const ejs = require("ejs");
|
const ejs = require("ejs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const htmlToPdf = require("html-pdf-node");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const pdf = require("html-pdf-node");
|
|
||||||
|
|
||||||
async function createInvoicePdf(req, res) {
|
async function createInvoicePdf(req, res) {
|
||||||
const patientId = req.params.id;
|
const patientId = req.params.id;
|
||||||
|
const connection = await db.promise().getConnection();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1️⃣ Patient laden
|
await connection.beginTransaction();
|
||||||
const [[patient]] = await db.promise().query(
|
|
||||||
|
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(
|
||||||
"SELECT * FROM patients WHERE id = ?",
|
"SELECT * FROM patients WHERE id = ?",
|
||||||
[patientId]
|
[patientId]
|
||||||
);
|
);
|
||||||
|
if (!patient) throw new Error("Patient nicht gefunden");
|
||||||
|
|
||||||
if (!patient) {
|
// 🔹 Leistungen
|
||||||
return res.status(404).send("Patient nicht gefunden");
|
const [rows] = await connection.query(
|
||||||
}
|
`
|
||||||
|
|
||||||
// 2️⃣ Leistungen laden (noch nicht abgerechnet)
|
|
||||||
const [rows] = await db.promise().query(`
|
|
||||||
SELECT
|
SELECT
|
||||||
ps.quantity,
|
ps.quantity,
|
||||||
COALESCE(ps.price_override, s.price) AS price,
|
COALESCE(ps.price_override, s.price) AS price,
|
||||||
|
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
|
FROM patient_services ps
|
||||||
JOIN services s ON ps.service_id = s.id
|
JOIN services s ON ps.service_id = s.id
|
||||||
WHERE ps.patient_id = ?
|
WHERE ps.patient_id = ?
|
||||||
AND ps.invoice_id IS NULL
|
AND ps.invoice_id IS NULL
|
||||||
`, [patient.country, patientId]);
|
`,
|
||||||
|
[patientId]
|
||||||
|
);
|
||||||
|
|
||||||
if (rows.length === 0) {
|
if (!rows.length) throw new Error("Keine Leistungen vorhanden");
|
||||||
return res.send("Keine Leistungen vorhanden");
|
|
||||||
}
|
|
||||||
|
|
||||||
const services = rows.map(s => ({
|
const services = rows.map((s) => ({
|
||||||
quantity: Number(s.quantity),
|
quantity: Number(s.quantity),
|
||||||
name: s.name,
|
name: s.name,
|
||||||
price: Number(s.price),
|
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);
|
const total = services.reduce((sum, s) => sum + s.total, 0);
|
||||||
|
|
||||||
// 3️⃣ HTML aus EJS erzeugen
|
// 🔹 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
|
||||||
const html = await ejs.renderFile(
|
const html = await ejs.renderFile(
|
||||||
path.join(__dirname, "../views/invoices/invoice.ejs"),
|
path.join(__dirname, "../views/invoices/invoice.ejs"),
|
||||||
{ patient, services, total }
|
{
|
||||||
);
|
patient,
|
||||||
|
services,
|
||||||
// 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,
|
total,
|
||||||
`documents/${fileName}`,
|
invoice,
|
||||||
req.session.user.id
|
doctor,
|
||||||
]);
|
company,
|
||||||
|
logoBase64,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const invoiceId = invoiceResult.insertId;
|
// 🔹 PDF erzeugen
|
||||||
|
const pdfBuffer = await htmlToPdf.generatePdf(
|
||||||
|
{ content: html },
|
||||||
|
{ format: "A4", printBackground: true }
|
||||||
|
);
|
||||||
|
|
||||||
// 8️⃣ Leistungen verknüpfen
|
// 💾 PDF speichern
|
||||||
await db.promise().query(`
|
fs.writeFileSync(absoluteFilePath, pdfBuffer);
|
||||||
|
|
||||||
|
// 🔗 Leistungen mit Rechnung verknüpfen
|
||||||
|
const [updateResult] = await connection.query(
|
||||||
|
`
|
||||||
UPDATE patient_services
|
UPDATE patient_services
|
||||||
SET invoice_id = ?
|
SET invoice_id = ?
|
||||||
WHERE patient_id = ?
|
WHERE patient_id = ?
|
||||||
AND invoice_id IS NULL
|
AND invoice_id IS NULL
|
||||||
`, [invoiceId, patientId]);
|
`,
|
||||||
|
[invoiceId, patientId]
|
||||||
// 9️⃣ PDF anzeigen
|
|
||||||
res.setHeader("Content-Type", "application/pdf");
|
|
||||||
res.setHeader(
|
|
||||||
"Content-Disposition",
|
|
||||||
`inline; filename="${fileName}"`
|
|
||||||
);
|
);
|
||||||
|
const [[cid]] = await connection.query("SELECT CONNECTION_ID() AS cid");
|
||||||
|
console.log("🔌 INVOICE CID:", cid.cid);
|
||||||
|
await connection.commit();
|
||||||
|
|
||||||
res.send(pdfBuffer);
|
console.log("🔌 INVOICE CID:", cid.cid);
|
||||||
|
// 📤 PDF anzeigen
|
||||||
|
res.render("invoice_preview", {
|
||||||
|
pdfUrl: dbFilePath,
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("❌ PDF ERROR:", err);
|
await connection.rollback();
|
||||||
res.status(500).send("Fehler beim Erstellen der Rechnung");
|
console.error("❌ INVOICE ERROR:", err);
|
||||||
|
res.status(500).send(err.message || "Fehler beim Erstellen der Rechnung");
|
||||||
|
} finally {
|
||||||
|
connection.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
109
controllers/invoicePdf.controller.js_original
Normal file
109
controllers/invoicePdf.controller.js_original
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
const db = require("../db");
|
||||||
|
const ejs = require("ejs");
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
const pdf = require("html-pdf-node");
|
||||||
|
|
||||||
|
async function createInvoicePdf(req, res) {
|
||||||
|
const patientId = req.params.id;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1️⃣ Patient laden
|
||||||
|
const [[patient]] = await db.promise().query(
|
||||||
|
"SELECT * FROM patients WHERE id = ?",
|
||||||
|
[patientId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!patient) {
|
||||||
|
return res.status(404).send("Patient nicht gefunden");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Leistungen laden (noch nicht abgerechnet)
|
||||||
|
const [rows] = await db.promise().query(`
|
||||||
|
SELECT
|
||||||
|
ps.quantity,
|
||||||
|
COALESCE(ps.price_override, s.price) AS price,
|
||||||
|
|
||||||
|
CASE
|
||||||
|
WHEN UPPER(TRIM(?)) = 'ES'
|
||||||
|
THEN COALESCE(NULLIF(s.name_es, ''), s.name_de)
|
||||||
|
ELSE s.name_de
|
||||||
|
END AS name
|
||||||
|
|
||||||
|
FROM patient_services ps
|
||||||
|
JOIN services s ON ps.service_id = s.id
|
||||||
|
WHERE ps.patient_id = ?
|
||||||
|
AND ps.invoice_id IS NULL
|
||||||
|
`, [patient.country, patientId]);
|
||||||
|
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return res.send("Keine Leistungen vorhanden");
|
||||||
|
}
|
||||||
|
|
||||||
|
const services = rows.map(s => ({
|
||||||
|
quantity: Number(s.quantity),
|
||||||
|
name: s.name,
|
||||||
|
price: Number(s.price),
|
||||||
|
total: Number(s.price) * Number(s.quantity)
|
||||||
|
}));
|
||||||
|
|
||||||
|
const total = services.reduce((sum, s) => sum + s.total, 0);
|
||||||
|
|
||||||
|
// 3️⃣ HTML aus EJS erzeugen
|
||||||
|
const html = await ejs.renderFile(
|
||||||
|
path.join(__dirname, "../views/invoices/invoice.ejs"),
|
||||||
|
{ patient, services, total }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4️⃣ PDF erzeugen
|
||||||
|
const pdfBuffer = await pdf.generatePdf(
|
||||||
|
{ content: html },
|
||||||
|
{ format: "A4" }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5️⃣ Dateiname + Pfad
|
||||||
|
const date = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
|
||||||
|
const fileName = `invoice_${patientId}_${date}.pdf`;
|
||||||
|
const outputPath = path.join(__dirname, "..", "documents", fileName);
|
||||||
|
|
||||||
|
// 6️⃣ PDF speichern
|
||||||
|
fs.writeFileSync(outputPath, pdfBuffer);
|
||||||
|
|
||||||
|
// 7️⃣ OPTIONAL: Rechnung in DB speichern (empfohlen)
|
||||||
|
const [invoiceResult] = await db.promise().query(`
|
||||||
|
INSERT INTO invoices
|
||||||
|
(patient_id, invoice_date, total_amount, file_path, created_by, status)
|
||||||
|
VALUES (?, CURDATE(), ?, ?, ?, 'open')
|
||||||
|
`, [
|
||||||
|
patientId,
|
||||||
|
total,
|
||||||
|
`documents/${fileName}`,
|
||||||
|
req.session.user.id
|
||||||
|
]);
|
||||||
|
|
||||||
|
const invoiceId = invoiceResult.insertId;
|
||||||
|
|
||||||
|
// 8️⃣ Leistungen verknüpfen
|
||||||
|
await db.promise().query(`
|
||||||
|
UPDATE patient_services
|
||||||
|
SET invoice_id = ?
|
||||||
|
WHERE patient_id = ?
|
||||||
|
AND invoice_id IS NULL
|
||||||
|
`, [invoiceId, patientId]);
|
||||||
|
|
||||||
|
// 9️⃣ PDF anzeigen
|
||||||
|
res.setHeader("Content-Type", "application/pdf");
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`inline; filename="${fileName}"`
|
||||||
|
);
|
||||||
|
|
||||||
|
res.send(pdfBuffer);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ PDF ERROR:", err);
|
||||||
|
res.status(500).send("Fehler beim Erstellen der Rechnung");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { createInvoicePdf };
|
||||||
@ -2,25 +2,50 @@ const db = require("../db");
|
|||||||
|
|
||||||
// 📋 LISTE
|
// 📋 LISTE
|
||||||
function listMedications(req, res, next) {
|
function listMedications(req, res, next) {
|
||||||
const sql = `
|
const { q, onlyActive } = req.query;
|
||||||
|
|
||||||
|
let sql = `
|
||||||
SELECT
|
SELECT
|
||||||
v.id,
|
v.id,
|
||||||
|
m.id AS medication_id,
|
||||||
m.name AS medication,
|
m.name AS medication,
|
||||||
|
m.active,
|
||||||
f.name AS form,
|
f.name AS form,
|
||||||
v.dosage,
|
v.dosage,
|
||||||
v.package
|
v.package
|
||||||
FROM medication_variants v
|
FROM medication_variants v
|
||||||
JOIN medications m ON v.medication_id = m.id
|
JOIN medications m ON v.medication_id = m.id
|
||||||
JOIN medication_forms f ON v.form_id = f.id
|
JOIN medication_forms f ON v.form_id = f.id
|
||||||
ORDER BY m.name, v.dosage
|
WHERE 1=1
|
||||||
`;
|
`;
|
||||||
|
|
||||||
db.query(sql, (err, rows) => {
|
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) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
res.render("medications", {
|
res.render("medications", {
|
||||||
rows,
|
rows,
|
||||||
user: req.session.user
|
query: { q, onlyActive },
|
||||||
|
user: req.session.user,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -38,16 +63,75 @@ function updateMedication(req, res, next) {
|
|||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`;
|
`;
|
||||||
|
|
||||||
db.query(sql, [dosage, pkg, id], err => {
|
db.query(sql, [dosage, pkg, id], (err) => {
|
||||||
if (err) return next(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");
|
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 = {
|
module.exports = {
|
||||||
listMedications,
|
listMedications,
|
||||||
updateMedication
|
updateMedication,
|
||||||
|
toggleMedication,
|
||||||
|
showCreateMedication,
|
||||||
|
createMedication,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ function createPatient(req, res) {
|
|||||||
db.query(
|
db.query(
|
||||||
"INSERT INTO patients (firstname, lastname, birthdate, active) VALUES (?, ?, ?, 1)",
|
"INSERT INTO patients (firstname, lastname, birthdate, active) VALUES (?, ?, ?, 1)",
|
||||||
[firstname, lastname, birthdate],
|
[firstname, lastname, birthdate],
|
||||||
err => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return res.send("Datenbankfehler");
|
return res.send("Datenbankfehler");
|
||||||
@ -26,15 +26,28 @@ function listPatients(req, res) {
|
|||||||
let sql = "SELECT * FROM patients WHERE 1=1";
|
let sql = "SELECT * FROM patients WHERE 1=1";
|
||||||
const params = [];
|
const params = [];
|
||||||
|
|
||||||
if (firstname) { sql += " AND firstname LIKE ?"; params.push(`%${firstname}%`); }
|
if (firstname) {
|
||||||
if (lastname) { sql += " AND lastname LIKE ?"; params.push(`%${lastname}%`); }
|
sql += " AND firstname LIKE ?";
|
||||||
if (birthdate) { sql += " AND birthdate = ?"; params.push(birthdate); }
|
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";
|
sql += " ORDER BY lastname, firstname";
|
||||||
|
|
||||||
db.query(sql, params, (err, patients) => {
|
db.query(sql, params, (err, patients) => {
|
||||||
if (err) return res.send("Datenbankfehler");
|
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 = ?",
|
"SELECT * FROM patients WHERE id = ?",
|
||||||
[req.params.id],
|
[req.params.id],
|
||||||
(err, results) => {
|
(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", {
|
res.render("patient_edit", {
|
||||||
patient: results[0],
|
patient: results[0],
|
||||||
error: null,
|
error: null,
|
||||||
user: req.session.user,
|
user: req.session.user,
|
||||||
returnTo: req.query.returnTo || null
|
returnTo: req.query.returnTo || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -71,13 +85,13 @@ function updatePatient(req, res) {
|
|||||||
postal_code,
|
postal_code,
|
||||||
city,
|
city,
|
||||||
country,
|
country,
|
||||||
notes
|
notes,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
if (!firstname || !lastname || !birthdate) {
|
if (!firstname || !lastname || !birthdate) {
|
||||||
req.session.flash = {
|
req.session.flash = {
|
||||||
type: "warning",
|
type: "warning",
|
||||||
message: "Vorname, Nachname und Geburtsdatum sind Pflichtfelder"
|
message: "Vorname, Nachname und Geburtsdatum sind Pflichtfelder",
|
||||||
};
|
};
|
||||||
return res.redirect("back");
|
return res.redirect("back");
|
||||||
}
|
}
|
||||||
@ -112,9 +126,9 @@ function updatePatient(req, res) {
|
|||||||
city || null,
|
city || null,
|
||||||
country || null,
|
country || null,
|
||||||
notes || null,
|
notes || null,
|
||||||
id
|
id,
|
||||||
],
|
],
|
||||||
err => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return res.send("Fehler beim Speichern");
|
return res.send("Fehler beim Speichern");
|
||||||
@ -174,14 +188,15 @@ function showPatientMedications(req, res) {
|
|||||||
if (err) return res.send("Medikamente konnten nicht geladen werden");
|
if (err) return res.send("Medikamente konnten nicht geladen werden");
|
||||||
|
|
||||||
db.query(currentSql, [patientId], (err, currentMeds) => {
|
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", {
|
res.render("patient_medications", {
|
||||||
patient: patients[0],
|
patient: patients[0],
|
||||||
meds,
|
meds,
|
||||||
currentMeds,
|
currentMeds,
|
||||||
user: req.session.user,
|
user: req.session.user,
|
||||||
returnTo
|
returnTo,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -195,13 +210,14 @@ function moveToWaitingRoom(req, res) {
|
|||||||
`
|
`
|
||||||
UPDATE patients
|
UPDATE patients
|
||||||
SET waiting_room = 1,
|
SET waiting_room = 1,
|
||||||
discharged = 0
|
discharged = 0,
|
||||||
|
active = 1
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`,
|
`,
|
||||||
[id],
|
[id],
|
||||||
err => {
|
(err) => {
|
||||||
if (err) return res.send("Fehler beim Verschieben ins Wartezimmer");
|
if (err) return res.send("Fehler beim Verschieben ins Wartezimmer");
|
||||||
res.redirect("/patients");
|
return res.redirect("/dashboard"); // optional: direkt Dashboard
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -214,7 +230,7 @@ function showWaitingRoom(req, res) {
|
|||||||
|
|
||||||
res.render("waiting_room", {
|
res.render("waiting_room", {
|
||||||
patients,
|
patients,
|
||||||
user: req.session.user
|
user: req.session.user,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -230,21 +246,28 @@ function showPatientOverview(req, res) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const notesSql = `
|
const notesSql = `
|
||||||
SELECT *
|
|
||||||
FROM patient_notes
|
|
||||||
WHERE patient_id = ?
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 🔤 Services dynamisch nach Sprache laden
|
|
||||||
const servicesSql = (nameField) => `
|
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
pn.*,
|
||||||
${nameField} AS name,
|
u.title,
|
||||||
price
|
u.first_name,
|
||||||
FROM services
|
u.last_name
|
||||||
WHERE active = 1
|
FROM patient_notes pn
|
||||||
ORDER BY ${nameField}
|
LEFT JOIN users u ON pn.created_by = u.id
|
||||||
|
WHERE pn.patient_id = ?
|
||||||
|
ORDER BY pn.created_at DESC
|
||||||
|
`;
|
||||||
|
|
||||||
|
const medicationVariantsSql = `
|
||||||
|
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
|
||||||
`;
|
`;
|
||||||
|
|
||||||
db.query(patientSql, [patientId], (err, patients) => {
|
db.query(patientSql, [patientId], (err, patients) => {
|
||||||
@ -254,12 +277,22 @@ function showPatientOverview(req, res) {
|
|||||||
|
|
||||||
const patient = patients[0];
|
const patient = patients[0];
|
||||||
|
|
||||||
// 🇪🇸 / 🇩🇪 Sprache bestimmen
|
// 🇪🇸 / 🇩🇪 Sprache für Leistungen
|
||||||
const serviceNameField =
|
const serviceNameField =
|
||||||
patient.country === "ES"
|
patient.country === "ES"
|
||||||
? "COALESCE(NULLIF(name_es, ''), name_de)"
|
? "COALESCE(NULLIF(name_es, ''), name_de)"
|
||||||
: "name_de";
|
: "name_de";
|
||||||
|
|
||||||
|
const servicesSql = `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
${serviceNameField} AS name,
|
||||||
|
price
|
||||||
|
FROM services
|
||||||
|
WHERE active = 1
|
||||||
|
ORDER BY ${serviceNameField}
|
||||||
|
`;
|
||||||
|
|
||||||
const todayServicesSql = `
|
const todayServicesSql = `
|
||||||
SELECT
|
SELECT
|
||||||
ps.id,
|
ps.id,
|
||||||
@ -279,18 +312,23 @@ function showPatientOverview(req, res) {
|
|||||||
db.query(notesSql, [patientId], (err, notes) => {
|
db.query(notesSql, [patientId], (err, notes) => {
|
||||||
if (err) return res.send("Fehler Notizen");
|
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");
|
if (err) return res.send("Fehler Leistungen");
|
||||||
|
|
||||||
db.query(todayServicesSql, [patientId], (err, todayServices) => {
|
db.query(todayServicesSql, [patientId], (err, todayServices) => {
|
||||||
if (err) return res.send("Fehler heutige Leistungen");
|
if (err) return res.send("Fehler heutige Leistungen");
|
||||||
|
|
||||||
|
db.query(medicationVariantsSql, (err, medicationVariants) => {
|
||||||
|
if (err) return res.send("Fehler Medikamente");
|
||||||
|
|
||||||
res.render("patient_overview", {
|
res.render("patient_overview", {
|
||||||
patient,
|
patient,
|
||||||
notes,
|
notes,
|
||||||
services,
|
services,
|
||||||
todayServices,
|
todayServices,
|
||||||
user: req.session.user
|
medicationVariants,
|
||||||
|
user: req.session.user,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -298,6 +336,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) {
|
function addPatientNote(req, res) {
|
||||||
const patientId = req.params.id;
|
const patientId = req.params.id;
|
||||||
@ -308,9 +387,10 @@ function addPatientNote(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db.query(
|
db.query(
|
||||||
"INSERT INTO patient_notes (patient_id, note) VALUES (?, ?)",
|
"INSERT INTO patient_notes (patient_id, created_by, note) VALUES (?, ?, ?)",
|
||||||
[patientId, note],
|
[patientId, req.session.user.id, note],
|
||||||
err => {
|
|
||||||
|
(err) => {
|
||||||
if (err) return res.send("Fehler beim Speichern der Notiz");
|
if (err) return res.send("Fehler beim Speichern der Notiz");
|
||||||
res.redirect(`/patients/${patientId}/overview`);
|
res.redirect(`/patients/${patientId}/overview`);
|
||||||
}
|
}
|
||||||
@ -323,7 +403,7 @@ function callFromWaitingRoom(req, res) {
|
|||||||
db.query(
|
db.query(
|
||||||
"UPDATE patients SET waiting_room = 0 WHERE id = ?",
|
"UPDATE patients SET waiting_room = 0 WHERE id = ?",
|
||||||
[patientId],
|
[patientId],
|
||||||
err => {
|
(err) => {
|
||||||
if (err) return res.send("Fehler beim Entfernen aus dem Wartezimmer");
|
if (err) return res.send("Fehler beim Entfernen aus dem Wartezimmer");
|
||||||
res.redirect(`/patients/${patientId}/overview`);
|
res.redirect(`/patients/${patientId}/overview`);
|
||||||
}
|
}
|
||||||
@ -334,11 +414,21 @@ function dischargePatient(req, res) {
|
|||||||
const patientId = req.params.id;
|
const patientId = req.params.id;
|
||||||
|
|
||||||
db.query(
|
db.query(
|
||||||
"UPDATE patients SET discharged = 1 WHERE id = ?",
|
`
|
||||||
|
UPDATE patients
|
||||||
|
SET discharged = 1,
|
||||||
|
waiting_room = 0,
|
||||||
|
active = 0
|
||||||
|
WHERE id = ?
|
||||||
|
`,
|
||||||
[patientId],
|
[patientId],
|
||||||
err => {
|
(err) => {
|
||||||
if (err) return res.send("Fehler beim Entlassen des Patienten");
|
if (err) {
|
||||||
res.redirect("/waiting-room");
|
console.error(err);
|
||||||
|
return res.send("Fehler beim Entlassen des Patienten");
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.redirect("/dashboard");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -375,7 +465,7 @@ function showMedicationPlan(req, res) {
|
|||||||
|
|
||||||
res.render("patient_plan", {
|
res.render("patient_plan", {
|
||||||
patient: patients[0],
|
patient: patients[0],
|
||||||
meds
|
meds,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -388,26 +478,28 @@ function movePatientToWaitingRoom(req, res) {
|
|||||||
`
|
`
|
||||||
UPDATE patients
|
UPDATE patients
|
||||||
SET waiting_room = 1,
|
SET waiting_room = 1,
|
||||||
discharged = 0
|
discharged = 0,
|
||||||
|
status = 'waiting',
|
||||||
|
active = 1
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`,
|
`,
|
||||||
[patientId],
|
[patientId],
|
||||||
err => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
req.session.flash = {
|
req.session.flash = {
|
||||||
type: "danger",
|
type: "danger",
|
||||||
message: "Fehler beim Zurücksetzen ins Wartezimmer"
|
message: "Fehler beim Zurücksetzen ins Wartezimmer",
|
||||||
};
|
};
|
||||||
return res.redirect(`/patients/${patientId}/overview`);
|
return res.redirect(`/patients/${patientId}/overview`);
|
||||||
}
|
}
|
||||||
|
|
||||||
req.session.flash = {
|
req.session.flash = {
|
||||||
type: "success",
|
type: "success",
|
||||||
message: "Patient wurde ins Wartezimmer gesetzt"
|
message: "Patient wurde ins Wartezimmer gesetzt",
|
||||||
};
|
};
|
||||||
|
|
||||||
res.redirect("/waiting-room");
|
return res.redirect("/dashboard");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -415,55 +507,107 @@ function movePatientToWaitingRoom(req, res) {
|
|||||||
function deactivatePatient(req, res) {
|
function deactivatePatient(req, res) {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
|
|
||||||
db.query(
|
db.query("UPDATE patients SET active = 0 WHERE id = ?", [id], (err) => {
|
||||||
"UPDATE patients SET active = 0 WHERE id = ?",
|
|
||||||
[id],
|
|
||||||
err => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
req.session.flash = {
|
req.session.flash = {
|
||||||
type: "danger",
|
type: "danger",
|
||||||
message: "Patient konnte nicht gesperrt werden"
|
message: "Patient konnte nicht gesperrt werden",
|
||||||
};
|
};
|
||||||
return res.redirect("/patients");
|
return res.redirect("/patients");
|
||||||
}
|
}
|
||||||
|
|
||||||
req.session.flash = {
|
req.session.flash = {
|
||||||
type: "success",
|
type: "success",
|
||||||
message: "Patient wurde gesperrt"
|
message: "Patient wurde gesperrt",
|
||||||
};
|
};
|
||||||
|
|
||||||
res.redirect("/patients");
|
res.redirect("/patients");
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function activatePatient(req, res) {
|
function activatePatient(req, res) {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
|
|
||||||
db.query(
|
db.query("UPDATE patients SET active = 1 WHERE id = ?", [id], (err) => {
|
||||||
"UPDATE patients SET active = 1 WHERE id = ?",
|
|
||||||
[id],
|
|
||||||
err => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
req.session.flash = {
|
req.session.flash = {
|
||||||
type: "danger",
|
type: "danger",
|
||||||
message: "Patient konnte nicht entsperrt werden"
|
message: "Patient konnte nicht entsperrt werden",
|
||||||
};
|
};
|
||||||
return res.redirect("/patients");
|
return res.redirect("/patients");
|
||||||
}
|
}
|
||||||
|
|
||||||
req.session.flash = {
|
req.session.flash = {
|
||||||
type: "success",
|
type: "success",
|
||||||
message: "Patient wurde entsperrt"
|
message: "Patient wurde entsperrt",
|
||||||
};
|
};
|
||||||
|
|
||||||
res.redirect("/patients");
|
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 = {
|
module.exports = {
|
||||||
listPatients,
|
listPatients,
|
||||||
@ -481,5 +625,7 @@ module.exports = {
|
|||||||
showMedicationPlan,
|
showMedicationPlan,
|
||||||
movePatientToWaitingRoom,
|
movePatientToWaitingRoom,
|
||||||
deactivatePatient,
|
deactivatePatient,
|
||||||
activatePatient
|
activatePatient,
|
||||||
|
showPatientOverviewDashborad,
|
||||||
|
assignMedicationToPatient,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -27,17 +27,15 @@ function addPatientService(req, res) {
|
|||||||
const price = results[0].price;
|
const price = results[0].price;
|
||||||
|
|
||||||
db.query(
|
db.query(
|
||||||
`
|
`INSERT INTO patient_services
|
||||||
INSERT INTO patient_services
|
|
||||||
(patient_id, service_id, quantity, price, service_date, created_by)
|
(patient_id, service_id, quantity, price, service_date, created_by)
|
||||||
VALUES (?, ?, ?, ?, CURDATE(), ?)
|
VALUES (?, ?, ?, ?, CURDATE(), ?) `,
|
||||||
`,
|
|
||||||
[
|
[
|
||||||
patientId,
|
patientId,
|
||||||
service_id,
|
service_id,
|
||||||
quantity || 1,
|
quantity || 1,
|
||||||
price,
|
price,
|
||||||
req.session.user.id
|
req.session.user.id // behandelnder Arzt
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -81,8 +79,24 @@ 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 = {
|
module.exports = {
|
||||||
addPatientService,
|
addPatientService,
|
||||||
deletePatientService,
|
deletePatientService,
|
||||||
updatePatientServicePrice
|
updatePatientServicePrice,
|
||||||
|
updatePatientServiceQuantity
|
||||||
};
|
};
|
||||||
|
|||||||
@ -222,7 +222,11 @@ function toggleService(req, res) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function listOpenServices(req, res, next) {
|
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");
|
||||||
|
|
||||||
const sql = `
|
const sql = `
|
||||||
SELECT
|
SELECT
|
||||||
p.id AS patient_id,
|
p.id AS patient_id,
|
||||||
@ -232,32 +236,48 @@ function listOpenServices(req, res, next) {
|
|||||||
ps.id AS patient_service_id,
|
ps.id AS patient_service_id,
|
||||||
ps.quantity,
|
ps.quantity,
|
||||||
COALESCE(ps.price_override, s.price) AS price,
|
COALESCE(ps.price_override, s.price) AS price,
|
||||||
|
|
||||||
-- 🌍 Sprachabhängiger Servicename
|
|
||||||
CASE
|
CASE
|
||||||
WHEN UPPER(TRIM(p.country)) = 'ES'
|
WHEN UPPER(TRIM(p.country)) = 'ES'
|
||||||
THEN COALESCE(NULLIF(s.name_es, ''), s.name_de)
|
THEN COALESCE(NULLIF(s.name_es, ''), s.name_de)
|
||||||
ELSE s.name_de
|
ELSE s.name_de
|
||||||
END AS name
|
END AS name
|
||||||
|
|
||||||
FROM patient_services ps
|
FROM patient_services ps
|
||||||
JOIN patients p ON ps.patient_id = p.id
|
JOIN patients p ON ps.patient_id = p.id
|
||||||
JOIN services s ON ps.service_id = s.id
|
JOIN services s ON ps.service_id = s.id
|
||||||
WHERE ps.invoice_id IS NULL
|
WHERE ps.invoice_id IS NULL
|
||||||
ORDER BY
|
ORDER BY p.lastname, p.firstname, name
|
||||||
p.lastname,
|
|
||||||
p.firstname,
|
|
||||||
name
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
db.query(sql, (err, rows) => {
|
let connection;
|
||||||
if (err) return next(err);
|
|
||||||
|
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);
|
||||||
|
|
||||||
res.render("open_services", {
|
res.render("open_services", {
|
||||||
rows,
|
rows,
|
||||||
user: req.session.user
|
user: req.session.user
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
} finally {
|
||||||
|
if (connection) connection.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
69
db.js
69
db.js
@ -1,15 +1,62 @@
|
|||||||
const mysql = require("mysql2");
|
const mysql = require("mysql2");
|
||||||
|
const { loadConfig } = require("./config-manager");
|
||||||
|
|
||||||
const db = mysql.createConnection({
|
let pool = null;
|
||||||
host: "85.215.63.122",
|
|
||||||
user: "praxisuser",
|
|
||||||
password: "praxisuser",
|
|
||||||
database: "praxissoftware"
|
|
||||||
});
|
|
||||||
|
|
||||||
db.connect(err => {
|
function initPool() {
|
||||||
if (err) throw err;
|
const config = loadConfig();
|
||||||
console.log("MySQL verbunden");
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = db;
|
// ✅ 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;
|
||||||
|
|||||||
208
debug_invoice.html
Normal file
208
debug_invoice.html
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
<!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
Normal file
BIN
libmysql.dll
Normal file
Binary file not shown.
40
locales/de.json
Normal file
40
locales/de.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
41
locales/es.json
Normal file
41
locales/es.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"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,25 +2,53 @@ function requireLogin(req, res, next) {
|
|||||||
if (!req.session.user) {
|
if (!req.session.user) {
|
||||||
return res.redirect("/");
|
return res.redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.user = req.session.user;
|
||||||
next();
|
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) {
|
function requireAdmin(req, res, next) {
|
||||||
console.log("ADMIN CHECK:", req.session.user);
|
console.log("ADMIN CHECK:", req.session.user);
|
||||||
|
|
||||||
if (!req.session.user) {
|
if (!req.session.user) {
|
||||||
return res.send("NICHT EINGELOGGT");
|
return res.redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.session.user.role !== "arzt") {
|
if (req.session.user.role !== "admin") {
|
||||||
return res.send("KEIN ARZT: " + req.session.user.role);
|
return res
|
||||||
|
.status(403)
|
||||||
|
.send(
|
||||||
|
"⛔ Kein Zugriff (Admin erforderlich). Rolle: " + req.session.user.role,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.user = req.session.user;
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
requireLogin,
|
requireLogin,
|
||||||
requireAdmin
|
requireArzt,
|
||||||
|
requireAdmin,
|
||||||
};
|
};
|
||||||
|
|||||||
24
middleware/uploadLogo.js
Normal file
24
middleware/uploadLogo.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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
Normal file
BIN
mysqldump.exe
Normal file
Binary file not shown.
274
package-lock.json
generated
274
package-lock.json
generated
@ -1559,18 +1559,6 @@
|
|||||||
"node": ">=0.8"
|
"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": {
|
"node_modules/ansi-escapes": {
|
||||||
"version": "4.3.2",
|
"version": "4.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
||||||
@ -2677,12 +2665,6 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/dezalgo": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||||
@ -3742,6 +3724,143 @@
|
|||||||
"puppeteer": "^10.4.0"
|
"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": {
|
"node_modules/htmlparser2": {
|
||||||
"version": "3.10.1",
|
"version": "3.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
||||||
@ -3776,19 +3895,6 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"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": {
|
"node_modules/human-signals": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||||
@ -5649,15 +5755,6 @@
|
|||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"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": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
@ -5694,54 +5791,6 @@
|
|||||||
"once": "^1.3.1"
|
"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": {
|
"node_modules/pure-rand": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
|
||||||
@ -6557,34 +6606,6 @@
|
|||||||
"url": "https://opencollective.com/synckit"
|
"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": {
|
"node_modules/test-exclude": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
|
||||||
@ -7078,27 +7099,6 @@
|
|||||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
"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": {
|
"node_modules/xlsx": {
|
||||||
"version": "0.18.5",
|
"version": "0.18.5",
|
||||||
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
||||||
|
|||||||
BIN
plugin/adt_null.dll
Normal file
BIN
plugin/adt_null.dll
Normal file
Binary file not shown.
BIN
plugin/authentication_kerberos_client.dll
Normal file
BIN
plugin/authentication_kerberos_client.dll
Normal file
Binary file not shown.
BIN
plugin/authentication_ldap_sasl_client.dll
Normal file
BIN
plugin/authentication_ldap_sasl_client.dll
Normal file
Binary file not shown.
BIN
plugin/authentication_oci_client.dll
Normal file
BIN
plugin/authentication_oci_client.dll
Normal file
Binary file not shown.
BIN
plugin/authentication_openid_connect_client.dll
Normal file
BIN
plugin/authentication_openid_connect_client.dll
Normal file
Binary file not shown.
BIN
plugin/authentication_webauthn_client.dll
Normal file
BIN
plugin/authentication_webauthn_client.dll
Normal file
Binary file not shown.
BIN
plugin/component_audit_api_message_emit.dll
Normal file
BIN
plugin/component_audit_api_message_emit.dll
Normal file
Binary file not shown.
BIN
plugin/component_connection_control.dll
Normal file
BIN
plugin/component_connection_control.dll
Normal file
Binary file not shown.
BIN
plugin/component_keyring_file.dll
Normal file
BIN
plugin/component_keyring_file.dll
Normal file
Binary file not shown.
BIN
plugin/component_log_filter_dragnet.dll
Normal file
BIN
plugin/component_log_filter_dragnet.dll
Normal file
Binary file not shown.
BIN
plugin/component_log_sink_json.dll
Normal file
BIN
plugin/component_log_sink_json.dll
Normal file
Binary file not shown.
BIN
plugin/component_log_sink_syseventlog.dll
Normal file
BIN
plugin/component_log_sink_syseventlog.dll
Normal file
Binary file not shown.
BIN
plugin/component_mysqlbackup.dll
Normal file
BIN
plugin/component_mysqlbackup.dll
Normal file
Binary file not shown.
BIN
plugin/component_query_attributes.dll
Normal file
BIN
plugin/component_query_attributes.dll
Normal file
Binary file not shown.
BIN
plugin/component_reference_cache.dll
Normal file
BIN
plugin/component_reference_cache.dll
Normal file
Binary file not shown.
BIN
plugin/component_validate_password.dll
Normal file
BIN
plugin/component_validate_password.dll
Normal file
Binary file not shown.
BIN
plugin/connection_control.dll
Normal file
BIN
plugin/connection_control.dll
Normal file
Binary file not shown.
BIN
plugin/ddl_rewriter.dll
Normal file
BIN
plugin/ddl_rewriter.dll
Normal file
Binary file not shown.
BIN
plugin/debug/adt_null.dll
Normal file
BIN
plugin/debug/adt_null.dll
Normal file
Binary file not shown.
BIN
plugin/debug/adt_null.pdb
Normal file
BIN
plugin/debug/adt_null.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/component_audit_api_message_emit.dll
Normal file
BIN
plugin/debug/component_audit_api_message_emit.dll
Normal file
Binary file not shown.
BIN
plugin/debug/component_audit_api_message_emit.pdb
Normal file
BIN
plugin/debug/component_audit_api_message_emit.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/component_connection_control.dll
Normal file
BIN
plugin/debug/component_connection_control.dll
Normal file
Binary file not shown.
BIN
plugin/debug/component_connection_control.pdb
Normal file
BIN
plugin/debug/component_connection_control.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/component_keyring_file.dll
Normal file
BIN
plugin/debug/component_keyring_file.dll
Normal file
Binary file not shown.
BIN
plugin/debug/component_keyring_file.pdb
Normal file
BIN
plugin/debug/component_keyring_file.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/component_log_filter_dragnet.dll
Normal file
BIN
plugin/debug/component_log_filter_dragnet.dll
Normal file
Binary file not shown.
BIN
plugin/debug/component_log_filter_dragnet.pdb
Normal file
BIN
plugin/debug/component_log_filter_dragnet.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/component_log_sink_json.dll
Normal file
BIN
plugin/debug/component_log_sink_json.dll
Normal file
Binary file not shown.
BIN
plugin/debug/component_log_sink_json.pdb
Normal file
BIN
plugin/debug/component_log_sink_json.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/component_log_sink_syseventlog.dll
Normal file
BIN
plugin/debug/component_log_sink_syseventlog.dll
Normal file
Binary file not shown.
BIN
plugin/debug/component_log_sink_syseventlog.pdb
Normal file
BIN
plugin/debug/component_log_sink_syseventlog.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/component_mysqlbackup.dll
Normal file
BIN
plugin/debug/component_mysqlbackup.dll
Normal file
Binary file not shown.
BIN
plugin/debug/component_mysqlbackup.pdb
Normal file
BIN
plugin/debug/component_mysqlbackup.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/component_query_attributes.dll
Normal file
BIN
plugin/debug/component_query_attributes.dll
Normal file
Binary file not shown.
BIN
plugin/debug/component_query_attributes.pdb
Normal file
BIN
plugin/debug/component_query_attributes.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/component_reference_cache.dll
Normal file
BIN
plugin/debug/component_reference_cache.dll
Normal file
Binary file not shown.
BIN
plugin/debug/component_reference_cache.pdb
Normal file
BIN
plugin/debug/component_reference_cache.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/component_validate_password.dll
Normal file
BIN
plugin/debug/component_validate_password.dll
Normal file
Binary file not shown.
BIN
plugin/debug/component_validate_password.pdb
Normal file
BIN
plugin/debug/component_validate_password.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/connection_control.dll
Normal file
BIN
plugin/debug/connection_control.dll
Normal file
Binary file not shown.
BIN
plugin/debug/connection_control.pdb
Normal file
BIN
plugin/debug/connection_control.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/ddl_rewriter.dll
Normal file
BIN
plugin/debug/ddl_rewriter.dll
Normal file
Binary file not shown.
BIN
plugin/debug/ddl_rewriter.pdb
Normal file
BIN
plugin/debug/ddl_rewriter.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/group_replication.dll
Normal file
BIN
plugin/debug/group_replication.dll
Normal file
Binary file not shown.
BIN
plugin/debug/group_replication.pdb
Normal file
BIN
plugin/debug/group_replication.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/ha_example.dll
Normal file
BIN
plugin/debug/ha_example.dll
Normal file
Binary file not shown.
BIN
plugin/debug/ha_example.pdb
Normal file
BIN
plugin/debug/ha_example.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/ha_mock.dll
Normal file
BIN
plugin/debug/ha_mock.dll
Normal file
Binary file not shown.
BIN
plugin/debug/ha_mock.pdb
Normal file
BIN
plugin/debug/ha_mock.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/keyring_udf.dll
Normal file
BIN
plugin/debug/keyring_udf.dll
Normal file
Binary file not shown.
BIN
plugin/debug/keyring_udf.pdb
Normal file
BIN
plugin/debug/keyring_udf.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/libpluginmecab.dll
Normal file
BIN
plugin/debug/libpluginmecab.dll
Normal file
Binary file not shown.
BIN
plugin/debug/libpluginmecab.pdb
Normal file
BIN
plugin/debug/libpluginmecab.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/locking_service.dll
Normal file
BIN
plugin/debug/locking_service.dll
Normal file
Binary file not shown.
BIN
plugin/debug/locking_service.pdb
Normal file
BIN
plugin/debug/locking_service.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/mypluglib.dll
Normal file
BIN
plugin/debug/mypluglib.dll
Normal file
Binary file not shown.
BIN
plugin/debug/mypluglib.pdb
Normal file
BIN
plugin/debug/mypluglib.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/mysql_clone.dll
Normal file
BIN
plugin/debug/mysql_clone.dll
Normal file
Binary file not shown.
BIN
plugin/debug/mysql_clone.pdb
Normal file
BIN
plugin/debug/mysql_clone.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/mysql_no_login.dll
Normal file
BIN
plugin/debug/mysql_no_login.dll
Normal file
Binary file not shown.
BIN
plugin/debug/mysql_no_login.pdb
Normal file
BIN
plugin/debug/mysql_no_login.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/rewrite_example.dll
Normal file
BIN
plugin/debug/rewrite_example.dll
Normal file
Binary file not shown.
BIN
plugin/debug/rewrite_example.pdb
Normal file
BIN
plugin/debug/rewrite_example.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/rewriter.dll
Normal file
BIN
plugin/debug/rewriter.dll
Normal file
Binary file not shown.
BIN
plugin/debug/rewriter.pdb
Normal file
BIN
plugin/debug/rewriter.pdb
Normal file
Binary file not shown.
BIN
plugin/debug/semisync_replica.dll
Normal file
BIN
plugin/debug/semisync_replica.dll
Normal file
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