diff --git a/2024091001 Preisliste PRAXIS.xlsx b/2024091001 Preisliste PRAXIS.xlsx
deleted file mode 100644
index 788d3de..0000000
Binary files a/2024091001 Preisliste PRAXIS.xlsx and /dev/null differ
diff --git a/MEDIKAMENTE 228.02.2024 docx.docx b/MEDIKAMENTE 228.02.2024 docx.docx
deleted file mode 100644
index 9e080b0..0000000
Binary files a/MEDIKAMENTE 228.02.2024 docx.docx and /dev/null differ
diff --git a/import_medications.js_nicht_mehr_Ausführen_ b/aenderungen_scripte/import_medications.js_nicht_mehr_Ausführen_
similarity index 100%
rename from import_medications.js_nicht_mehr_Ausführen_
rename to aenderungen_scripte/import_medications.js_nicht_mehr_Ausführen_
diff --git a/import_services.jss_nicht_mehr_Ausführen_ b/aenderungen_scripte/import_services.jss_nicht_mehr_Ausführen_
similarity index 100%
rename from import_services.jss_nicht_mehr_Ausführen_
rename to aenderungen_scripte/import_services.jss_nicht_mehr_Ausführen_
diff --git a/app.js b/app.js
index 7e98013..31ed4e4 100644
--- a/app.js
+++ b/app.js
@@ -17,6 +17,9 @@ const serviceRoutes = require("./routes/service.routes");
const patientServiceRoutes = require("./routes/patientService.routes");
const invoiceRoutes = require("./routes/invoice.routes");
const patientFileRoutes = require("./routes/patientFile.routes");
+const companySettingsRoutes = require("./routes/companySettings.routes");
+
+
require("dotenv").config();
@@ -48,6 +51,12 @@ app.use("/patients", require("./routes/patient.routes"));
app.use("/uploads", express.static("uploads"));
+/* ===============================
+ COMPANYDATA
+================================ */
+app.use(companySettingsRoutes);
+
+
/* ===============================
LOGIN
================================ */
diff --git a/controllers/admin.controller.js b/controllers/admin.controller.js
index e287d89..0ae9390 100644
--- a/controllers/admin.controller.js
+++ b/controllers/admin.controller.js
@@ -23,18 +23,55 @@ function showCreateUser(req, res) {
}
async function postCreateUser(req, res) {
- let { username, password, role } = req.body;
- username = username.trim();
+ let {
+ first_name,
+ last_name,
+ username,
+ password,
+ role,
+ fachrichtung,
+ arztnummer
+ } = req.body;
- if (!username || !password || !role) {
+ 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", {
- error: "Alle Felder sind Pflichtfelder",
+ error: "Alle Pflichtfelder müssen ausgefüllt sein",
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;
+ }
+
try {
- await createUser(db, username, password, role);
+ await createUser(
+ db,
+ first_name,
+ last_name,
+ username,
+ password,
+ role,
+ fachrichtung,
+ arztnummer
+ );
req.session.flash = {
type: "success",
@@ -42,6 +79,7 @@ async function postCreateUser(req, res) {
};
res.redirect("/admin/users");
+
} catch (error) {
res.render("admin_create_user", {
error,
@@ -50,6 +88,7 @@ async function postCreateUser(req, res) {
}
}
+
async function changeUserRole(req, res) {
const userId = req.params.id;
const { role } = req.body;
diff --git a/controllers/companySettings.controller.js b/controllers/companySettings.controller.js
new file mode 100644
index 0000000..b6fa1aa
--- /dev/null
+++ b/controllers/companySettings.controller.js
@@ -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
+};
diff --git a/controllers/invoicePdf.controller.js b/controllers/invoicePdf.controller.js
index 40cf12b..bcd0152 100644
--- a/controllers/invoicePdf.controller.js
+++ b/controllers/invoicePdf.controller.js
@@ -1,110 +1,197 @@
const db = require("../db");
const ejs = require("ejs");
const path = require("path");
+const htmlToPdf = require("html-pdf-node");
const fs = require("fs");
-const pdf = require("html-pdf-node");
async function createInvoicePdf(req, res) {
const patientId = req.params.id;
+ const connection = await db.promise().getConnection();
try {
- // 1️⃣ Patient laden
- const [[patient]] = await db.promise().query(
+ await connection.beginTransaction();
+
+ const year = new Date().getFullYear();
+
+ // 🔒 Rechnungszähler sperren
+ const [[counterRow]] = await connection.query(
+ "SELECT counter FROM invoice_counters WHERE year = ? FOR UPDATE",
+ [year]
+ );
+
+ let counter;
+ if (!counterRow) {
+ counter = 1;
+ await connection.query(
+ "INSERT INTO invoice_counters (year, counter) VALUES (?, ?)",
+ [year, counter]
+ );
+ } else {
+ counter = counterRow.counter + 1;
+ await connection.query(
+ "UPDATE invoice_counters SET counter = ? WHERE year = ?",
+ [counter, year]
+ );
+ }
+
+ const invoiceNumber = `${year}-${String(counter).padStart(4, "0")}`;
+
+ // 🔹 Patient
+ const [[patient]] = await connection.query(
"SELECT * FROM patients WHERE id = ?",
[patientId]
);
+ if (!patient) throw new Error("Patient nicht gefunden");
- if (!patient) {
- return res.status(404).send("Patient nicht gefunden");
- }
-
- // 2️⃣ Leistungen laden
- const [rows] = await db.promise().query(`
+ // 🔹 Leistungen
+ const [rows] = await connection.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
+ s.name_de 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]);
+ `,
+ [patientId]
+ );
- if (rows.length === 0) {
- return res.send("Keine Leistungen vorhanden");
- }
+ if (!rows.length) throw new Error("Keine Leistungen vorhanden");
- const services = rows.map(s => ({
+ const services = rows.map((s) => ({
quantity: Number(s.quantity),
name: s.name,
price: Number(s.price),
- total: Number(s.price) * Number(s.quantity)
+ total: Number(s.price) * Number(s.quantity),
}));
const total = services.reduce((sum, s) => sum + s.total, 0);
- // 3️⃣ HTML rendern (NOCH OHNE invoiceId)
+ // 🔹 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: "—",
- date: new Date().toLocaleDateString("de-DE")
+ number: invoiceNumber,
+ date: new Date().toLocaleDateString("de-DE"),
};
+ // 🔹 HTML rendern
const html = await ejs.renderFile(
path.join(__dirname, "../views/invoices/invoice.ejs"),
- { patient, services, total, invoice }
+ {
+ patient,
+ services,
+ total,
+ invoice,
+ doctor,
+ company,
+ logoBase64,
+ }
);
- // 4️⃣ PDF erzeugen
- const pdfBuffer = await pdf.generatePdf(
+ // 🔹 PDF erzeugen
+ const pdfBuffer = await htmlToPdf.generatePdf(
{ content: html },
- { format: "A4" }
+ { format: "A4", printBackground: true }
);
- // 5️⃣ Datei speichern
- const dateStr = new Date().toISOString().split("T")[0];
- const fileName = `invoice_${patientId}_${dateStr}.pdf`;
- const outputPath = path.join(__dirname, "..", "documents", fileName);
+ // 💾 PDF speichern
+ fs.writeFileSync(absoluteFilePath, pdfBuffer);
- fs.writeFileSync(outputPath, pdfBuffer);
-
- // 6️⃣ Rechnung EINMAL in DB speichern
- 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;
-
- // 7️⃣ Leistungen verknüpfen
- await db.promise().query(`
+ // 🔗 Leistungen mit Rechnung verknüpfen
+ const [updateResult] = await connection.query(
+ `
UPDATE patient_services
SET invoice_id = ?
WHERE patient_id = ?
AND invoice_id IS NULL
- `, [invoiceId, patientId]);
-
- // 8️⃣ PDF anzeigen
- res.setHeader("Content-Type", "application/pdf");
- res.setHeader(
- "Content-Disposition",
- `inline; filename="${fileName}"`
+ `,
+ [invoiceId, patientId]
);
+ 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) {
- console.error("❌ PDF ERROR:", err);
- res.status(500).send("Fehler beim Erstellen der Rechnung");
+ await connection.rollback();
+ console.error("❌ INVOICE ERROR:", err);
+ res.status(500).send(err.message || "Fehler beim Erstellen der Rechnung");
+ } finally {
+ connection.release();
}
}
diff --git a/controllers/patientService.controller.js b/controllers/patientService.controller.js
index 3622776..c01670b 100644
--- a/controllers/patientService.controller.js
+++ b/controllers/patientService.controller.js
@@ -27,17 +27,15 @@ function addPatientService(req, res) {
const price = results[0].price;
db.query(
- `
- INSERT INTO patient_services
+ `INSERT INTO patient_services
(patient_id, service_id, quantity, price, service_date, created_by)
- VALUES (?, ?, ?, ?, CURDATE(), ?)
- `,
+ VALUES (?, ?, ?, ?, CURDATE(), ?) `,
[
- patientId,
- service_id,
- quantity || 1,
- price,
- req.session.user.id
+ patientId,
+ service_id,
+ quantity || 1,
+ price,
+ req.session.user.id // behandelnder Arzt
],
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 = {
addPatientService,
deletePatientService,
- updatePatientServicePrice
+ updatePatientServicePrice,
+ updatePatientServiceQuantity
};
diff --git a/controllers/service.controller.js b/controllers/service.controller.js
index 25efb3d..199e8ca 100644
--- a/controllers/service.controller.js
+++ b/controllers/service.controller.js
@@ -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 = `
SELECT
p.id AS patient_id,
@@ -232,32 +236,48 @@ function listOpenServices(req, res, next) {
ps.id AS patient_service_id,
ps.quantity,
COALESCE(ps.price_override, s.price) AS price,
-
- -- 🌍 Sprachabhängiger Servicename
CASE
- WHEN UPPER(TRIM(p.country)) = 'ES'
- THEN COALESCE(NULLIF(s.name_es, ''), s.name_de)
- ELSE s.name_de
+ WHEN UPPER(TRIM(p.country)) = 'ES'
+ THEN COALESCE(NULLIF(s.name_es, ''), s.name_de)
+ ELSE s.name_de
END AS name
-
FROM patient_services ps
JOIN patients p ON ps.patient_id = p.id
JOIN services s ON ps.service_id = s.id
WHERE ps.invoice_id IS NULL
- ORDER BY
- p.lastname,
- p.firstname,
- name
+ ORDER BY p.lastname, p.firstname, name
`;
- db.query(sql, (err, rows) => {
- if (err) return next(err);
+ let connection;
+
+ try {
+ // 🔌 EXAKT EINE Connection holen
+ connection = await db.promise().getConnection();
+
+ // 🔒 Isolation Level für DIESE Connection
+ await connection.query(
+ "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"
+ );
+
+ const [[cid]] = await connection.query(
+ "SELECT CONNECTION_ID() AS cid"
+ );
+ console.log("🔌 OPEN SERVICES CID:", cid.cid);
+
+ const [rows] = await connection.query(sql);
+
+ console.log("🧾 OPEN SERVICES ROWS:", rows.length);
res.render("open_services", {
rows,
user: req.session.user
});
- });
+
+ } catch (err) {
+ next(err);
+ } finally {
+ if (connection) connection.release();
+ }
}
diff --git a/db.js b/db.js
index 92d6cd3..d64fac8 100644
--- a/db.js
+++ b/db.js
@@ -1,15 +1,24 @@
const mysql = require("mysql2");
-const db = mysql.createConnection({
- host: "85.215.63.122",
- user: "praxisuser",
- password: "praxisuser",
- database: "praxissoftware"
+const pool = mysql.createPool({
+ host: "85.215.63.122",
+ user: "praxisuser",
+ password: "praxisuser",
+ database: "praxissoftware",
+ waitForConnections: true,
+ connectionLimit: 10,
+ queueLimit: 0
});
-db.connect(err => {
- if (err) throw err;
- console.log("MySQL verbunden");
+// Optionaler Test beim Start
+pool.getConnection((err, connection) => {
+ if (err) {
+ console.error("❌ MySQL Pool Fehler:", err);
+ process.exit(1);
+ }
+ console.log("✅ MySQL Pool verbunden");
+ connection.release();
});
-module.exports = db;
+module.exports = pool;
+
diff --git a/debug_invoice.html b/debug_invoice.html
new file mode 100644
index 0000000..abd7bd9
--- /dev/null
+++ b/debug_invoice.html
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+RECHNUNG / FACTURA
+
+
+
+
+ | Factura número |
+ — |
+ Fecha |
+ 7.1.2026 |
+
+
+ | Rechnungsnummer |
+ — |
+ Datum |
+ 7.1.2026 |
+
+
+ | N.I.E. / DNI |
+ |
+ Geburtsdatum |
+
+ 9.11.1968
+ |
+
+
+
+
+
+
+Patient:
+Cay Joksch
+Calle la Fuente 24
+38628 San Miguel de Abina
+
+
+
+
+Diagnosis / Diagnose:
+
+
+
+
+
+Für unsere Leistungen erlauben wir uns Ihnen folgendes in Rechnung zu stellen:
+
+
+
+
+ | Menge |
+ Terapia / Behandlung |
+ Preis (€) |
+ Summe (€) |
+
+
+
+
+
+
+ | 1 |
+ 1 x Ampulle Benerva 100 mg (Vitamin B1) |
+ 3.00 |
+ 3.00 |
+
+
+
+
+
+
+T O T A L: 3.00 €
+
+
+
+
+
+Behandelnder Arzt / Médico tratante:
+Cay Joksch
+
+
+Fachrichtung / Especialidad:
+Homoopath
+
+
+
+Arztnummer / Nº colegiado:
+6514.651.651.
+
+
+
+
+
+
+Forma de pago / Zahlungsform:
+Efectivo □ Tarjeta □
+Barzahlung EC/Kreditkarte
+
+
+
+
+Santander
+IBAN: ES37 0049 4507 8925 1002 3301
+BIC: BSCHESMMXXX
+
+
+
+
+
diff --git a/middleware/auth.middleware.js b/middleware/auth.middleware.js
index 92c2840..4e766c7 100644
--- a/middleware/auth.middleware.js
+++ b/middleware/auth.middleware.js
@@ -2,6 +2,10 @@ function requireLogin(req, res, next) {
if (!req.session.user) {
return res.redirect("/");
}
+
+ // optional, aber sauber
+ req.user = req.session.user;
+
next();
}
@@ -16,10 +20,12 @@ function requireAdmin(req, res, next) {
return res.send("KEIN ARZT: " + req.session.user.role);
}
+ // 🔑 DAS HAT GEFEHLT
+ req.user = req.session.user;
+
next();
}
-
module.exports = {
requireLogin,
requireAdmin
diff --git a/middleware/uploadLogo.js b/middleware/uploadLogo.js
new file mode 100644
index 0000000..60c8653
--- /dev/null
+++ b/middleware/uploadLogo.js
@@ -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 });
+
diff --git a/package-lock.json b/package-lock.json
index 3e5bf85..82692a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1559,18 +1559,6 @@
"node": ">=0.8"
}
},
- "node_modules/agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "4"
- },
- "engines": {
- "node": ">= 6.0.0"
- }
- },
"node_modules/ansi-escapes": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -2677,12 +2665,6 @@
"node": ">=8"
}
},
- "node_modules/devtools-protocol": {
- "version": "0.0.901419",
- "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.901419.tgz",
- "integrity": "sha512-4INMPwNm9XRpBukhNbF7OB6fNTTCaI8pzy/fXg0xQzAy5h3zL1P8xT3QazgKqBrb/hAYwIBizqDBZ7GtJE74QQ==",
- "license": "BSD-3-Clause"
- },
"node_modules/dezalgo": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
@@ -3742,6 +3724,143 @@
"puppeteer": "^10.4.0"
}
},
+ "node_modules/html-pdf-node/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/html-pdf-node/node_modules/debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/html-pdf-node/node_modules/devtools-protocol": {
+ "version": "0.0.901419",
+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.901419.tgz",
+ "integrity": "sha512-4INMPwNm9XRpBukhNbF7OB6fNTTCaI8pzy/fXg0xQzAy5h3zL1P8xT3QazgKqBrb/hAYwIBizqDBZ7GtJE74QQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/html-pdf-node/node_modules/https-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/html-pdf-node/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "license": "MIT"
+ },
+ "node_modules/html-pdf-node/node_modules/progress": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz",
+ "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/html-pdf-node/node_modules/puppeteer": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-10.4.0.tgz",
+ "integrity": "sha512-2cP8mBoqnu5gzAVpbZ0fRaobBWZM8GEUF4I1F6WbgHrKV/rz7SX8PG2wMymZgD0wo0UBlg2FBPNxlF/xlqW6+w==",
+ "deprecated": "< 24.15.0 is no longer supported",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "4.3.1",
+ "devtools-protocol": "0.0.901419",
+ "extract-zip": "2.0.1",
+ "https-proxy-agent": "5.0.0",
+ "node-fetch": "2.6.1",
+ "pkg-dir": "4.2.0",
+ "progress": "2.0.1",
+ "proxy-from-env": "1.1.0",
+ "rimraf": "3.0.2",
+ "tar-fs": "2.0.0",
+ "unbzip2-stream": "1.3.3",
+ "ws": "7.4.6"
+ },
+ "engines": {
+ "node": ">=10.18.1"
+ }
+ },
+ "node_modules/html-pdf-node/node_modules/tar-fs": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz",
+ "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==",
+ "license": "MIT",
+ "dependencies": {
+ "chownr": "^1.1.1",
+ "mkdirp": "^0.5.1",
+ "pump": "^3.0.0",
+ "tar-stream": "^2.0.0"
+ }
+ },
+ "node_modules/html-pdf-node/node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/html-pdf-node/node_modules/ws": {
+ "version": "7.4.6",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
+ "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/htmlparser2": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
@@ -3776,19 +3895,6 @@
"url": "https://opencollective.com/express"
}
},
- "node_modules/https-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
- "license": "MIT",
- "dependencies": {
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -5649,15 +5755,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/progress": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz",
- "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -5694,54 +5791,6 @@
"once": "^1.3.1"
}
},
- "node_modules/puppeteer": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-10.4.0.tgz",
- "integrity": "sha512-2cP8mBoqnu5gzAVpbZ0fRaobBWZM8GEUF4I1F6WbgHrKV/rz7SX8PG2wMymZgD0wo0UBlg2FBPNxlF/xlqW6+w==",
- "deprecated": "< 24.15.0 is no longer supported",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "dependencies": {
- "debug": "4.3.1",
- "devtools-protocol": "0.0.901419",
- "extract-zip": "2.0.1",
- "https-proxy-agent": "5.0.0",
- "node-fetch": "2.6.1",
- "pkg-dir": "4.2.0",
- "progress": "2.0.1",
- "proxy-from-env": "1.1.0",
- "rimraf": "3.0.2",
- "tar-fs": "2.0.0",
- "unbzip2-stream": "1.3.3",
- "ws": "7.4.6"
- },
- "engines": {
- "node": ">=10.18.1"
- }
- },
- "node_modules/puppeteer/node_modules/debug": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
- "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/puppeteer/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "license": "MIT"
- },
"node_modules/pure-rand": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
@@ -6557,34 +6606,6 @@
"url": "https://opencollective.com/synckit"
}
},
- "node_modules/tar-fs": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz",
- "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==",
- "license": "MIT",
- "dependencies": {
- "chownr": "^1.1.1",
- "mkdirp": "^0.5.1",
- "pump": "^3.0.0",
- "tar-stream": "^2.0.0"
- }
- },
- "node_modules/tar-stream": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
- "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
- "license": "MIT",
- "dependencies": {
- "bl": "^4.0.3",
- "end-of-stream": "^1.4.1",
- "fs-constants": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.1.1"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -7078,27 +7099,6 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
- "node_modules/ws": {
- "version": "7.4.6",
- "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
- "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
- "license": "MIT",
- "engines": {
- "node": ">=8.3.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": "^5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
"node_modules/xlsx": {
"version": "0.18.5",
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
diff --git a/public/images/logo.png b/public/images/logo.png
new file mode 100644
index 0000000..74d0ef6
Binary files /dev/null and b/public/images/logo.png differ
diff --git a/public/invoices/2026/invoice-2026-0019.pdf b/public/invoices/2026/invoice-2026-0019.pdf
new file mode 100644
index 0000000..64144c4
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0019.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0020.pdf b/public/invoices/2026/invoice-2026-0020.pdf
new file mode 100644
index 0000000..029243b
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0020.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0021.pdf b/public/invoices/2026/invoice-2026-0021.pdf
new file mode 100644
index 0000000..1391402
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0021.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0022.pdf b/public/invoices/2026/invoice-2026-0022.pdf
new file mode 100644
index 0000000..e350961
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0022.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0023.pdf b/public/invoices/2026/invoice-2026-0023.pdf
new file mode 100644
index 0000000..489e9ea
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0023.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0024.pdf b/public/invoices/2026/invoice-2026-0024.pdf
new file mode 100644
index 0000000..2108a7a
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0024.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0025.pdf b/public/invoices/2026/invoice-2026-0025.pdf
new file mode 100644
index 0000000..7990cec
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0025.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0026.pdf b/public/invoices/2026/invoice-2026-0026.pdf
new file mode 100644
index 0000000..bb694c3
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0026.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0027.pdf b/public/invoices/2026/invoice-2026-0027.pdf
new file mode 100644
index 0000000..d417bbc
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0027.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0028.pdf b/public/invoices/2026/invoice-2026-0028.pdf
new file mode 100644
index 0000000..ae8458d
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0028.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0029.pdf b/public/invoices/2026/invoice-2026-0029.pdf
new file mode 100644
index 0000000..a1114a0
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0029.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0030.pdf b/public/invoices/2026/invoice-2026-0030.pdf
new file mode 100644
index 0000000..6047907
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0030.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0031.pdf b/public/invoices/2026/invoice-2026-0031.pdf
new file mode 100644
index 0000000..76c8665
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0031.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0032.pdf b/public/invoices/2026/invoice-2026-0032.pdf
new file mode 100644
index 0000000..f79a7bb
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0032.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0033.pdf b/public/invoices/2026/invoice-2026-0033.pdf
new file mode 100644
index 0000000..e115524
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0033.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0034.pdf b/public/invoices/2026/invoice-2026-0034.pdf
new file mode 100644
index 0000000..4d83a39
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0034.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0035.pdf b/public/invoices/2026/invoice-2026-0035.pdf
new file mode 100644
index 0000000..703a6aa
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0035.pdf differ
diff --git a/public/invoices/2026/invoice-2026-0036.pdf b/public/invoices/2026/invoice-2026-0036.pdf
new file mode 100644
index 0000000..0f28d2f
Binary files /dev/null and b/public/invoices/2026/invoice-2026-0036.pdf differ
diff --git a/public/js/admin_create_user.js b/public/js/admin_create_user.js
new file mode 100644
index 0000000..d3d9d24
--- /dev/null
+++ b/public/js/admin_create_user.js
@@ -0,0 +1,15 @@
+document.addEventListener("DOMContentLoaded", function () {
+ const roleSelect = document.getElementById("roleSelect");
+ const arztFields = document.getElementById("arztFields");
+
+ if (!roleSelect || !arztFields) return;
+
+ function toggleArztFields() {
+ arztFields.style.display = roleSelect.value === "arzt" ? "block" : "none";
+ }
+
+ roleSelect.addEventListener("change", toggleArztFields);
+
+ // Beim Laden prüfen
+ toggleArztFields();
+});
diff --git a/public/js/open-services.js b/public/js/open-services.js
index 40b9691..d8996b6 100644
--- a/public/js/open-services.js
+++ b/public/js/open-services.js
@@ -1,26 +1,15 @@
-document.addEventListener("DOMContentLoaded", () => {
- const invoiceForms = document.querySelectorAll(".invoice-form");
-
- invoiceForms.forEach(form => {
- form.addEventListener("submit", () => {
- // Nach PDF-Erstellung Seite neu laden
- setTimeout(() => {
- window.location.reload();
- }, 1000);
- });
- });
-});
-
-document.addEventListener("DOMContentLoaded", () => {
+/* document.addEventListener("DOMContentLoaded", () => {
const invoiceForms = document.querySelectorAll(".invoice-form");
invoiceForms.forEach(form => {
form.addEventListener("submit", () => {
console.log("🧾 Rechnung erstellt – Reload folgt");
+ // kleiner Delay, damit Backend committen kann
setTimeout(() => {
window.location.reload();
}, 1200);
});
});
-});
\ No newline at end of file
+});
+ */
\ No newline at end of file
diff --git a/routes/companySettings.routes.js b/routes/companySettings.routes.js
new file mode 100644
index 0000000..fa249b6
--- /dev/null
+++ b/routes/companySettings.routes.js
@@ -0,0 +1,23 @@
+const express = require("express");
+const router = express.Router();
+const { requireAdmin } = require("../middleware/auth.middleware");
+const uploadLogo = require("../middleware/uploadLogo");
+const {
+ getCompanySettings,
+ saveCompanySettings
+} = require("../controllers/companySettings.controller");
+
+router.get(
+ "/admin/company-settings",
+ requireAdmin,
+ getCompanySettings
+);
+
+router.post(
+ "/admin/company-settings",
+ requireAdmin,
+ uploadLogo.single("logo"), // 🔑 MUSS VOR DEM CONTROLLER KOMMEN
+ saveCompanySettings
+);
+
+module.exports = router;
diff --git a/routes/patientService.routes.js b/routes/patientService.routes.js
index 0cf2f00..0578e79 100644
--- a/routes/patientService.routes.js
+++ b/routes/patientService.routes.js
@@ -5,13 +5,14 @@ const { requireLogin, requireAdmin } = require("../middleware/auth.middleware");
const {
addPatientService,
deletePatientService,
- updatePatientServicePrice
+ updatePatientServicePrice,
+ updatePatientServiceQuantity
} = require("../controllers/patientService.controller");
router.post("/:id/services", requireLogin, addPatientService);
router.post("/services/delete/:id", requireAdmin, deletePatientService);
router.post("/services/update-price/:id", requireAdmin, updatePatientServicePrice);
-
+router.post("/patients/services/update-quantity/:id", updatePatientServiceQuantity);
module.exports = router;
diff --git a/services/admin.service.js b/services/admin.service.js
index c107a32..9e21dd7 100644
--- a/services/admin.service.js
+++ b/services/admin.service.js
@@ -1,12 +1,31 @@
const bcrypt = require("bcrypt");
-async function createUser(db, username, password, role) {
+async function createUser(
+ db,
+ first_name,
+ last_name,
+ username,
+ password,
+ role,
+ fachrichtung,
+ arztnummer
+) {
const hash = await bcrypt.hash(password, 10);
return new Promise((resolve, reject) => {
db.query(
- "INSERT INTO users (username, password, role, active) VALUES (?, ?, ?, 1)",
- [username, hash, role],
+ `INSERT INTO users
+ (first_name, last_name, username, password, role, fachrichtung, arztnummer, active)
+ VALUES (?, ?, ?, ?, ?, ?, ?, 1)`,
+ [
+ first_name,
+ last_name,
+ username,
+ hash,
+ role,
+ fachrichtung,
+ arztnummer
+ ],
err => {
if (err) {
if (err.code === "ER_DUP_ENTRY") {
@@ -23,7 +42,16 @@ async function createUser(db, username, password, role) {
function getAllUsers(db) {
return new Promise((resolve, reject) => {
db.query(
- "SELECT id, username, role, active FROM users ORDER BY username",
+ `SELECT
+ id,
+ first_name,
+ last_name,
+ username,
+ role,
+ active,
+ lock_until
+ FROM users
+ ORDER BY last_name, first_name`,
(err, users) => {
if (err) return reject(err);
resolve(users);
diff --git a/services/invoiceGenerator.js b/services/invoiceGenerator.js
deleted file mode 100644
index 054e872..0000000
--- a/services/invoiceGenerator.js
+++ /dev/null
@@ -1,26 +0,0 @@
-const fs = require("fs");
-const path = require("path");
-const PizZip = require("pizzip");
-const Docxtemplater = require("docxtemplater");
-
-function generateInvoice(data) {
- const templatePath = path.join(__dirname, "../templates/invoice_template.docx");
- const content = fs.readFileSync(templatePath, "binary");
-
- const zip = new PizZip(content);
- const doc = new Docxtemplater(zip, {
- paragraphLoop: true,
- linebreaks: true
- });
-
- doc.render(data);
-
- const buffer = doc.getZip().generate({
- type: "nodebuffer",
- compression: "DEFLATE"
- });
-
- return buffer;
-}
-
-module.exports = generateInvoice;
diff --git a/views/admin/company-settings.ejs b/views/admin/company-settings.ejs
new file mode 100644
index 0000000..24a2eef
--- /dev/null
+++ b/views/admin/company-settings.ejs
@@ -0,0 +1,132 @@
+
+
+
+
+ Firmendaten
+
+
+
+
+
+
+
+
diff --git a/views/admin_create_user.ejs b/views/admin_create_user.ejs
index 8da1216..5a3c341 100644
--- a/views/admin_create_user.ejs
+++ b/views/admin_create_user.ejs
@@ -1,15 +1,16 @@
-
+
+
Benutzer anlegen
-
+
<%- include("partials/flash") %>
-
-
+
+
+
+
+
diff --git a/views/admin_users.ejs b/views/admin_users.ejs
index e229098..84faeb6 100644
--- a/views/admin_users.ejs
+++ b/views/admin_users.ejs
@@ -48,7 +48,7 @@
| ID |
- Benutzername |
+ Name |
Rolle |
Status |
Aktionen |
@@ -59,7 +59,10 @@
<% users.forEach(u => { %>
| <%= u.id %> |
- <%= u.username %> |
+
+ <%= u.first_name %> <%= u.last_name %>
+ @<%= u.username %>
+ |
<% if (u.role === "arzt") { %>
Arzt
diff --git a/views/dashboard.ejs b/views/dashboard.ejs
index 2d10015..c1d81d8 100644
--- a/views/dashboard.ejs
+++ b/views/dashboard.ejs
@@ -77,6 +77,12 @@
📜 Änderungsprotokoll (Services)
<% } %>
+
+ <% if (user.role === 'arzt') { %>
+
+ 🏢 Firmendaten
+
+ <% } %>
diff --git a/views/invoice_preview.ejs b/views/invoice_preview.ejs
new file mode 100644
index 0000000..f52fb0a
--- /dev/null
+++ b/views/invoice_preview.ejs
@@ -0,0 +1,31 @@
+
+
+
+
+ Rechnung anzeigen
+
+
+
+
+
+
+
+
diff --git a/views/invoices/invoice.ejs b/views/invoices/invoice.ejs
index 24b3174..b403724 100644
--- a/views/invoices/invoice.ejs
+++ b/views/invoices/invoice.ejs
@@ -4,22 +4,17 @@
+ tr {
+ page-break-inside: avoid;
+ }
+
+.doctor-block {
+ margin-top: 25px;
+ page-break-inside: avoid;
+}
+
+
-
- |