Files
kaffeekasse-saas/einzahlung.php
T
clemensandClaude Opus 4.8 3e24910831 Legacy-Abbau Schritt 2: Legacy-Authentifizierung entfernt
Die Zugriffspruefung lief bisher in 19 Dateien auf einen Fallback gegen die
Legacy-Tabelle kl_Mitarbeiter (checkKaffeelisteAdmin/-Access). Der Zugang
haengt jetzt ausschliesslich an der SaaS-Anmeldung.

- Fallback-Bloecke in allen 19 Dateien entfernt, ebenso getUserName/getUserId
- functions.php besteht nur noch aus der SaaS-Anmeldung; die per String
  zusammengebaute Abfrage "WHERE Email like '<eingabe>'" ist damit weg
- config.php baut keine sqlsrv-Verbindung mehr auf, der Kompatibilitaetslayer
  lib/sqlsrv_mysql_compat.php ist verwaist und entfaellt
- Auto-Login per DEV_AUTH_EMAIL gibt es nicht mehr: er meldete jeden Besucher
  an und machte damit den Login-Schutz unpruefbar. Angemeldet wird auch lokal
  ueber login.php; die Variable dient nur noch scripts/init-mysql-dev.php

http-smoke war an die Golden-Master-Daten des Default-Mandanten gebunden und
hatte deshalb sechs dauerhaft rote Pruefungen. Der Test legt sich jetzt einen
eigenen Mandanten mit bekannten Salden an, meldet sich per HTTP an und raeumt
danach auf. Geschuetzte Seiten werden nicht mehr ueber das Wort "Login" im
Text geprueft, sondern ueber die tatsaechliche 302-Umleitung.

  http-smoke        27 PASS / 6 FAIL  ->  33 PASS / 0 FAIL
  role-matrix       55 PASS / 0 FAIL  ->  unveraendert
  tenant-isolation   9 PASS / 1 FAIL  ->  11 PASS / 1 FAIL (Altfehler)

Ausserdem "keine Zugang" -> "keinen Zugang" in zwei Fehlermeldungen; der
Rollentest erkannte die Ablehnung an genau diesem Tippfehler und wurde
mitgezogen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 21:10:09 +02:00

263 lines
7.7 KiB
PHP

<?php
include "functions.php";
require_once __DIR__ . "/app/ledger.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) {
echo "<h2>Kein Zugriff</h2>";
include "footer.php";
exit;
}
/**
* Liefert die Teilnehmerliste fuer die Vorder-/Rueckseiten- oder Alle-Auswahl.
* Siehe stricheintragen.php fuer dieselbe Logik.
*
* @return list<array>
*/
function einzahlung_fetch_participants(PDO $pdo, int $tenantId, string $action): array
{
if ($action === 'vorderseite' || $action === 'rueckseite') {
$settings = saas_fetch_tenant_settings($pdo, $tenantId);
$windowDays = $settings['sheet_window_days'] ?? 100;
return ledger_fetch_participants_by_window_marks($pdo, $tenantId, $windowDays, $action === 'vorderseite');
}
return ledger_fetch_participant_summaries($pdo, $tenantId, ['active_only' => true]);
}
$eingetragen = 0;
$fehlgeschlagen = false;
$hatGespeichert = false;
$validierungsFehler = [];
$eingaben = [];
// Obergrenze je Einzahlungszeile. Faengt Groessenordnungs-Tippfehler ab
// (z. B. 500 statt 5,00); groessere Betraege lassen sich in mehreren
// Schritten eintragen.
const EINZAHLUNG_MAX_BETRAG = 1000.00;
// Verarbeitung des Formulars, wenn es gesendet wurde
if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
$hatGespeichert = true;
// Teilnehmer des Mandanten: dient zugleich der Zugehoerigkeitspruefung und
// liefert die Namen fuer verstaendliche Fehlermeldungen.
$namen = [];
$stmt = $pdo->prepare('SELECT id, display_name FROM participants WHERE tenant_id = ?');
$stmt->execute([$tenantId]);
foreach ($stmt->fetchAll() as $row) {
$namen[(int)$row['id']] = (string)$row['display_name'];
}
// Erst alle Zeilen einsammeln und pruefen, dann buchen. Schlaegt eine
// Zeile fehl, wird gar nichts gebucht - sonst waere nach einem Tippfehler
// unklar, was schon drin ist und was nicht.
$zuBuchen = [];
foreach ($_POST["anzahlBetrag"] ?? [] as $participantId => $anzahlBetrag) {
$participantId = (int)$participantId;
if ($participantId <= 0 || !array_key_exists($participantId, $namen)) {
continue;
}
$rohBetrag = trim((string)$anzahlBetrag);
$bemerkung = trim((string)($_POST['bemerkung'][$participantId] ?? ''));
$eingaben[$participantId] = ['betrag' => $rohBetrag, 'bemerkung' => $bemerkung];
if ($rohBetrag === '') {
// Leere Zeile: nur meckern, wenn trotzdem eine Bemerkung dransteht
// (sonst hat jemand getippt und den Betrag vergessen).
if ($bemerkung !== '') {
$validierungsFehler[] = sprintf(
'%s: Es wurde eine Bemerkung ohne Betrag angegeben.',
$namen[$participantId] ?? ('Teilnehmer ' . $participantId)
);
}
continue;
}
$anzahlBetrag = floatval(str_replace(',', '.', $rohBetrag));
if ($anzahlBetrag == 0.0) {
continue;
}
$name = $namen[$participantId] ?? ('Teilnehmer ' . $participantId);
// Abzuege muessen begruendet werden, sonst ist spaeter nicht mehr
// nachvollziehbar, warum jemandem Geld abgezogen wurde.
if ($anzahlBetrag < 0 && $bemerkung === '') {
$validierungsFehler[] = sprintf(
'%s: Bei einem Abzug (negativer Betrag) ist eine Bemerkung Pflicht.',
$name
);
continue;
}
// Groessenordnungs-Tippfehler abfangen (500 statt 5,00).
if (abs($anzahlBetrag) > EINZAHLUNG_MAX_BETRAG) {
$validierungsFehler[] = sprintf(
'%s: %s € übersteigt die Plausibilitätsgrenze von %s €. Bitte prüfen oder in mehreren Schritten eintragen.',
$name,
number_format($anzahlBetrag, 2, ',', '.'),
number_format(EINZAHLUNG_MAX_BETRAG, 2, ',', '.')
);
continue;
}
$zuBuchen[$participantId] = [
'betrag' => $anzahlBetrag,
'bemerkung' => $bemerkung !== '' ? $bemerkung : null,
];
}
if ($validierungsFehler !== []) {
$action = 'alle';
} else {
try {
$pdo->beginTransaction();
foreach ($zuBuchen as $participantId => $zeile) {
ledger_record_payment(
$pdo,
$tenantId,
$participantId,
(int)round($zeile['betrag'] * 100),
'manual_bulk',
$saasUser['user_id'] ?? null,
$zeile['bemerkung']
);
$eingetragen++;
}
$pdo->commit();
$eingaben = []; // Erfolgreich gebucht: Formular wieder leeren.
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
$fehlgeschlagen = true;
}
$action = 'alle';
}
} else {
$action = (string)($_GET['action'] ?? 'alle');
}
$mitarbeiter = einzahlung_fetch_participants($pdo, $tenantId, $action);
?>
<h2>Einzahlungen für alle Mitarbeiter</h2>
<?php if ($hatGespeichert): ?>
<?php if ($validierungsFehler !== []): ?>
<div class="hint-box error">
<p><b>Es wurde nichts gebucht.</b> Bitte korrigiere zuerst:</p>
<ul>
<?php foreach ($validierungsFehler as $fehler): ?>
<li><?php echo saas_html($fehler); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php elseif ($fehlgeschlagen): ?>
<div class="hint-box error"><p>Die Einzahlungen konnten nicht gespeichert werden.</p></div>
<?php else: ?>
<div class="hint-box success"><p><?php echo (int)$eingetragen; ?> Einträge erfolgreich hinzugefügt.</p></div>
<?php endif; ?>
<?php endif; ?>
<div class="hint-box">
<p>In der Bemerkung könnt ihr festhalten, worum es bei einer Buchung ging. Ein <b>negativer Betrag</b> bucht einen Abzug (z.&nbsp;B. Auszahlung bei Austritt oder eine Erstattung) und braucht deshalb immer eine Bemerkung.</p>
<p><small>Wurde dagegen schlicht <b>falsch eingetragen</b>, ist ein Abzug der falsche Weg: Die Auswertung zählt sonst beide Buchungen als Umsatz. Solche Fehler stattdessen unter <a href="letzteneintraege.php">Letzte Einträge</a> stornieren.</small></p>
</div>
<ul class="actions">
<li>
<form action="einzahlung.php" method="get">
<input type="hidden" name="action" value="vorderseite">
<button type="submit">Vorderseite</button>
</form>
</li>
<li>
<form action="einzahlung.php" method="get">
<input type="hidden" name="action" value="rueckseite">
<button type="submit">Rückseite</button>
</form>
</li>
<li>
<form action="einzahlung.php" method="get">
<input type="hidden" name="action" value="alle">
<button type="submit">Alle</button>
</form>
</li>
<li>
<form action="letzteneintraege.php" method="get">
<button type="submit">Letzten Einträge</button>
</form>
</li>
<li>
<form action="csvupload.php" method="get">
<button type="submit">CSV Upload</button>
</form>
</li>
</ul>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
<?php echo app_csrf_field(); ?>
<?php
echo "<table>";
echo " <tr>
<th>Mitarbeiter</th>
<th>Betrag</th>
<th>Bemerkung</th>
</tr>";
foreach ($mitarbeiter as $teilnehmer) {
$participantId = (int)$teilnehmer['participant_id'];
$name = saas_html($teilnehmer['display_name']);
// Nach einem Validierungsfehler die Eingaben stehen lassen.
$betragWert = saas_html($eingaben[$participantId]['betrag'] ?? '');
$bemerkungWert = saas_html($eingaben[$participantId]['bemerkung'] ?? '');
echo "<tr>";
echo "<td><label for='anzahlBetrag[$participantId]'>$name </label></td>";
echo "<td><input type='number' name='anzahlBetrag[$participantId]' step='0.01' value='$betragWert'></td>";
echo "<td><input type='text' name='bemerkung[$participantId]' maxlength='1000' value='$bemerkungWert' placeholder='optional, bei Abzug Pflicht'></td>";
echo "</tr>";
}
echo "</table>";
?>
<button type="submit" name="submit" value="Speichern" >Eintragen</button>
</form>
</div>
</section>
<?php include "footer.php"; ?>