Erste drei Bausteine der Haertung: - Security-Headers (X-Content-Type-Options, X-Frame-Options, Referrer- Policy, Permissions-Policy, HSTS bei HTTPS) laufen automatisch ueber app_send_security_headers() am Ende von app/bootstrap.php fuer jede dynamische Seite; landing.php war als einzige Seite ganz ohne PHP und bekam einen minimalen Bootstrap-Aufruf. Bewusst kein CSP, da die bestehenden Templates durchgaengig auf Inline-style-Attribute setzen. - DB-gestuetzte Rate-Limits (neue Tabelle rate_limit_attempts) fuer Login (10/15min je E-Mail, 20/15min je IP), Registrierung (5/h je IP) und Passwort-Reset-Anfrage (5/h je E-Mail, 10/h je IP); bei ausgereiztem Reset-Limit erscheint dieselbe generische Meldung wie im Erfolgsfall, um kein Konto-Enumeration-Signal zu geben. - Zentrales Audit-Log (neue Tabelle audit_log) fuer Mitgliederverwaltung, Zugangsvergabe/-entzug, Storno, Mandant-Einstellungen, Hinweise, CSV-Import, Jahresbonus-Verteilung und Live-Mailversand; sichtbar fuer Owner/Admin auf mandant-einstellungen.php. Live getestet: Rate-Limit greift nach 10 Fehlversuchen, Audit-Log-Eintrag mit korrekten Metadaten und Nutzernamen ueber einen isolierten Test- Mandanten geprueft. Alle Regressionstests weiterhin gruen (26/26 Smoke, 104 Golden-Master-Assertions). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
241 lines
7.0 KiB
PHP
241 lines
7.0 KiB
PHP
<?php
|
|
|
|
include "functions.php";
|
|
require_once __DIR__ . "/app/ledger.php";
|
|
require_once __DIR__ . "/app/audit.php";
|
|
app_require_csrf();
|
|
include "header.php";
|
|
include "headerline.php";
|
|
include "nav.php";
|
|
|
|
|
|
|
|
?>
|
|
<!-- Banner -->
|
|
<section id="banner">
|
|
<div class="content">
|
|
|
|
<?php
|
|
|
|
$pdo = app_db_pdo();
|
|
$saasUser = saas_current_user($pdo);
|
|
$tenantId = 0;
|
|
$hasAccess = false;
|
|
|
|
if ($saasUser !== null && saas_user_has_role(['owner', 'admin', 'treasurer'], $saasUser)) {
|
|
$tenantId = (int)$saasUser['tenant_id'];
|
|
$hasAccess = true;
|
|
}
|
|
|
|
if (!$hasAccess && $saasUser === null && checkKaffeelisteAdmin($conn, $mailadress)) {
|
|
$tenant = ledger_fetch_default_tenant($pdo);
|
|
if ($tenant !== null) {
|
|
$tenantId = (int)$tenant['id'];
|
|
$hasAccess = true;
|
|
}
|
|
}
|
|
|
|
if($hasAccess){
|
|
|
|
// Funktion zum Löschen einer Einzahlung: der Ledger-Eintrag wird als Storno
|
|
// markiert statt geloescht, damit die Buchungshistorie nachvollziehbar bleibt.
|
|
function loescheEinzahlung($einzahlungID, PDO $pdo, int $tenantId) {
|
|
try {
|
|
$pdo->beginTransaction();
|
|
|
|
ledger_void_entry_by_legacy_id($pdo, $tenantId, 'kl_Einzahlungen', $einzahlungID);
|
|
|
|
$stmt = $pdo->prepare('DELETE FROM kl_Einzahlungen WHERE EinzahlungsID = ?');
|
|
$stmt->execute([$einzahlungID]);
|
|
|
|
$pdo->commit();
|
|
|
|
return true; // Erfolgreich gelöscht
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
|
|
return 'Die Einzahlung konnte nicht gelöscht werden.';
|
|
}
|
|
}
|
|
|
|
// Überprüfen, ob ein Löschvorgang angefordert wurde
|
|
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["aktion"]) && $_POST["aktion"] == "loescheneinzahlung") {
|
|
$einzahlungID = (int)$_POST["einzahlungID"];
|
|
|
|
$ergebnis = loescheEinzahlung($einzahlungID, $pdo, $tenantId);
|
|
|
|
if ($ergebnis === true) {
|
|
app_audit_log($pdo, $tenantId, $saasUser['user_id'] ?? null, 'payment.voided', 'kl_Einzahlungen', $einzahlungID);
|
|
echo "Einzahlung erfolgreich gelöscht.";
|
|
} else {
|
|
echo "Fehler: $ergebnis";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
// SQL-Abfrage für die letzten 100 Einzahlungen
|
|
$sqlEinzahlungen = "SELECT TOP 100 kl_Mitarbeiter.Name AS MitarbeiterName, kl_Einzahlungen.EinzahlungsID, kl_Einzahlungen.Betrag, kl_Einzahlungen.Datum
|
|
FROM kl_Einzahlungen
|
|
JOIN kl_Mitarbeiter ON kl_Einzahlungen.MitarbeiterID = kl_Mitarbeiter.MitarbeiterID
|
|
ORDER BY kl_Einzahlungen.Datum DESC";
|
|
|
|
$stmtEinzahlungen = sqlsrv_query($conn, $sqlEinzahlungen);
|
|
|
|
?>
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Letzte 100 Einzahlungen</title>
|
|
</head>
|
|
|
|
|
|
<h2>Letzte 100 Einzahlungen</h2>
|
|
|
|
<table border="1" class="table table-striped">
|
|
<tr>
|
|
<th>Name des Mitarbeiters</th>
|
|
<th>Betrag</th>
|
|
<th>Datum</th>
|
|
<th>Aktion</th>
|
|
</tr>
|
|
<?php
|
|
while ($row = sqlsrv_fetch_array($stmtEinzahlungen, SQLSRV_FETCH_ASSOC)) {
|
|
$einzahlungID = $row['EinzahlungsID'];
|
|
$mitarbeiterName = $row['MitarbeiterName'];
|
|
$betrag = $row['Betrag'];
|
|
$datum = $row['Datum']->format('Y-m-d H:i:s'); // Das Format kann angepasst werden
|
|
$betrag = number_format($betrag, 2, ',', '.');
|
|
echo "<tr>";
|
|
echo "<td>{$mitarbeiterName}</td>";
|
|
echo "<td>{$betrag}</td>";
|
|
echo "<td>{$datum}</td>";
|
|
echo "<td>";
|
|
echo "<form method='post' action='{$_SERVER["PHP_SELF"]}'>";
|
|
echo "<input type='hidden' name='aktion' value='loescheneinzahlung'>";
|
|
echo "<input type='hidden' name='einzahlungID' value='{$einzahlungID}'>";
|
|
echo app_csrf_field();
|
|
echo "<button type='submit'>Löschen</button>";
|
|
echo "</form>";
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
}
|
|
?>
|
|
</table>
|
|
|
|
<br><br>
|
|
<?php
|
|
// Funktion zum Löschen eines Strich-Eintrags: der Ledger-Eintrag wird als
|
|
// Storno markiert statt geloescht, damit die Buchungshistorie nachvollziehbar bleibt.
|
|
function loescheStrichEintrag($strichID, PDO $pdo, int $tenantId) {
|
|
try {
|
|
$pdo->beginTransaction();
|
|
|
|
ledger_void_entry_by_legacy_id($pdo, $tenantId, 'kl_Kaffeeverbrauch', $strichID);
|
|
|
|
$stmt = $pdo->prepare('DELETE FROM kl_Kaffeeverbrauch WHERE VerbrauchID = ?');
|
|
$stmt->execute([$strichID]);
|
|
|
|
$pdo->commit();
|
|
|
|
return true; // Erfolgreich gelöscht
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
|
|
return 'Der Strich-Eintrag konnte nicht gelöscht werden.';
|
|
}
|
|
}
|
|
|
|
// Überprüfen, ob ein Löschvorgang angefordert wurde
|
|
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["aktion"]) && $_POST["aktion"] == "loeschen") {
|
|
$strichID = (int)$_POST["strichID"];
|
|
|
|
$ergebnis = loescheStrichEintrag($strichID, $pdo, $tenantId);
|
|
|
|
if ($ergebnis === true) {
|
|
app_audit_log($pdo, $tenantId, $saasUser['user_id'] ?? null, 'consumption.voided', 'kl_Kaffeeverbrauch', $strichID);
|
|
echo "Strich-Eintrag erfolgreich gelöscht.";
|
|
} else {
|
|
echo "Fehler: $ergebnis";
|
|
}
|
|
}
|
|
|
|
// SQL-Abfrage für die letzten 100 Strich-Einträge
|
|
$sqlStriche = "SELECT TOP 100 kl_Kaffeeverbrauch.VerbrauchID, kl_Mitarbeiter.Name AS MitarbeiterName, kl_Kaffeeverbrauch.AnzahlStriche, kl_Kaffeeverbrauch.Kosten, kl_Kaffeeverbrauch.Datum
|
|
FROM kl_Kaffeeverbrauch
|
|
JOIN kl_Mitarbeiter ON kl_Kaffeeverbrauch.MitarbeiterID = kl_Mitarbeiter.MitarbeiterID
|
|
ORDER BY kl_Kaffeeverbrauch.Datum DESC";
|
|
|
|
$stmtStriche = sqlsrv_query($conn, $sqlStriche);
|
|
|
|
?>
|
|
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Letzte 100 Strich-Einträge</title>
|
|
</head>
|
|
<body>
|
|
|
|
<h2>Letzte 100 Strich-Einträge</h2>
|
|
|
|
<table border="1" class="table table-striped">
|
|
<tr>
|
|
<th>Name des Mitarbeiters</th>
|
|
<th>Anzahlstriche</th>
|
|
<th>Betrag</th>
|
|
<th>Datum</th>
|
|
<th>Aktion</th>
|
|
</tr>
|
|
<?php
|
|
while ($row = sqlsrv_fetch_array($stmtStriche, SQLSRV_FETCH_ASSOC)) {
|
|
$strichID = $row['VerbrauchID'];
|
|
$mitarbeiterName = $row['MitarbeiterName'];
|
|
$anzahlStriche = $row['AnzahlStriche'];
|
|
$betrag = $row['Kosten'];
|
|
$datum = $row['Datum']->format('Y-m-d H:i:s'); // Das Format kann angepasst werden
|
|
$anzahlStriche = number_format($anzahlStriche, 0, ',', '.');
|
|
$betrag = number_format($betrag, 0, ',', '.');
|
|
echo "<tr>";
|
|
echo "<td>{$mitarbeiterName}</td>";
|
|
echo "<td>{$anzahlStriche}</td>";
|
|
echo "<td>{$betrag}</td>";
|
|
echo "<td>{$datum}</td>";
|
|
echo "<td>";
|
|
echo "<form method='post' action='{$_SERVER["PHP_SELF"]}'>";
|
|
echo "<input type='hidden' name='aktion' value='loeschen'>";
|
|
echo "<input type='hidden' name='strichID' value='{$strichID}'>";
|
|
echo app_csrf_field();
|
|
echo "<button type='submit'>Löschen</button>";
|
|
echo "</form>";
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
}
|
|
?>
|
|
</table>
|
|
|
|
|
|
|
|
|
|
</body>
|
|
</html>
|
|
<?php
|
|
}else{
|
|
echo "<h2>Kein Zugriff</h2>";
|
|
|
|
}
|
|
?>
|
|
|
|
</div>
|
|
</section>
|
|
|
|
|
|
<?php include "footer.php"; ?>
|