Bemerkung und Abzuege bei Einzahlungen
Einzahlungen koennen jetzt eine Bemerkung tragen und negativ sein, damit Abzuege (Auszahlung bei Austritt, Erstattung) nachvollziehbar gebucht werden koennen. - ledger_entries.note wurde bisher nur beim Import befuellt und wird nun auch bei Einzahlungen geschrieben und angezeigt - Negative Betraege verlangen zwingend eine Bemerkung, sonst ist spaeter nicht mehr nachvollziehbar, warum jemandem Geld abgezogen wurde - Plausibilitaetsgrenze von 1000 EUR gegen Groessenordnungs-Tippfehler; bei einem Fehler in einer Zeile wird gar nichts gebucht und die Eingaben bleiben stehen - Legacy-Tabelle kl_Einzahlungen bekommt eine Bemerkung-Spalte, sonst haette ledger_mirror_legacy_payment die Notiz beim Re-Sync wieder mit NULL ueberschrieben - PayPal-Buchungen tragen jetzt Zahler, Datum und Mitteilung als Bemerkung Ausserdem behoben: letzteneintraege.php hat Einzahlungen und Striche direkt aus den Legacy-Tabellen gelesen, ohne nach Mandant zu filtern. Jeder Mandanten-Admin sah damit die Buchungen aus den Legacy-Tabellen, und das Stornieren lief ueber die nicht mandantengetrennten Legacy-IDs. Die Seite arbeitet jetzt auf dem mandantengebundenen Journal, und ledger_void_entry prueft Mandant und Buchungsart, bevor es storniert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+46
-126
@@ -37,153 +37,80 @@ if (!$hasAccess && $saasUser === null && checkKaffeelisteAdmin($conn, $mailadres
|
||||
|
||||
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
|
||||
// Stornieren einer Einzahlung. Gearbeitet wird auf der Ledger-ID, weil nur die
|
||||
// mandantensicher geprueft werden kann - die Legacy-IDs sind nicht nach
|
||||
// Mandant getrennt. Der Eintrag wird als storniert markiert statt geloescht,
|
||||
// damit die Buchungshistorie nachvollziehbar bleibt.
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["aktion"]) && $_POST["aktion"] == "loescheneinzahlung") {
|
||||
$einzahlungID = (int)$_POST["einzahlungID"];
|
||||
$entryId = (int)($_POST["entryId"] ?? 0);
|
||||
|
||||
$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.";
|
||||
if (ledger_void_entry($pdo, $tenantId, $entryId, 'payment')) {
|
||||
app_audit_log($pdo, $tenantId, $saasUser['user_id'] ?? null, 'payment.voided', 'ledger_entries', $entryId);
|
||||
echo "<div class='hint-box success'><p>Einzahlung wurde storniert.</p></div>";
|
||||
} else {
|
||||
echo "Fehler: $ergebnis";
|
||||
echo "<div class='hint-box error'><p>Die Einzahlung konnte nicht storniert werden. Vielleicht wurde sie bereits storniert.</p></div>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 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);
|
||||
$einzahlungen = ledger_fetch_recent_entries($pdo, $tenantId, ['type' => 'payment', 'limit' => 100]);
|
||||
|
||||
?>
|
||||
|
||||
<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>
|
||||
|
||||
<p><small>Stornieren ist der richtige Weg, wenn eine Buchung <b>falsch eingetragen</b> wurde. Für einen tatsächlichen Abzug (Auszahlung, Erstattung) stattdessen unter <a href="einzahlung.php">Einzahlung eintragen</a> einen negativen Betrag mit Bemerkung buchen.</small></p>
|
||||
|
||||
<table border="1" class="table table-striped">
|
||||
<tr>
|
||||
<th>Name des Mitarbeiters</th>
|
||||
<th>Betrag</th>
|
||||
<th>Bemerkung</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, ',', '.');
|
||||
foreach ($einzahlungen as $eintrag) {
|
||||
$betrag = number_format($eintrag['amount_cents'] / 100, 2, ',', '.');
|
||||
echo "<tr>";
|
||||
echo "<td>{$mitarbeiterName}</td>";
|
||||
echo "<td>{$betrag}</td>";
|
||||
echo "<td>{$datum}</td>";
|
||||
echo "<td>" . saas_html($eintrag['display_name']) . "</td>";
|
||||
echo "<td>" . saas_html($betrag) . " €</td>";
|
||||
echo "<td>" . saas_html((string)($eintrag['note'] ?? '')) . "</td>";
|
||||
echo "<td>" . saas_html($eintrag['booked_at']) . "</td>";
|
||||
echo "<td>";
|
||||
echo "<form method='post' action='letzteneintraege.php'>";
|
||||
echo "<form method='post' action='letzteneintraege.php' onsubmit=\"return confirm('Diese Einzahlung wirklich stornieren?');\">";
|
||||
echo "<input type='hidden' name='aktion' value='loescheneinzahlung'>";
|
||||
echo "<input type='hidden' name='einzahlungID' value='{$einzahlungID}'>";
|
||||
echo "<input type='hidden' name='entryId' value='" . (int)$eintrag['id'] . "'>";
|
||||
echo app_csrf_field();
|
||||
echo "<button type='submit'>Löschen</button>";
|
||||
echo "<button type='submit'>Stornieren</button>";
|
||||
echo "</form>";
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
if ($einzahlungen === []) {
|
||||
echo "<tr><td colspan='5'>Noch keine Einzahlungen erfasst.</td></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
|
||||
// Stornieren eines Strich-Eintrags, ebenfalls ueber die mandantensichere
|
||||
// Ledger-ID.
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["aktion"]) && $_POST["aktion"] == "loeschen") {
|
||||
$strichID = (int)$_POST["strichID"];
|
||||
$entryId = (int)($_POST["entryId"] ?? 0);
|
||||
|
||||
$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.";
|
||||
if (ledger_void_entry($pdo, $tenantId, $entryId, 'consumption')) {
|
||||
app_audit_log($pdo, $tenantId, $saasUser['user_id'] ?? null, 'consumption.voided', 'ledger_entries', $entryId);
|
||||
echo "<div class='hint-box success'><p>Strich-Eintrag wurde storniert.</p></div>";
|
||||
} else {
|
||||
echo "Fehler: $ergebnis";
|
||||
echo "<div class='hint-box error'><p>Der Strich-Eintrag konnte nicht storniert werden. Vielleicht wurde er bereits storniert.</p></div>";
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
$striche = ledger_fetch_recent_entries($pdo, $tenantId, ['type' => 'consumption', 'limit' => 100]);
|
||||
|
||||
?>
|
||||
|
||||
|
||||
<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">
|
||||
@@ -195,37 +122,30 @@ $stmtStriche = sqlsrv_query($conn, $sqlStriche);
|
||||
<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, ',', '.');
|
||||
foreach ($striche as $eintrag) {
|
||||
// Verbrauch ist im Journal negativ hinterlegt, hier als positive Kosten zeigen.
|
||||
$betrag = number_format(-$eintrag['amount_cents'] / 100, 2, ',', '.');
|
||||
echo "<tr>";
|
||||
echo "<td>{$mitarbeiterName}</td>";
|
||||
echo "<td>{$anzahlStriche}</td>";
|
||||
echo "<td>{$betrag}</td>";
|
||||
echo "<td>{$datum}</td>";
|
||||
echo "<td>" . saas_html($eintrag['display_name']) . "</td>";
|
||||
echo "<td>" . (int)($eintrag['marks_count'] ?? 0) . "</td>";
|
||||
echo "<td>" . saas_html($betrag) . " €</td>";
|
||||
echo "<td>" . saas_html($eintrag['booked_at']) . "</td>";
|
||||
echo "<td>";
|
||||
echo "<form method='post' action='letzteneintraege.php'>";
|
||||
echo "<form method='post' action='letzteneintraege.php' onsubmit=\"return confirm('Diesen Strich-Eintrag wirklich stornieren?');\">";
|
||||
echo "<input type='hidden' name='aktion' value='loeschen'>";
|
||||
echo "<input type='hidden' name='strichID' value='{$strichID}'>";
|
||||
echo "<input type='hidden' name='entryId' value='" . (int)$eintrag['id'] . "'>";
|
||||
echo app_csrf_field();
|
||||
echo "<button type='submit'>Löschen</button>";
|
||||
echo "<button type='submit'>Stornieren</button>";
|
||||
echo "</form>";
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
if ($striche === []) {
|
||||
echo "<tr><td colspan='5'>Noch keine Striche erfasst.</td></tr>";
|
||||
}
|
||||
?>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}else{
|
||||
echo "<h2>Kein Zugriff</h2>";
|
||||
|
||||
Reference in New Issue
Block a user