Die Migration in participants/ledger_entries ist abgeschlossen: kein
Teilnehmer traegt noch eine legacy_mitarbeiter_id, kein Journaleintrag eine
legacy_table. Damit war der jeweils zweite Zweig ("Default-Mandant schreibt
zusaetzlich nach kl_Einzahlungen/kl_Kaffeeverbrauch/kl_Mitarbeiter") in
sechs Dateien nicht mehr erreichbar - er musste aber bei jeder Aenderung
mitgepflegt werden, zuletzt bei der Bemerkung fuer Einzahlungen.
Entfernt:
- Dual-Write beim Buchen: einzahlung.php, stricheintragen.php, index.php,
jahresauswertung.php, app/imports.php, app/paypal-inbox.php
- Dual-Write in der Mitgliederverwaltung: ledger_create_participant,
ledger_update_participant, ledger_set_participant_active,
ledger_anonymize_participant
- die verwaisten Spiegelfunktionen ledger_mirror_legacy_payment,
ledger_mirror_legacy_consumption und ledger_void_entry_by_legacy_id
Nebenbei behoben: kaffeeliste.php hat die Teilnehmer-Detailseite nur fuer
Mitglieder mit legacy_mitarbeiter_id verlinkt - die hat seit der Migration
niemand mehr, die Seite war also fuer alle unerreichbar. Verlinkt und
adressiert wird jetzt ueber participant_id; "user_id" bleibt als Alias
erhalten. Der Mandanten-Isolationstest prueft jetzt ledger_void_entry, also
den Pfad, den letzteneintraege.php tatsaechlich nutzt.
Pruefskripte unveraendert gegenueber der Baseline vor dem Umbau
(http-smoke 27/6, role-matrix 55/0); die Isolationspruefung steigt von
9 auf 11 PASS bei gleichem vorbestehendem Fehler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
271 lines
8.0 KiB
PHP
271 lines
8.0 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 && $saasUser === null && checkKaffeelisteAdmin($conn, $mailadress)) {
|
|
$tenant = ledger_fetch_default_tenant($pdo);
|
|
if ($tenant !== null) {
|
|
$tenantId = (int)$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. 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"; ?>
|